summaryrefslogtreecommitdiffstats
path: root/vendor/clue
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/clue')
-rw-r--r--vendor/clue/block-react/CHANGELOG.md152
-rw-r--r--vendor/clue/block-react/LICENSE21
-rw-r--r--vendor/clue/block-react/README.md335
-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/connection-manager-extra/CHANGELOG.md191
-rw-r--r--vendor/clue/connection-manager-extra/LICENSE21
-rw-r--r--vendor/clue/connection-manager-extra/README.md287
-rw-r--r--vendor/clue/connection-manager-extra/composer.json29
-rw-r--r--vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php41
-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.php46
-rw-r--r--vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php34
-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/CHANGELOG.md200
-rw-r--r--vendor/clue/http-proxy-react/LICENSE21
-rw-r--r--vendor/clue/http-proxy-react/README.md510
-rw-r--r--vendor/clue/http-proxy-react/composer.json31
-rw-r--r--vendor/clue/http-proxy-react/src/ProxyConnector.php278
-rw-r--r--vendor/clue/mq-react/CHANGELOG.md96
-rw-r--r--vendor/clue/mq-react/LICENSE21
-rw-r--r--vendor/clue/mq-react/README.md532
-rw-r--r--vendor/clue/mq-react/composer.json33
-rw-r--r--vendor/clue/mq-react/src/Queue.php465
-rw-r--r--vendor/clue/redis-protocol/.travis.yml8
-rw-r--r--vendor/clue/redis-protocol/CHANGELOG.md50
-rw-r--r--vendor/clue/redis-protocol/README.md139
-rw-r--r--vendor/clue/redis-protocol/composer.json19
-rw-r--r--vendor/clue/redis-protocol/example/client.php22
-rw-r--r--vendor/clue/redis-protocol/example/perf.php31
-rw-r--r--vendor/clue/redis-protocol/phpunit.xml.dist19
-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-protocol/tests/FactoryTest.php34
-rw-r--r--vendor/clue/redis-protocol/tests/Model/AbstractModelTest.php22
-rw-r--r--vendor/clue/redis-protocol/tests/Model/BulkReplyTest.php43
-rw-r--r--vendor/clue/redis-protocol/tests/Model/ErrorReplyTest.php19
-rw-r--r--vendor/clue/redis-protocol/tests/Model/IntegerReplyTest.php40
-rw-r--r--vendor/clue/redis-protocol/tests/Model/MultiBulkReplyTest.php115
-rw-r--r--vendor/clue/redis-protocol/tests/Model/RequestTest.php37
-rw-r--r--vendor/clue/redis-protocol/tests/Model/StatusReplyTest.php19
-rw-r--r--vendor/clue/redis-protocol/tests/Parser/AbstractParserTest.php67
-rw-r--r--vendor/clue/redis-protocol/tests/Parser/RequestParserTest.php132
-rw-r--r--vendor/clue/redis-protocol/tests/Parser/ResponseParserTest.php130
-rw-r--r--vendor/clue/redis-protocol/tests/Serializer/AbstractSerializerTest.php141
-rw-r--r--vendor/clue/redis-protocol/tests/Serializer/RecursiveSerializerTest.php11
-rw-r--r--vendor/clue/redis-protocol/tests/bootstrap.php7
-rw-r--r--vendor/clue/redis-react/CHANGELOG.md268
-rw-r--r--vendor/clue/redis-react/LICENSE21
-rw-r--r--vendor/clue/redis-react/README.md660
-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/CHANGELOG.md130
-rw-r--r--vendor/clue/soap-react/LICENSE21
-rw-r--r--vendor/clue/soap-react/README.md453
-rw-r--r--vendor/clue/soap-react/composer.json29
-rw-r--r--vendor/clue/soap-react/src/Client.php326
-rw-r--r--vendor/clue/soap-react/src/Protocol/ClientDecoder.php51
-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/CHANGELOG.md96
-rw-r--r--vendor/clue/socket-raw/LICENSE21
-rw-r--r--vendor/clue/socket-raw/README.md258
-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/CHANGELOG.md481
-rw-r--r--vendor/clue/socks-react/LICENSE21
-rw-r--r--vendor/clue/socks-react/README.md1116
-rw-r--r--vendor/clue/socks-react/composer.json31
-rw-r--r--vendor/clue/socks-react/src/Client.php458
-rw-r--r--vendor/clue/socks-react/src/Server.php416
-rw-r--r--vendor/clue/socks-react/src/StreamReader.php149
-rw-r--r--vendor/clue/stdio-react/CHANGELOG.md281
-rw-r--r--vendor/clue/stdio-react/LICENSE21
-rw-r--r--vendor/clue/stdio-react/README.md708
-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/CHANGELOG.md63
-rw-r--r--vendor/clue/term-react/LICENSE21
-rw-r--r--vendor/clue/term-react/README.md171
-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/CHANGELOG.md41
-rw-r--r--vendor/clue/utf8-react/LICENSE21
-rw-r--r--vendor/clue/utf8-react/README.md116
-rw-r--r--vendor/clue/utf8-react/composer.json27
-rw-r--r--vendor/clue/utf8-react/src/Sequencer.php174
111 files changed, 16417 insertions, 0 deletions
diff --git a/vendor/clue/block-react/CHANGELOG.md b/vendor/clue/block-react/CHANGELOG.md
new file mode 100644
index 0000000..5f145e9
--- /dev/null
+++ b/vendor/clue/block-react/CHANGELOG.md
@@ -0,0 +1,152 @@
+# Changelog
+
+## 1.5.0 (2021-10-20)
+
+* Feature: Simplify usage by supporting new [default loop](https://github.com/reactphp/event-loop#loop).
+ (#60 by @clue)
+
+ ```php
+ // old (still supported)
+ Clue\React\Block\await($promise, $loop);
+ Clue\React\Block\awaitAny($promises, $loop);
+ Clue\React\Block\awaitAll($promises, $loop);
+
+ // new (using default loop)
+ Clue\React\Block\await($promise);
+ Clue\React\Block\awaitAny($promises);
+ Clue\React\Block\awaitAll($promises);
+ ```
+
+* Feature: Added support for upcoming react/promise v3.
+ (#61 by @davidcole1340 and @SimonFrings)
+
+* Improve error reporting by appending previous message for `Throwable`s.
+ (#57 by @clue)
+
+* Deprecate `$timeout` argument for `await*()` functions.
+ (#59 by @clue)
+
+ ```php
+ // deprecated
+ Clue\React\Block\await($promise, $loop, $timeout);
+ Clue\React\Block\awaitAny($promises, $loop, $timeout);
+ Clue\React\Block\awaitAll($promises, $loop, $timeout);
+
+ // still supported
+ Clue\React\Block\await($promise, $loop);
+ Clue\React\Block\awaitAny($promises, $loop);
+ Clue\React\Block\awaitAll($promises, $loop);
+ ```
+
+* Improve API documentation.
+ (#58 and #63 by @clue and #55 by @PaulRotmann)
+
+* Improve test suite and use GitHub actions for continuous integration (CI).
+ (#54 by @SimonFrings)
+
+## 1.4.0 (2020-08-21)
+
+* Improve API documentation, update README and add examples.
+ (#45 by @clue and #51 by @SimonFrings)
+
+* Improve test suite and add `.gitattributes` to exclude dev files from exports.
+ Prepare PHP 8 support, update to PHPUnit 9, run tests on PHP 7.4 and simplify test matrix.
+ (#46, #47 and #50 by @SimonFrings)
+
+## 1.3.1 (2019-04-09)
+
+* Fix: Fix getting the type of unexpected rejection reason when not rejecting with an `Exception`.
+ (#42 by @Furgas and @clue)
+
+* Fix: Check if the function is declared before declaring it.
+ (#39 by @Niko9911)
+
+## 1.3.0 (2018-06-14)
+
+* Feature: Improve memory consumption by cleaning up garbage references.
+ (#35 by @clue)
+
+* Fix minor documentation typos.
+ (#28 by @seregazhuk)
+
+* Improve test suite by locking Travis distro so new defaults will not break the build,
+ support PHPUnit 6 and update Travis config to also test against PHP 7.2.
+ (#30 by @clue, #31 by @carusogabriel and #32 by @andreybolonin)
+
+* Update project homepage.
+ (#34 by @clue)
+
+## 1.2.0 (2017-08-03)
+
+* Feature / Fix: Forward compatibility with future EventLoop v1.0 and v0.5 and
+ cap small timeout values for legacy EventLoop
+ (#26 by @clue)
+
+ ```php
+ // now works across all versions
+ Block\sleep(0.000001, $loop);
+ ```
+
+* Feature / Fix: Throw `UnexpectedValueException` if Promise gets rejected with non-Exception
+ (#27 by @clue)
+
+ ```php
+ // now throws an UnexceptedValueException
+ Block\await(Promise\reject(false), $loop);
+ ```
+
+* First class support for legacy PHP 5.3 through PHP 7.1 and HHVM
+ (#24 and #25 by @clue)
+
+* Improve testsuite by adding PHPUnit to require-dev and
+ Fix HHVM build for now again and ignore future HHVM build errors
+ (#23 and #24 by @clue)
+
+## 1.1.0 (2016-03-09)
+
+* Feature: Add optional timeout parameter to all await*() functions
+ (#17 by @clue)
+
+* Feature: Cancellation is now supported across all PHP versions
+ (#16 by @clue)
+
+## 1.0.0 (2015-11-13)
+
+* First stable release, now following SemVer
+* Improved documentation
+
+> Contains no other changes, so it's actually fully compatible with the v0.3.0 release.
+
+## 0.3.0 (2015-07-09)
+
+* BC break: Use functional API approach instead of pseudo-OOP.
+ All existing methods are now exposed as simple functions.
+ ([#13](https://github.com/clue/php-block-react/pull/13))
+ ```php
+// old
+$blocker = new Block\Blocker($loop);
+$result = $blocker->await($promise);
+
+// new
+$result = Block\await($promise, $loop);
+```
+
+## 0.2.0 (2015-07-05)
+
+* BC break: Rename methods in order to avoid confusion.
+ * Rename `wait()` to `sleep()`.
+ ([#8](https://github.com/clue/php-block-react/pull/8))
+ * Rename `awaitRace()` to `awaitAny()`.
+ ([#9](https://github.com/clue/php-block-react/pull/9))
+ * Rename `awaitOne()` to `await()`.
+ ([#10](https://github.com/clue/php-block-react/pull/10))
+
+## 0.1.1 (2015-04-05)
+
+* `run()` the loop instead of making it `tick()`.
+ This results in significant performance improvements (less resource utilization) by avoiding busy waiting
+ ([#1](https://github.com/clue/php-block-react/pull/1))
+
+## 0.1.0 (2015-04-04)
+
+* First tagged release
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/README.md b/vendor/clue/block-react/README.md
new file mode 100644
index 0000000..d000a16
--- /dev/null
+++ b/vendor/clue/block-react/README.md
@@ -0,0 +1,335 @@
+# clue/reactphp-block
+
+[![CI status](https://github.com/clue/reactphp-block/workflows/CI/badge.svg)](https://github.com/clue/reactphp-block/actions)
+[![installs on Packagist](https://img.shields.io/packagist/dt/clue/block-react?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/block-react)
+
+Lightweight library that eases integrating async components built for
+[ReactPHP](https://reactphp.org/) in a traditional, blocking environment.
+
+[ReactPHP](https://reactphp.org/) provides you a great set of base components and
+a huge ecosystem of third party libraries in order to perform async operations.
+The event-driven paradigm and asynchronous processing of any number of streams
+in real time enables you to build a whole new set of application on top of it.
+This is great for building modern, scalable applications from scratch and will
+likely result in you relying on a whole new software architecture.
+
+But let's face it: Your day-to-day business is unlikely to allow you to build
+everything from scratch and ditch your existing production environment.
+This is where this library comes into play:
+
+*Let's block ReactPHP*
+More specifically, this library eases the pain of integrating async components
+into your traditional, synchronous (blocking) application stack.
+
+**Table of contents**
+
+* [Support us](#support-us)
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [sleep()](#sleep)
+ * [await()](#await)
+ * [awaitAny()](#awaitany)
+ * [awaitAll()](#awaitall)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+### Quickstart example
+
+The following example code demonstrates how this library can be used along with
+an [async HTTP client](https://github.com/reactphp/http#client-usage) to process two
+non-blocking HTTP requests and block until the first (faster) one resolves.
+
+```php
+function blockingExample()
+{
+ // this example uses an HTTP client
+ // this could be pretty much everything that binds to an event loop
+ $browser = new React\Http\Browser();
+
+ // set up two parallel requests
+ $request1 = $browser->get('http://www.google.com/');
+ $request2 = $browser->get('http://www.google.co.uk/');
+
+ // keep the loop running (i.e. block) until the first response arrives
+ $fasterResponse = Clue\React\Block\awaitAny(array($request1, $request2));
+
+ return $fasterResponse->getBody();
+}
+```
+
+## Usage
+
+This lightweight library consists only of a few simple functions.
+All functions reside under the `Clue\React\Block` namespace.
+
+The below examples refer to all functions with their fully-qualified names like this:
+
+```php
+Clue\React\Block\await(…);
+```
+
+As of PHP 5.6+ you can also import each required function into your code like this:
+
+```php
+use function Clue\React\Block\await;
+
+await(…);
+```
+
+Alternatively, you can also use an import statement similar to this:
+
+```php
+use Clue\React\Block;
+
+Block\await(…);
+```
+
+### sleep()
+
+The `sleep(float $seconds, ?LoopInterface $loop = null): void` function can be used to
+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.
+
+### await()
+
+The `await(PromiseInterface $promise, ?LoopInterface $loop = null, ?float $timeout = null): mixed` function can be used to
+block waiting for the given `$promise` to be fulfilled.
+
+```php
+$result = Clue\React\Block\await($promise);
+```
+
+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);
+ // 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.
+
+### awaitAny()
+
+The `awaitAny(PromiseInterface[] $promises, ?LoopInterface $loop = null, ?float $timeout = null): mixed` function can be used to
+wait for ANY of the given promises to be fulfilled.
+
+```php
+$promises = array(
+ $promise1,
+ $promise2
+);
+
+$firstResult = Clue\React\Block\awaitAny($promises);
+
+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.
+
+### awaitAll()
+
+The `awaitAll(PromiseInterface[] $promises, ?LoopInterface $loop = null, ?float $timeout = null): mixed[]` function can be used to
+wait for ALL of the given promises to be fulfilled.
+
+```php
+$promises = array(
+ $promise1,
+ $promise2
+);
+
+$allResults = Clue\React\Block\awaitAll($promises);
+
+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.
+Likewise, this will return an empty array if an empty array of `$promises` is 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.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org/).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require clue/block-react:^1.5
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
+HHVM.
+It's *highly recommended to use the latest supported PHP version* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org/):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ vendor/bin/phpunit
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
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/connection-manager-extra/CHANGELOG.md b/vendor/clue/connection-manager-extra/CHANGELOG.md
new file mode 100644
index 0000000..5402fd5
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/CHANGELOG.md
@@ -0,0 +1,191 @@
+# Changelog
+
+## 1.3.0 (2022-08-30)
+
+* Feature: Simplify usage by supporting new default loop.
+ (#33 by @SimonFrings)
+
+ ```php
+ // old (still supported)
+ $connector = new ConnectionManagerTimeout($connector, 3.0, $loop);
+ $delayed = new ConnectionManagerDelayed($connector, 0.5, $loop);
+
+ // new (using default loop)
+ $connector = new ConnectionManagerTimeout($connector, 3.0);
+ $delayed = new ConnectionManagerDelayed($connector, 0.5);
+ ```
+
+* Feature: Full support for PHP 8.1 and PHP 8.2.
+ (#36 and #37 by @SimonFrings)
+
+* Feature: Forward compatibility with upcoming Promise v3.
+ (#34 by @clue)
+
+* Improve test suite and add badge to show number of project installations.
+ (#35 by @SimonFrings and #31 by @PaulRotmann)
+
+## 1.2.0 (2020-12-12)
+
+* Improve test suite and add `.gitattributes` to exclude dev files from exports.
+ Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
+ (#24, #25 and #26 by @clue and #27, #28, #29 and #30 by @SimonFrings)
+
+## 1.1.0 (2017-08-03)
+
+* Feature: Support custom rejection reason for ConnectionManagerReject
+ (#23 by @clue)
+
+ ```php
+ $connector = new ConnectionManagerReject(function ($uri) {
+ throw new RuntimeException($uri . ' blocked');
+ });
+ ```
+
+## 1.0.1 (2017-06-23)
+
+* Fix: Ignore URI scheme when matching selective connectors
+ (#21 by @clue)
+
+* Fix HHVM build for now again and ignore future HHVM build errors
+ (#22 by @clue)
+
+## 1.0.0 (2017-05-09)
+
+* First stable release, now following SemVer
+
+ > Contains no other changes, so it's actually fully compatible with the v0.7 releases.
+
+## 0.7.1 (2017-05-09)
+
+* Fix: Reject promise for invalid URI passed to ConnectionManagerSelective
+ (#19 by @clue)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8 and
+ upcoming EventLoop v1.0 and v0.5
+ (#18 and #20 by @clue)
+
+## 0.7.0 (2017-04-10)
+
+* Feature / BC break: Replace deprecated SocketClient with new Socket component
+ (#17 by @clue)
+
+ This implies that all connectors from this package now implement the
+ `React\Socket\ConnectorInterface` instead of the legacy
+ `React\SocketClient\ConnectorInterface`.
+
+## 0.6.0 (2017-04-07)
+
+* Feature / BC break: Update SocketClient to v0.7 or v0.6
+ (#16 by @clue)
+
+* Improve test suite by adding PHPUnit to require-dev
+ (#15 by @clue)
+
+## 0.5.0 (2016-06-01)
+
+* BC break: Change $retries to $tries
+ (#14 by @clue)
+
+ ```php
+ // old
+ // 1 try plus 2 retries => 3 total tries
+ $c = new ConnectionManagerRepeat($c, 2);
+
+ // new
+ // 3 total tries (1 try plus 2 retries)
+ $c = new ConnectionManagerRepeat($c, 3);
+ ```
+
+* BC break: Timed connectors now use $loop as last argument
+ (#13 by @clue)
+
+ ```php
+ // old
+ // $c = new ConnectionManagerDelay($c, $loop, 1.0);
+ $c = new ConnectionManagerTimeout($c, $loop, 1.0);
+
+ // new
+ $c = new ConnectionManagerTimeout($c, 1.0, $loop);
+ ```
+
+* BC break: Move all connector lists to the constructor
+ (#12 by @clue)
+
+ ```php
+ // old
+ // $c = new ConnectionManagerConcurrent();
+ // $c = new ConnectionManagerRandom();
+ $c = new ConnectionManagerConsecutive();
+ $c->addConnectionManager($c1);
+ $c->addConnectionManager($c2);
+
+ // new
+ $c = new ConnectionManagerConsecutive(array(
+ $c1,
+ $c2
+ ));
+ ```
+
+* BC break: ConnectionManagerSelective now accepts connector list in constructor
+ (#11 by @clue)
+
+ ```php
+ // old
+ $c = new ConnectionManagerSelective();
+ $c->addConnectionManagerFor($c1, 'host1');
+ $c->addConnectionManagerFor($c2, 'host2');
+
+ // new
+ $c = new ConnectionManagerSelective(array(
+ 'host1' => $c1,
+ 'host2' => $c2
+ ));
+ ```
+
+## 0.4.0 (2016-05-30)
+
+* Feature: Add `ConnectionManagerConcurrent`
+ (#10 by @clue)
+
+* Feature: Support Promise cancellation for all connectors
+ (#9 by @clue)
+
+## 0.3.3 (2016-05-29)
+
+* Fix repetitions for `ConnectionManagerRepeat`
+ (#8 by @clue)
+
+* First class support for PHP 5.3 through PHP 7 and HHVM
+ (#7 by @clue)
+
+## 0.3.2 (2016-03-19)
+
+* Compatibility with react/socket-client:v0.5 (keeping full BC)
+ (#6 by @clue)
+
+## 0.3.1 (2014-09-27)
+
+* Support React PHP v0.4 (while preserving BC with React PHP v0.3)
+ (#4)
+
+## 0.3.0 (2013-06-24)
+
+* BC break: Switch from (deprecated) `clue/connection-manager` to `react/socket-client`
+ and thus replace each occurance of `getConnect($host, $port)` with `create($host, $port)`
+ (#1)
+
+* Fix: Timeouts in `ConnectionManagerTimeout` now actually work
+ (#1)
+
+* Fix: Properly reject promise in `ConnectionManagerSelective` when no targets
+ have been found
+ (#1)
+
+## 0.2.0 (2013-02-08)
+
+* Feature: Add `ConnectionManagerSelective` which works like a network/firewall ACL
+
+## 0.1.0 (2013-01-12)
+
+* First tagged release
+
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/README.md b/vendor/clue/connection-manager-extra/README.md
new file mode 100644
index 0000000..48eb599
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/README.md
@@ -0,0 +1,287 @@
+# clue/reactphp-connection-manager-extra
+
+[![CI status](https://github.com/clue/reactphp-connection-manager-extra/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/reactphp-connection-manager-extra/actions)
+[![installs on Packagist](https://img.shields.io/packagist/dt/clue/connection-manager-extra?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/connection-manager-extra)
+
+This project provides _extra_ (in terms of "additional", "extraordinary", "special" and "unusual") decorators,
+built on top of [ReactPHP's Socket](https://github.com/reactphp/socket).
+
+**Table of Contents**
+
+* [Support us](#support-us)
+* [Introduction](#introduction)
+* [Usage](#usage)
+ * [Repeat](#repeat)
+ * [Timeout](#timeout)
+ * [Delay](#delay)
+ * [Reject](#reject)
+ * [Swappable](#swappable)
+ * [Consecutive](#consecutive)
+ * [Random](#random)
+ * [Concurrent](#concurrent)
+ * [Selective](#selective)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Introduction
+
+If you're not already familar with [react/socket](https://github.com/reactphp/socket),
+think of it as an async (non-blocking) version of [`fsockopen()`](https://www.php.net/manual/en/function.fsockopen.php)
+or [`stream_socket_client()`](https://www.php.net/manual/en/function.stream-socket-client.php).
+I.e. before you can send and receive data to/from a remote server, you first have to establish a connection - which
+takes its time because it involves several steps.
+In order to be able to establish several connections at the same time, [react/socket](https://github.com/reactphp/socket) provides a simple
+API to establish simple connections in an async (non-blocking) way.
+
+This project includes several classes that extend this base functionality by implementing the same simple `ConnectorInterface`.
+This interface provides a single promise-based method `connect($uri)` which can be used to easily notify
+when the connection is successfully established or the `Connector` gives up and the connection fails.
+
+```php
+$connector->connect('www.google.com:80')->then(function ($stream) {
+ echo 'connection successfully established';
+ $stream->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
+ $stream->end();
+}, function ($exception) {
+ echo 'connection attempt failed: ' . $exception->getMessage();
+});
+
+```
+
+Because everything uses the same simple API, the resulting `Connector` classes can be easily interchanged
+and be used in places that expect the normal `ConnectorInterface`. This can be used to stack them into each other,
+like using [timeouts](#timeout) for TCP connections, [delaying](#delay) SSL/TLS connections,
+[retrying](#repeat) failed connection attempts, [randomly](#random) picking a `Connector` or
+any combination thereof.
+
+## Usage
+
+This section lists all features of this library along with some examples.
+The examples assume you've [installed](#install) this library and
+already [set up a `Socket/Connector` instance `$connector`](https://github.com/reactphp/socket#connector).
+
+All classes are located in the `ConnectionManager\Extra` namespace.
+
+### Repeat
+
+The `ConnectionManagerRepeat($connector, $tries)` tries connecting to the given location up to a maximum
+of `$tries` times when the connection fails.
+
+If you pass a value of `3` to it, it will first issue a normal connection attempt
+and then retry up to 2 times if the connection attempt fails:
+
+```php
+$connectorRepeater = new ConnectionManagerRepeat($connector, 3);
+
+$connectorRepeater->connect('www.google.com:80')->then(function ($stream) {
+ echo 'connection successfully established';
+ $stream->close();
+});
+```
+
+### Timeout
+
+The `ConnectionManagerTimeout($connector, $timeout, $loop = null)` sets a maximum `$timeout` in seconds on when to give up
+waiting for the connection to complete.
+
+```php
+$connector = new ConnectionManagerTimeout($connector, 3.0);
+```
+
+### Delay
+
+The `ConnectionManagerDelay($connector, $delay, $loop = null)` sets a fixed initial `$delay` in seconds before actually
+trying to connect. (Not to be confused with [`ConnectionManagerTimeout`](#timeout) which sets a _maximum timeout_.)
+
+```php
+$delayed = new ConnectionManagerDelayed($connector, 0.5);
+```
+
+### Reject
+
+The `ConnectionManagerReject(null|string|callable $reason)` simply rejects every single connection attempt.
+This is particularly useful for the below [`ConnectionManagerSelective`](#selective) to reject connection attempts
+to only certain destinations (for example blocking advertisements or harmful sites).
+
+The constructor accepts an optional rejection reason which will be used for
+rejecting the resulting promise.
+
+You can explicitly pass a `string` value which will be used as the message for
+the `Exception` instance:
+
+```php
+$connector = new ConnectionManagerReject('Blocked');
+$connector->connect('www.google.com:80')->then(null, function ($e) {
+ assert($e instanceof \Exception);
+ assert($e->getMessage() === 'Blocked');
+});
+```
+
+You can explicitly pass a `callable` value which will be used to either
+`throw` or `return` a custom `Exception` instance:
+
+```php
+$connector = new ConnectionManagerReject(function ($uri) {
+ throw new RuntimeException($uri . ' blocked');
+});
+$connector->connect('www.google.com:80')->then(null, function ($e) {
+ assert($e instanceof \RuntimeException);
+ assert($e->getMessage() === 'www.google.com:80 blocked');
+});
+```
+
+### Swappable
+
+The `ConnectionManagerSwappable($connector)` is a simple decorator for other `ConnectionManager`s to
+simplify exchanging the actual `ConnectionManager` during runtime (`->setConnectionManager($connector)`).
+
+### Consecutive
+
+The `ConnectionManagerConsecutive($connectors)` establishes connections by trying to connect through
+any of the given `ConnectionManager`s in consecutive order until the first one succeeds.
+
+```php
+$consecutive = new ConnectionManagerConsecutive(array(
+ $connector1,
+ $connector2
+));
+```
+
+### Random
+
+The `ConnectionManagerRandom($connectors)` works much like `ConnectionManagerConsecutive` but instead
+of using a fixed order, it always uses a randomly shuffled order.
+
+```php
+$random = new ConnectionManagerRandom(array(
+ $connector1,
+ $connector2
+));
+```
+
+### Concurrent
+
+The `ConnectionManagerConcurrent($connectors)` establishes connections by trying to connect through
+ALL of the given `ConnectionManager`s at once, until the first one succeeds.
+
+```php
+$concurrent = new ConnectionManagerConcurrent(array(
+ $connector1,
+ $connector2
+));
+```
+
+### Selective
+
+The `ConnectionManagerSelective($connectors)` manages a list of `Connector`s and
+forwards each connection through the first matching one.
+This can be used to implement networking access control lists (ACLs) or firewall
+rules like a blacklist or whitelist.
+
+This allows fine-grained control on how to handle outgoing connections, like
+rejecting advertisements, delaying unencrypted HTTP requests or forwarding HTTPS
+connection through a foreign country.
+
+If none of the entries in the list matches, the connection will be rejected.
+This can be used to implement a very simple whitelist like this:
+
+```php
+$selective = new ConnectionManagerSelective(array(
+ 'github.com' => $connector,
+ '*:443' => $connector
+));
+```
+
+If you want to implement a blacklist (i.e. reject only certain targets), make
+sure to add a default target to the end of the list like this:
+
+```php
+$reject = new ConnectionManagerReject();
+$selective = new ConnectionManagerSelective(array(
+ 'ads.example.com' => $reject,
+ '*:80-81' => $reject,
+ '*' => $connector
+));
+```
+
+Similarly, you can also combine any of the other connectors to implement more
+advanced connection setups, such as delaying unencrypted connections only and
+retrying unreliable hosts:
+
+```php
+// delay connection by 2 seconds
+$delayed = new ConnectionManagerDelay($connector, 2.0);
+
+// maximum of 3 tries, each taking no longer than 2.0 seconds
+$retry = new ConnectionManagerRepeat(
+ new ConnectionManagerTimeout($connector, 2.0),
+ 3
+);
+
+$selective = new ConnectionManagerSelective(array(
+ '*:80' => $delayed,
+ 'unreliable.example.com' => $retry,
+ '*' => $connector
+));
+```
+
+Each entry in the list MUST be in the form `host` or `host:port`, where
+`host` may contain the `*` wildcard character and `port` may be given as
+either an exact port number or as a range in the form of `min-max`.
+Passing anything else will result in an `InvalidArgumentException`.
+
+> Note that the host will be matched exactly as-is otherwise. This means that
+ if you only block `youtube.com`, this has no effect on `www.youtube.com`.
+ You may want to add a second rule for `*.youtube.com` in this case.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org/).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+composer require clue/connection-manager-extra:^1.3
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
+HHVM.
+It's *highly recommended to use the latest supported PHP version* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org/):
+
+```bash
+composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+vendor/bin/phpunit
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
diff --git a/vendor/clue/connection-manager-extra/composer.json b/vendor/clue/connection-manager-extra/composer.json
new file mode 100644
index 0000000..01075a9
--- /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/event-loop": "^1.2",
+ "react/promise": "^3 || ^2.1 || ^1.2.1",
+ "react/promise-timer": "^1.9",
+ "react/socket": "^1.12"
+ },
+ "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..a8a85c5
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace ConnectionManager\Extra;
+
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise\Timer;
+use React\Socket\ConnectorInterface;
+
+class ConnectionManagerDelay implements ConnectorInterface
+{
+ /** @var ConnectorInterface */
+ private $connectionManager;
+
+ /** @var float */
+ private $delay;
+
+ /** @var LoopInterface */
+ private $loop;
+
+ /**
+ * @param ConnectorInterface $connectionManager
+ * @param float $delay
+ * @param ?LoopInterface $loop
+ */
+ public function __construct(ConnectorInterface $connectionManager, $delay, LoopInterface $loop = null)
+ {
+ $this->connectionManager = $connectionManager;
+ $this->delay = $delay;
+ $this->loop = $loop ?: Loop::get();
+ }
+
+ 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..5151610
--- /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\PromiseInterface;
+
+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 PromiseInterface && \method_exists($pending, 'cancel')) {
+ $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..5ec23a5
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/ConnectionManagerTimeout.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace ConnectionManager\Extra;
+
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise\Timer;
+use React\Socket\ConnectorInterface;
+
+class ConnectionManagerTimeout implements ConnectorInterface
+{
+ /** @var ConnectorInterface */
+ private $connectionManager;
+
+ /** @var float */
+ private $timeout;
+
+ /** @var LoopInterface */
+ private $loop;
+
+ /**
+ * @param ConnectorInterface $connectionManager
+ * @param float $timeout
+ * @param ?LoopInterface $loop
+ */
+ public function __construct(ConnectorInterface $connectionManager, $timeout, LoopInterface $loop = null)
+ {
+ $this->connectionManager = $connectionManager;
+ $this->timeout = $timeout;
+ $this->loop = $loop ?: Loop::get();
+ }
+
+ 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..b6175f8
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace ConnectionManager\Extra\Multiple;
+
+use React\Promise;
+use React\Promise\PromiseInterface;
+
+class ConnectionManagerConcurrent extends ConnectionManagerConsecutive
+{
+ public function connect($uri)
+ {
+ $all = array();
+ foreach ($this->managers as $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 PromiseInterface && \method_exists($promise, 'cancel')) {
+ $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..b3bfd4c
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConsecutive.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace ConnectionManager\Extra\Multiple;
+
+use React\Promise;
+use React\Promise\PromiseInterface;
+use React\Socket\ConnectorInterface;
+use UnderflowException;
+
+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 PromiseInterface && \method_exists($pending, 'cancel')) {
+ $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/CHANGELOG.md b/vendor/clue/http-proxy-react/CHANGELOG.md
new file mode 100644
index 0000000..19d534d
--- /dev/null
+++ b/vendor/clue/http-proxy-react/CHANGELOG.md
@@ -0,0 +1,200 @@
+# Changelog
+
+## 1.8.0 (2022-09-01)
+
+* Feature: Full support for PHP 8.1 and PHP 8.2.
+ (#47 and #48 by @SimonFrings)
+
+* Feature: Mark passwords and URIs as `#[\SensitiveParameter]` (PHP 8.2+).
+ (#49 by @SimonFrings)
+
+* Feature: Forward compatibility with upcoming Promise v3.
+ (#44 by @clue)
+
+* Fix: Fix invalid references in exception stack trace.
+ (#45 by @clue)
+
+* Improve test suite and fix legacy HHVM build.
+ (#46 by @SimonFrings)
+
+## 1.7.0 (2021-08-06)
+
+* Feature: Simplify usage by supporting new default loop and making `Connector` optional.
+ (#41 and #42 by @clue)
+
+ ```php
+ // old (still supported)
+ $proxy = new Clue\React\HttpProxy\ProxyConnector(
+ '127.0.0.1:8080',
+ new React\Socket\Connector($loop)
+ );
+
+ // new (using default loop)
+ $proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
+ ```
+
+* Documentation improvements and updated examples.
+ (#39 and #43 by @clue and #40 by @PaulRotmann)
+
+* Improve test suite and use GitHub actions for continuous integration (CI).
+ (#38 by @SimonFrings)
+
+## 1.6.0 (2020-10-23)
+
+* Enhanced documentation for ReactPHP's new HTTP client.
+ (#35 and #37 by @SimonFrings)
+
+* Improve test suite, prepare PHP 8 support and support PHPUnit 9.3.
+ (#36 by @SimonFrings)
+
+## 1.5.0 (2020-06-19)
+
+* Feature / Fix: Support PHP 7.4 by skipping unneeded cleanup of exception trace args.
+ (#33 by @clue)
+
+* Clean up test suite and add `.gitattributes` to exclude dev files from exports.
+ Run tests on PHP 7.4, PHPUnit 9 and simplify test matrix.
+ Link to using SSH proxy (SSH tunnel) as an alternative.
+ (#27 by @clue and #31, #32 and #34 by @SimonFrings)
+
+## 1.4.0 (2018-10-30)
+
+* Feature: Improve error reporting for failed connection attempts and improve
+ cancellation forwarding during proxy connection setup.
+ (#23 and #26 by @clue)
+
+ All error messages now always contain a reference to the remote URI to give
+ more details which connection actually failed and the reason for this error.
+ Similarly, any underlying connection issues to the proxy server will now be
+ reported as part of the previous exception.
+
+ For most common use cases this means that simply reporting the `Exception`
+ message should give the most relevant details for any connection issues:
+
+ ```php
+ $promise = $proxy->connect('tcp://example.com:80');
+ $promise->then(function (ConnectionInterface $connection) {
+ // …
+ }, function (Exception $e) {
+ echo $e->getMessage();
+ });
+ ```
+
+* Feature: Add support for custom HTTP request headers.
+ (#25 by @valga and @clue)
+
+ ```php
+ // new: now supports custom HTTP request headers
+ $proxy = new ProxyConnector('127.0.0.1:8080', $connector, array(
+ 'Proxy-Authorization' => 'Bearer abc123',
+ 'User-Agent' => 'ReactPHP'
+ ));
+ ```
+
+* Fix: Fix connecting to IPv6 destination hosts.
+ (#22 by @clue)
+
+* Link to clue/reactphp-buzz for HTTP requests and update project homepage.
+ (#21 and #24 by @clue)
+
+## 1.3.0 (2018-02-13)
+
+* Feature: Support communication over Unix domain sockets (UDS)
+ (#20 by @clue)
+
+ ```php
+ // new: now supports communication over Unix domain sockets (UDS)
+ $proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector);
+ ```
+
+* Reduce memory consumption by avoiding circular reference from stream reader
+ (#18 by @valga)
+
+* Improve documentation
+ (#19 by @clue)
+
+## 1.2.0 (2017-08-30)
+
+* Feature: Use socket error codes for connection rejections
+ (#17 by @clue)
+
+ ```php
+ $promise = $proxy->connect('imap.example.com:143');
+ $promise->then(null, function (Exeption $e) {
+ if ($e->getCode() === SOCKET_EACCES) {
+ echo 'Failed to authenticate with proxy!';
+ }
+ throw $e;
+ });
+ ```
+
+* Improve test suite by locking Travis distro so new defaults will not break the build and
+ optionally exclude tests that rely on working internet connection
+ (#15 and #16 by @clue)
+
+## 1.1.0 (2017-06-11)
+
+* Feature: Support proxy authentication if proxy URL contains username/password
+ (#14 by @clue)
+
+ ```php
+ // new: username/password will now be passed to HTTP proxy server
+ $proxy = new ProxyConnector('user:pass@127.0.0.1:8080', $connector);
+ ```
+
+## 1.0.0 (2017-06-10)
+
+* First stable release, now following SemVer
+
+> Contains no other changes, so it's actually fully compatible with the v0.3.2 release.
+
+## 0.3.2 (2017-06-10)
+
+* Fix: Fix rejecting invalid URIs and unexpected URI schemes
+ (#13 by @clue)
+
+* Fix HHVM build for now again and ignore future HHVM build errors
+ (#12 by @clue)
+
+* Documentation for Connector concepts (TCP/TLS, timeouts, DNS resolution)
+ (#11 by @clue)
+
+## 0.3.1 (2017-05-10)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
+ (#10 by @clue)
+
+## 0.3.0 (2017-04-10)
+
+* Feature / BC break: Replace deprecated SocketClient with new Socket component
+ (#9 by @clue)
+
+ This implies that the `ProxyConnector` from this package now implements the
+ `React\Socket\ConnectorInterface` instead of the legacy
+ `React\SocketClient\ConnectorInterface`.
+
+## 0.2.0 (2017-04-10)
+
+* Feature / BC break: Update SocketClient to v0.7 or v0.6 and
+ use `connect($uri)` instead of `create($host, $port)`
+ (#8 by @clue)
+
+ ```php
+ // old
+ $connector->create($host, $port)->then(function (Stream $conn) {
+ $conn->write("…");
+ });
+
+ // new
+ $connector->connect($uri)->then(function (ConnectionInterface $conn) {
+ $conn->write("…");
+ });
+ ```
+
+* Improve test suite by adding PHPUnit to require-dev
+ (#7 by @clue)
+
+
+## 0.1.0 (2016-11-01)
+
+* First tagged release
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/README.md b/vendor/clue/http-proxy-react/README.md
new file mode 100644
index 0000000..b5337c1
--- /dev/null
+++ b/vendor/clue/http-proxy-react/README.md
@@ -0,0 +1,510 @@
+# clue/reactphp-http-proxy
+
+[![CI status](https://github.com/clue/reactphp-http-proxy/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/reactphp-http-proxy/actions)
+[![installs on Packagist](https://img.shields.io/packagist/dt/clue/http-proxy-react?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/http-proxy-react)
+
+Async HTTP proxy connector, tunnel any TCP/IP-based protocol through an HTTP
+CONNECT proxy server, built on top of [ReactPHP](https://reactphp.org/).
+
+HTTP CONNECT proxy servers (also commonly known as "HTTPS proxy" or "SSL proxy")
+are commonly used to tunnel HTTPS traffic through an intermediary ("proxy"), to
+conceal the origin address (anonymity) or to circumvent address blocking
+(geoblocking). While many (public) HTTP CONNECT proxy servers often limit this
+to HTTPS port `443` only, this can technically be used to tunnel any
+TCP/IP-based protocol (HTTP, SMTP, IMAP etc.).
+This library provides a simple API to create these tunneled connections for you.
+Because it implements ReactPHP's standard
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
+it can simply be used in place of a normal connector.
+This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
+existing higher-level protocol implementation.
+
+* **Async execution of connections** -
+ Send any number of HTTP CONNECT requests in parallel and process their
+ responses as soon as results come in.
+ The Promise-based design provides a *sane* interface to working with out of
+ order responses and possible connection errors.
+* **Standard interfaces** -
+ Allows easy integration with existing higher-level components by implementing
+ ReactPHP's standard
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface).
+* **Lightweight, SOLID design** -
+ Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
+ and does not get in your way.
+ Builds on top of well-tested components and well-established concepts instead of reinventing the wheel.
+* **Good test coverage** -
+ Comes with an automated tests suite and is regularly tested against actual proxy servers in the wild.
+
+**Table of contents**
+
+* [Support us](#support-us)
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [ProxyConnector](#proxyconnector)
+ * [Plain TCP connections](#plain-tcp-connections)
+ * [Secure TLS connections](#secure-tls-connections)
+ * [HTTP requests](#http-requests)
+ * [Connection timeout](#connection-timeout)
+ * [DNS resolution](#dns-resolution)
+ * [Authentication](#authentication)
+ * [Advanced HTTP headers](#advanced-http-headers)
+ * [Advanced secure proxy connections](#advanced-secure-proxy-connections)
+ * [Advanced Unix domain sockets](#advanced-unix-domain-sockets)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Quickstart example
+
+The following example code demonstrates how this library can be used to send a
+secure HTTPS request to google.com through a local HTTP proxy server:
+
+```php
+<?php
+
+require __DIR__ . '/vendor/autoload.php';
+
+$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$browser = new React\Http\Browser($connector);
+
+$browser->get('https://google.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
+ var_dump($response->getHeaders(), (string) $response->getBody());
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### ProxyConnector
+
+The `ProxyConnector` is responsible for creating plain TCP/IP connections to
+any destination by using an intermediary HTTP CONNECT proxy.
+
+```
+[you] -> [proxy] -> [destination]
+```
+
+Its constructor simply accepts an HTTP proxy URL with the proxy server address:
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
+```
+
+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 (often the alternative HTTP port `8080`).
+
+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(array(
+ 'dns' => '127.0.0.1',
+ 'tcp' => array(
+ 'bindto' => '192.168.10.1:0'
+ ),
+ 'tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ )
+));
+
+$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080', $connector);
+```
+
+This is the main class in this package.
+Because it implements ReactPHP's standard
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
+it can simply be used in place of a normal connector.
+Accordingly, it provides only a single public method, the
+[`connect()`](https://github.com/reactphp/socket#connect) method.
+The `connect(string $uri): PromiseInterface<ConnectionInterface, Exception>`
+method can be used to establish a streaming connection.
+It returns a [Promise](https://github.com/reactphp/promise) which either
+fulfills with a [ConnectionInterface](https://github.com/reactphp/socket#connectioninterface)
+on success or rejects with an `Exception` on error.
+
+This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
+higher-level component:
+
+```diff
+- $acme = new AcmeApi($connector);
++ $proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080', $connector);
++ $acme = new AcmeApi($proxy);
+```
+
+#### Plain TCP connections
+
+HTTP CONNECT proxies are 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.
+As documented above, you can simply invoke its `connect()` method to establish
+a streaming plain TCP/IP connection and use any higher level protocol like so:
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
+
+$proxy->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface $connection) {
+ $connection->write("EHLO local\r\n");
+ $connection->on('data', function ($chunk) use ($connection) {
+ echo $chunk;
+ });
+});
+```
+
+You can either use the `ProxyConnector` directly or you may want to wrap this connector
+in ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector):
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$connector->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface $connection) {
+ $connection->write("EHLO local\r\n");
+ $connection->on('data', function ($chunk) use ($connection) {
+ echo $chunk;
+ });
+});
+```
+
+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.
+
+#### Secure TLS connections
+
+This class can also be used if you want to establish a secure TLS connection
+(formerly known as SSL) between you and your destination, such as when using
+secure HTTPS to your destination site. You can simply wrap this connector in
+ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector):
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$connector->connect('tls://smtp.googlemail.com:465')->then(function (React\Socket\ConnectionInterface $connection) {
+ $connection->write("EHLO local\r\n");
+ $connection->on('data', function ($chunk) use ($connection) {
+ echo $chunk;
+ });
+});
+```
+
+> Note how secure TLS connections are in fact entirely handled outside of
+ this HTTP CONNECT client implementation.
+
+#### HTTP requests
+
+This library also allows you to send HTTP requests through an HTTP CONNECT proxy server.
+
+In order to send HTTP requests, you first have to add a dependency for
+[ReactPHP's async HTTP client](https://github.com/reactphp/http#client-usage).
+This allows you to send both plain HTTP and TLS-encrypted HTTPS requests like this:
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$browser = new React\Http\Browser($connector);
+
+$browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
+ var_dump($response->getHeaders(), (string) $response->getBody());
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+See also [ReactPHP's HTTP client](https://github.com/reactphp/http#client-usage)
+and any of the [examples](examples) for more details.
+
+#### Connection timeout
+
+By default, the `ProxyConnector` does not implement any timeouts for establishing remote
+connections.
+Your underlying operating system may impose limits on pending and/or idle TCP/IP
+connections, anywhere in a range of a few minutes to several hours.
+
+Many use cases require more control over the timeout and likely values much
+smaller, usually in the range of a few seconds only.
+
+You can use ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector)
+to decorate any given `ConnectorInterface` instance.
+It provides the same `connect()` method, but will automatically reject the
+underlying connection attempt if it takes too long:
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false,
+ 'timeout' => 3.0
+));
+
+$connector->connect('tcp://google.com:80')->then(function ($connection) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+> Note how the connection timeout is in fact entirely handled outside of this
+ HTTP CONNECT client implementation.
+
+#### DNS resolution
+
+By default, the `ProxyConnector` does not perform any DNS resolution at all and simply
+forwards any hostname you're trying to connect to the remote proxy server.
+The remote proxy server is thus responsible for looking up any hostnames via DNS
+(this default mode is thus called *remote DNS resolution*).
+
+As an alternative, you can also send the destination IP to the remote proxy
+server.
+In this mode you either have to stick to using IPs only (which is ofen unfeasable)
+or perform any DNS lookups locally and only transmit the resolved destination IPs
+(this mode is thus called *local DNS resolution*).
+
+The default *remote DNS resolution* is useful if your local `ProxyConnector` either can
+not resolve target hostnames because it has no direct access to the internet or
+if it should not resolve target hostnames because its outgoing DNS traffic might
+be intercepted.
+
+As noted above, the `ProxyConnector` defaults to using remote DNS resolution.
+However, wrapping the `ProxyConnector` in ReactPHP's
+[`Connector`](https://github.com/reactphp/socket#connector) actually
+performs local DNS resolution unless explicitly defined otherwise.
+Given that remote DNS resolution is assumed to be the preferred mode, all
+other examples explicitly disable DNS resolution like this:
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+```
+
+If you want to explicitly use *local DNS resolution*, you can use the following code:
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
+
+// set up Connector which uses Google's public DNS (8.8.8.8)
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => '8.8.8.8'
+));
+```
+
+> Note how local DNS resolution is in fact entirely handled outside of this
+ HTTP CONNECT client implementation.
+
+#### Authentication
+
+If your HTTP proxy server requires authentication, you may pass the username and
+password as part of the HTTP proxy URL like this:
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('alice:password@127.0.0.1:8080');
+```
+
+Note that both the username and password must be percent-encoded if they contain
+special characters:
+
+```php
+$user = 'he:llo';
+$pass = 'p@ss';
+$url = rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1:8080';
+
+$proxy = new Clue\React\HttpProxy\ProxyConnector($url);
+```
+
+> The authentication details will be used for basic authentication and will be
+ transferred in the `Proxy-Authorization` HTTP request header for each
+ connection attempt.
+ If the authentication details are missing or not accepted by the remote HTTP
+ proxy server, it is expected to reject each connection attempt with a
+ `407` (Proxy Authentication Required) response status code and an exception
+ error code of `SOCKET_EACCES` (13).
+
+#### Advanced HTTP headers
+
+The `ProxyConnector` constructor accepts an optional array of custom request
+headers to send in the `CONNECT` request. This can be useful if you're using a
+custom proxy setup or authentication scheme if the proxy server does not support
+basic [authentication](#authentication) as documented above. This is rarely used
+in practice, but may be useful for some more advanced use cases. In this case,
+you may simply pass an assoc array of additional request headers like this:
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector(
+ '127.0.0.1:8080',
+ null,
+ array(
+ 'Proxy-Authorization' => 'Bearer abc123',
+ 'User-Agent' => 'ReactPHP'
+ )
+);
+```
+
+#### Advanced secure proxy connections
+
+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 the
+`https://` scheme (HTTPS default port 443) to create a secure connection to the proxy:
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('https://127.0.0.1:443');
+
+$proxy->connect('tcp://smtp.googlemail.com:587');
+```
+
+#### Advanced Unix domain sockets
+
+HTTP CONNECT proxy servers support forwarding TCP/IP based connections and
+higher level protocols.
+In some advanced cases, it may be useful to let your HTTP CONNECT proxy server
+listen on a Unix domain socket (UDS) path instead of a IP:port combination.
+For example, this allows you to rely on file system permissions instead of
+having to rely on explicit [authentication](#authentication).
+
+You can simply use the `http+unix://` URI scheme like this:
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('http+unix:///tmp/proxy.sock');
+
+$proxy->connect('tcp://google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ // connected…
+});
+```
+
+Similarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$proxy = new Clue\React\HttpProxy\ProxyConnector('http+unix://alice:password@/tmp/proxy.sock');
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
+ has limited support for this.
+ In particular, enabling [secure TLS](#secure-tls-connections) may not be
+ supported.
+
+> Note that the HTTP CONNECT protocol does not support the notion of UDS paths.
+ The above works reasonably well because UDS is only used for the connection between
+ client and proxy server and the path will not actually passed over the protocol.
+ This implies that this does not support connecting to UDS destination paths.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org/).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+composer require clue/http-proxy-react:^1.8
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
+HHVM.
+It's *highly recommended to use the latest supported PHP version* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org/):
+
+```bash
+composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+vendor/bin/phpunit
+```
+
+The test suite contains tests that rely on a working internet connection,
+alternatively you can also run it like this:
+
+```bash
+vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
+
+## More
+
+* If you want to learn more about how the
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ and its usual implementations look like, refer to the documentation of the underlying
+ [react/socket](https://github.com/reactphp/socket) component.
+* If you want to learn more about processing streams of data, refer to the
+ documentation of the underlying
+ [react/stream](https://github.com/reactphp/stream) component.
+* As an alternative to an HTTP CONNECT proxy, you may also want to look into
+ using a SOCKS (SOCKS4/SOCKS5) proxy instead.
+ You may want to use [clue/reactphp-socks](https://github.com/clue/reactphp-socks)
+ which also provides an implementation of the same
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ so that supporting either proxy protocol should be fairly trivial.
+* As an alternative to an HTTP CONNECT proxy, you may also want to look into
+ using an SSH proxy (SSH tunnel) instead.
+ You may want to use [clue/reactphp-ssh-proxy](https://github.com/clue/reactphp-ssh-proxy)
+ which also provides an implementation of the same
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ so that supporting either proxy protocol should be fairly trivial.
+* If you're dealing with public proxies, you'll likely have to work with mixed
+ quality and unreliable proxies. You may want to look into using
+ [clue/reactphp-connection-manager-extra](https://github.com/clue/reactphp-connection-manager-extra)
+ which allows retrying unreliable ones, implying connection timeouts,
+ concurrently working with multiple connectors and more.
+* If you're looking for an end-user HTTP CONNECT proxy server daemon, you may
+ want to use [LeProxy](https://leproxy.org/).
diff --git a/vendor/clue/http-proxy-react/composer.json b/vendor/clue/http-proxy-react/composer.json
new file mode 100644
index 0000000..4941b1a
--- /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": "^3 || ^2.1 || ^1.2.1",
+ "react/socket": "^1.12",
+ "ringcentral/psr7": "^1.2"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.5",
+ "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..165e8ba
--- /dev/null
+++ b/vendor/clue/http-proxy-react/src/ProxyConnector.php
@@ -0,0 +1,278 @@
+<?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(
+ #[\SensitiveParameter]
+ $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 $ti => $one) {
+ if (isset($one['args'])) {
+ foreach ($one['args'] as $ai => $arg) {
+ if ($arg instanceof \Closure) {
+ $trace[$ti]['args'][$ai] = 'Object(' . get_class($arg) . ')';
+ }
+ }
+ }
+ }
+ // @codeCoverageIgnoreEnd
+ $r->setValue($e, $trace);
+ });
+
+ return $deferred->promise();
+ }
+}
diff --git a/vendor/clue/mq-react/CHANGELOG.md b/vendor/clue/mq-react/CHANGELOG.md
new file mode 100644
index 0000000..8e491d4
--- /dev/null
+++ b/vendor/clue/mq-react/CHANGELOG.md
@@ -0,0 +1,96 @@
+# Changelog
+
+## 1.6.0 (2023-07-28)
+
+* Feature: Improve Promise v3 support and use template types.
+ (#41 and #42 by @clue)
+
+* Feature: Improve PHP 8.2+ support by refactoring queuing logic.
+ (#43 by @clue)
+
+* Improve test suite, ensure 100% code coverage and report failed assertions.
+ (#37 and #39 by @clue)
+
+## 1.5.0 (2022-09-30)
+
+* Feature: Forward compatibility with upcoming Promise v3.
+ (#33 by @clue)
+
+* Update to use new reactphp/async package instead of clue/reactphp-block.
+ (#34 by @SimonFrings)
+
+## 1.4.0 (2021-11-15)
+
+* Feature: Support PHP 8.1, avoid deprecation warning concerning `\Countable::count(...)` return type.
+ (#32 by @bartvanhoutte)
+
+* Improve documentation and simplify examples by updating to new [default loop](https://reactphp.org/event-loop/#loop).
+ (#27 and #29 by @PaulRotmann and #30 by @SimonFrings)
+
+* Improve test suite to use GitHub actions for continuous integration (CI).
+ (#28 by @SimonFrings)
+
+## 1.3.0 (2020-10-16)
+
+* Enhanced documentation for ReactPHP's new HTTP client and
+ add support / sponsorship info.
+ (#21 and #24 by @clue)
+
+* Improve test suite and add `.gitattributes` to exclude dev files from exports.
+ Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
+ (#22, #23 and #25 by @SimonFrings)
+
+## 1.2.0 (2019-12-05)
+
+* Feature: Add `any()` helper to await first successful fulfillment of operations.
+ (#18 by @clue)
+
+ ```php
+ // new: limit concurrency while awaiting any operation to complete
+ $promise = Queue::any(3, $urls, function ($url) use ($browser) {
+ return $browser->get($url);
+ });
+
+ $promise->then(function (ResponseInterface $response) {
+ echo 'First successful: ' . $response->getStatusCode() . PHP_EOL;
+ });
+ ```
+
+* Minor documentation improvements (fix syntax issues and typos) and update examples.
+ (#9 and #11 by @clue and #15 by @holtkamp)
+
+* Improve test suite to test against PHP 7.4 and PHP 7.3, drop legacy HHVM support,
+ update distro on Travis and update project homepage.
+ (#10 and #19 by @clue)
+
+## 1.1.0 (2018-04-30)
+
+* Feature: Add `all()` helper to await successful fulfillment of all operations
+ (#8 by @clue)
+
+ ```php
+ // new: limit concurrency while awaiting all operations to complete
+ $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;
+ });
+ ```
+
+* Fix: Implement cancellation forwarding for previously queued operations
+ (#7 by @clue)
+
+## 1.0.0 (2018-02-26)
+
+* First stable release, following SemVer
+
+ I'd like to thank [Bergfreunde GmbH](https://www.bergfreunde.de/), a German
+ online retailer for Outdoor Gear & Clothing, for sponsoring the first release! 🎉
+ Thanks to sponsors like this, who understand the importance of open source
+ development, I can justify spending time and focus on open source development
+ instead of traditional paid work.
+
+ > Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
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/README.md b/vendor/clue/mq-react/README.md
new file mode 100644
index 0000000..10c0999
--- /dev/null
+++ b/vendor/clue/mq-react/README.md
@@ -0,0 +1,532 @@
+# clue/reactphp-mq
+
+[![CI status](https://github.com/clue/reactphp-mq/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/reactphp-mq/actions)
+[![code coverage](https://img.shields.io/badge/code%20coverage-100%25-success)](#tests)
+[![installs on Packagist](https://img.shields.io/packagist/dt/clue/mq-react?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/mq-react)
+
+Mini Queue, the lightweight in-memory message queue to concurrently do many (but not too many) things at once,
+built on top of [ReactPHP](https://reactphp.org/).
+
+Let's say you crawl a page and find that you need to send 100 HTTP requests to
+following pages which each takes `0.2s`. You can either send them all
+sequentially (taking around `20s`) or you can use
+[ReactPHP](https://reactphp.org) to concurrently request all your pages at the
+same time. This works perfectly fine for a small number of operations, but
+sending an excessive number of requests can either take up all resources on your
+side or may get you banned by the remote side as it sees an unreasonable number
+of requests from your side.
+Instead, you can use this library to effectively rate limit your operations and
+queue excessives ones so that not too many operations are processed at once.
+This library provides a simple API that is easy to use in order to manage any
+kind of async operation without having to mess with most of the low-level details.
+You can use this to throttle multiple HTTP requests, database queries or pretty
+much any API that already uses Promises.
+
+* **Async execution of operations** -
+ Process any number of async operations and choose how many should be handled
+ concurrently and how many operations can be queued in-memory. Process their
+ results as soon as responses come in.
+ The Promise-based design provides a *sane* interface to working with out of order results.
+* **Lightweight, SOLID design** -
+ Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
+ and does not get in your way.
+ Builds on top of well-tested components and well-established concepts instead of reinventing the wheel.
+* **Good test coverage** -
+ Comes with an automated tests suite and is regularly tested in the *real world*.
+
+**Table of contents**
+
+* [Support us](#support-us)
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [Queue](#queue)
+ * [Promises](#promises)
+ * [Cancellation](#cancellation)
+ * [Timeout](#timeout)
+ * [all()](#all)
+ * [any()](#any)
+ * [Blocking](#blocking)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Quickstart example
+
+Once [installed](#install), you can use the following code to access an
+HTTP webserver and send a large number of HTTP GET requests:
+
+```php
+<?php
+
+require __DIR__ . '/vendor/autoload.php';
+
+$browser = new React\Http\Browser();
+
+// load a huge array of URLs to fetch
+$urls = file('urls.txt');
+
+// each job should use the browser to GET a certain URL
+// limit number of concurrent jobs here
+$q = new Clue\React\Mq\Queue(3, null, function ($url) use ($browser) {
+ return $browser->get($url);
+});
+
+foreach ($urls as $url) {
+ $q($url)->then(function (Psr\Http\Message\ResponseInterface $response) use ($url) {
+ echo $url . ': ' . $response->getBody()->getSize() . ' bytes' . PHP_EOL;
+ });
+}
+
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### Queue
+
+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.
+
+The `new Queue(int $concurrency, ?int $limit, callable(mixed):PromiseInterface<T> $handler)` call
+can be used to create a new queue instance.
+You can create any number of queues, for example when you want to apply
+different limits to different kinds 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
+// accepts any callable, so PHP's array notation is also supported
+$q = new Queue(10, null, array($browser, 'get'));
+```
+
+#### Promises
+
+This library works under the assumption that you want to concurrently handle
+async operations that use a [Promise](https://github.com/reactphp/promise)-based API.
+
+The demonstration purposes, the examples in this documentation use
+[ReactPHP's async HTTP client](https://github.com/reactphp/http#client-usage), but you
+may use any Promise-based API with this project. Its API can be used like this:
+
+```php
+$browser = new React\Http\Browser();
+
+$promise = $browser->get($url);
+```
+
+If you wrap this in a `Queue` instance as given above, this code will look
+like this:
+
+```php
+$browser = new React\Http\Browser();
+
+$q = new Queue(10, null, function ($url) use ($browser) {
+ return $browser->get($url);
+});
+
+$promise = $q($url);
+```
+
+The `$q` instance is invokable, so that invoking `$q(...$args)` will
+actually be forwarded as `$browser->get(...$args)` as given in the
+`$handler` argument when concurrency is still below limits.
+
+Each operation is expected to be async (non-blocking), so you may actually
+invoke multiple operations concurrently (send multiple requests in parallel).
+The `$handler` is responsible for responding to each request with a resolution
+value, the order is not guaranteed.
+These operations use a [Promise](https://github.com/reactphp/promise)-based
+interface that makes it easy to react to when an operation is completed (i.e.
+either successfully fulfilled or rejected with an error):
+
+```php
+$promise->then(
+ function ($result) {
+ var_dump('Result received', $result);
+ },
+ function (Exception $error) {
+ var_dump('There was an error', $error->getMessage());
+ }
+);
+```
+
+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.
+
+If this looks strange to you, you can also use the more traditional
+[blocking API](#blocking).
+
+#### Cancellation
+
+The returned Promise is implemented in such a way that it can be cancelled
+when it is still pending.
+Cancelling a pending operation will invoke its cancellation handler which is
+responsible for rejecting its value with an Exception and cleaning up any
+underlying resources.
+
+```php
+$promise = $q($url);
+
+Loop::addTimer(2.0, function () use ($promise) {
+ $promise->cancel();
+});
+```
+
+Similarly, cancelling an operation that is queued and has not yet been started
+will be rejected without ever starting the operation.
+
+#### Timeout
+
+By default, this library does not limit how long a single operation can take,
+so that the resulting promise may stay pending for a long time.
+Many use cases involve some kind of "timeout" logic so that an operation is
+cancelled after a certain threshold is reached.
+
+You can simply use [cancellation](#cancellation) as in the previous chapter or
+you may want to look into using [react/promise-timer](https://github.com/reactphp/promise-timer)
+which helps taking care of this through a simple API.
+
+The resulting code with timeouts applied look something like this:
+
+```php
+use React\Promise\Timer;
+
+$q = new Queue(10, null, function ($uri) use ($browser) {
+ return Timer\timeout($browser->get($uri), 2.0);
+});
+
+$promise = $q($uri);
+```
+
+The resulting promise can be consumed as usual and the above code will ensure
+that execution of this operation can not take longer than the given timeout
+(i.e. after it is actually started).
+In particular, note how this differs from applying a timeout to the resulting
+promise. The following code will ensure that the total time for queuing and
+executing this operation can not take longer than the given timeout:
+
+```php
+// usually not recommended
+$promise = Timer\timeout($q($url), 2.0);
+```
+
+Please refer to [react/promise-timer](https://github.com/reactphp/promise-timer)
+for more details.
+
+#### all()
+
+The static `all(int $concurrency, array<TKey,TIn> $jobs, callable(TIn):PromiseInterface<TOut> $handler): PromiseInterface<array<TKey,TOut>>` method can be used to
+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.
+
+#### any()
+
+The static `any(int $concurrency, array<TKey,TIn> $jobs, callable(TIn):Promise<TOut> $handler): PromiseInterface<TOut>` method can be used to
+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'));
+```
+
+#### Blocking
+
+As stated above, this library provides you a powerful, async API by default.
+
+You can also integrate this into your traditional, blocking environment by using
+[reactphp/async](https://github.com/reactphp/async). This allows you to simply
+await async HTTP requests like this:
+
+```php
+use function React\Async\await;
+
+$browser = new React\Http\Browser();
+
+$promise = Queue::all(3, $urls, function ($url) use ($browser) {
+ return $browser->get($url);
+});
+
+try {
+ $responses = await($promise);
+ // responses successfully received
+} catch (Exception $e) {
+ // an error occured while performing the requests
+}
+```
+
+Similarly, you can also wrap this in a function to provide a simple API and hide
+all the async details from the outside:
+
+```php
+use function React\Async\await;
+
+/**
+ * Concurrently downloads all the given URIs
+ *
+ * @param string[] $uris list of URIs to download
+ * @return ResponseInterface[] map with a response object for each URI
+ * @throws Exception if any of the URIs can not be downloaded
+ */
+function download(array $uris)
+{
+ $browser = new React\Http\Browser();
+
+ $promise = Queue::all(3, $uris, function ($uri) use ($browser) {
+ return $browser->get($uri);
+ });
+
+ return await($promise);
+}
+```
+
+This is made possible thanks to fibers available in PHP 8.1+ and our
+compatibility API that also works on all supported PHP versions.
+Please refer to [reactphp/async](https://github.com/reactphp/async#readme) for more details.
+
+> Keep in mind that returning an array of response messages means that the whole
+ response body has to be kept in memory.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org/).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+composer require clue/mq-react:^1.6
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 8+.
+It's *highly recommended to use the latest supported PHP version* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org/):
+
+```bash
+composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+vendor/bin/phpunit
+```
+
+The test suite is set up to always ensure 100% code coverage across all
+supported environments. If you have the Xdebug extension installed, you can also
+generate a code coverage report locally like this:
+
+```bash
+XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+I'd like to thank [Bergfreunde GmbH](https://www.bergfreunde.de/), a German
+online retailer for Outdoor Gear & Clothing, for sponsoring the first release! 🎉
+Thanks to sponsors like this, who understand the importance of open source
+development, I can justify spending time and focus on open source development
+instead of traditional paid work.
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
diff --git a/vendor/clue/mq-react/composer.json b/vendor/clue/mq-react/composer.json
new file mode 100644
index 0000000..8a83e34
--- /dev/null
+++ b/vendor/clue/mq-react/composer.json
@@ -0,0 +1,33 @@
+{
+ "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"
+ }
+ ],
+ "require": {
+ "php": ">=5.3",
+ "react/promise": "^3 || ^2.2.1 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/async": "^4 || ^3 || ^2",
+ "react/event-loop": "^1.2",
+ "react/http": "^1.8"
+ },
+ "autoload": {
+ "psr-4": {
+ "Clue\\React\\Mq\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Clue\\Tests\\React\\Mq\\": "tests/"
+ }
+ }
+}
diff --git a/vendor/clue/mq-react/src/Queue.php b/vendor/clue/mq-react/src/Queue.php
new file mode 100644
index 0000000..2287514
--- /dev/null
+++ b/vendor/clue/mq-react/src/Queue.php
@@ -0,0 +1,465 @@
+<?php
+
+namespace Clue\React\Mq;
+
+use React\Promise;
+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.
+ *
+ * @template T
+ */
+class Queue implements \Countable
+{
+ private $concurrency;
+ private $limit;
+ private $handler;
+
+ /** @var int<0,max> */
+ private $pending = 0;
+
+ /** @var array<int,\Closure():void> */
+ 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.
+ *
+ * @template TKey
+ * @template TIn
+ * @template TOut
+ * @param int $concurrency concurrency soft limit
+ * @param array<TKey,TIn> $jobs
+ * @param callable(TIn):PromiseInterface<TOut> $handler
+ * @return PromiseInterface<array<TKey,TOut>> Returns a Promise 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 PromiseInterface && \method_exists($promise, 'cancel')) {
+ $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 PromiseInterface && \method_exists($promise, 'cancel')) {
+ $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'));
+ * ```
+ *
+ * @template TKey
+ * @template TIn
+ * @template TOut
+ * @param int $concurrency concurrency soft limit
+ * @param array<TKey,TIn> $jobs
+ * @param callable(TIn):PromiseInterface<TOut> $handler
+ * @return PromiseInterface<TOut> Returns a Promise 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 PromiseInterface && \method_exists($promise, 'cancel')) {
+ $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 PromiseInterface && \method_exists($promise, 'cancel')) {
+ $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(mixed):PromiseInterface<T> $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 PromiseInterface<T>
+ */
+ 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);
+ assert(is_int($id));
+
+ /** @var ?PromiseInterface<T> $pending */
+ $pending = null;
+
+ $deferred = new Deferred(function ($_, $reject) use (&$queue, $id, &$pending) {
+ // forward cancellation to pending operation if it is currently executing
+ if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
+ $pending->cancel();
+ }
+ $pending = null;
+
+ if (isset($queue[$id])) {
+ // queued promise cancelled before its handler is invoked
+ // remove from queue and reject explicitly
+ unset($queue[$id]);
+ $reject(new \RuntimeException('Cancelled queued job before processing started'));
+ }
+ });
+
+ // queue job to process if number of pending jobs is below concurrency limit again
+ $handler = $this->handler; // PHP 5.4+
+ $args = func_get_args();
+ $that = $this; // PHP 5.4+
+ $queue[$id] = function () use ($handler, $args, $deferred, &$pending, $that) {
+ $pending = \call_user_func_array($handler, $args);
+
+ $that->await($pending)->then(
+ function ($result) use ($deferred, &$pending) {
+ $pending = null;
+ $deferred->resolve($result);
+ },
+ function ($e) use ($deferred, &$pending) {
+ $pending = null;
+ $deferred->reject($e);
+ }
+ );
+ };
+
+ return $deferred->promise();
+ }
+
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ return $this->pending + count($this->queue);
+ }
+
+ /**
+ * @internal
+ * @param PromiseInterface<T> $promise
+ */
+ public function await(PromiseInterface $promise)
+ {
+ $that = $this; // PHP 5.4+
+
+ 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;
+ }
+
+ $next = reset($this->queue);
+ assert($next instanceof \Closure);
+ 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;
+
+ // invoke handler and await its resolution before invoking next queued job
+ $next();
+ }
+}
diff --git a/vendor/clue/redis-protocol/.travis.yml b/vendor/clue/redis-protocol/.travis.yml
new file mode 100644
index 0000000..2af5cf5
--- /dev/null
+++ b/vendor/clue/redis-protocol/.travis.yml
@@ -0,0 +1,8 @@
+language: php
+php:
+ - 5.4
+ - 5.3
+before_script:
+ - composer install --dev --prefer-source --no-interaction
+script:
+ - phpunit --coverage-text
diff --git a/vendor/clue/redis-protocol/CHANGELOG.md b/vendor/clue/redis-protocol/CHANGELOG.md
new file mode 100644
index 0000000..d037017
--- /dev/null
+++ b/vendor/clue/redis-protocol/CHANGELOG.md
@@ -0,0 +1,50 @@
+# Changelog
+
+## 0.3.1 (2017-06-06)
+
+* Fix: Fix server-side parsing of legacy inline protocol when multiple requests are processed at once
+ (#12 by @kelunik and #13 by @clue)
+
+## 0.3.0 (2014-01-27)
+
+* Feature: Add dedicated and faster `RequestParser` that also support the old
+ inline request protocol.
+* Feature: Message serialization can now be handled directly by the Serializer
+ again without having to construct the appropriate model first.
+* BC break: The `Factory` now has two distinct methods to create parsers:
+ * `createResponseParser()` for a client-side library
+ * `createRequestParser()` for a server-side library / testing framework
+* BC break: Simplified parser API, now `pushIncoming()` returns an array of all
+ parsed message models.
+* BC break: The signature for getting a serialized message from a model was
+ changed and now requires a Serializer passed:
+ ```php
+ModelInterface::getMessageSerialized($serializer)
+```
+* Many, many performance improvements
+
+## 0.2.0 (2014-01-21)
+
+* Re-organize the whole API into dedicated
+ * `Parser` (protocol reader) and
+ * `Serializer` (protocol writer) sub-namespaces. (#4)
+
+* Use of the factory has now been unified:
+
+ ```php
+ $factory = new Clue\Redis\Protocol\Factory();
+ $parser = $factory->createParser();
+ $serializer = $factory->createSerializer();
+ ```
+
+* Add a dedicated `Model` for each type of reply. Among others, this now allows
+ you to distinguish a single line `StatusReply` from a binary-safe `BulkReply`. (#2)
+
+* Fix parsing binary values and do not trip over trailing/leading whitespace. (#4)
+
+* Improve parser and serializer performance by up to 20%. (#4)
+
+## 0.1.0 (2013-09-10)
+
+* First tagged release
+
diff --git a/vendor/clue/redis-protocol/README.md b/vendor/clue/redis-protocol/README.md
new file mode 100644
index 0000000..ea36a67
--- /dev/null
+++ b/vendor/clue/redis-protocol/README.md
@@ -0,0 +1,139 @@
+# clue/redis-protocol [![Build Status](https://travis-ci.org/clue/php-redis-protocol.png?branch=master)](https://travis-ci.org/clue/php-redis-protocol)
+
+A streaming redis protocol parser and serializer written in PHP
+
+This parser and serializer implementation allows you to parse redis protocol
+messages into native PHP values and vice-versa. This is usually needed by a
+redis client implementation which also handles the connection socket.
+
+To re-iterate: This is *not* a redis client implementation. This is a protocol
+implementation that is usually used by a redis client implementation. If you're
+looking for an easy way to build your own client implementation, then this is
+for you. If you merely want to connect to a redis server and issue some
+commands, you're probably better off using one of the existing client
+implementations.
+
+**Table of contents**
+
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [Factory](#factory)
+ * [Parser](#parser)
+ * [Model](#model)
+ * [Serializer](#serializer)
+* [Install](#install)
+* [License](#license)
+
+## Quickstart example
+
+```php
+use Clue\Redis\Protocol;
+
+$factory = new Protocol\Factory();
+$parser = $factory->createResponseParser();
+$serializer = $factory->createSerializer();
+
+$fp = fsockopen('tcp://localhost', 6379);
+fwrite($fp, $serializer->getRequestMessage('SET', array('name', 'value')));
+fwrite($fp, $serializer->getRequestMessage('GET', array('name')));
+
+// the commands are pipelined, so this may parse multiple responses
+$models = $parser->pushIncoming(fread($fp, 4096));
+
+$reply1 = array_shift($models);
+$reply2 = array_shift($models);
+
+var_dump($reply1->getValueNative()); // string(2) "OK"
+var_dump($reply2->getValueNative()); // string(5) "value"
+```
+
+## Usage
+
+### Factory
+
+The factory helps with instantiating the *right* parser and serializer.
+Eventually the *best* available implementation will be chosen depending on your
+installed extensions. You're also free to instantiate them directly, but this
+will lock you down on a given implementation (which could be okay depending on
+your use-case).
+
+### Parser
+
+The library includes a streaming redis protocol parser. As such, it can safely
+parse redis protocol messages and work with an incomplete data stream. For this,
+each included parser implements a single method
+`ParserInterface::pushIncoming($chunk)`.
+
+* The `ResponseParser` is what most redis client implementation would want to
+ use in order to parse incoming response messages from a redis server instance.
+* The `RequestParser` can be used to test messages coming from a redis client or
+ even to implement a redis server.
+* The `MessageBuffer` decorates either of the available parsers and merely
+ offers some helper methods in order to work with single messages:
+ * `hasIncomingModel()` to check if there's a complete message in the pipeline
+ * `popIncomingModel()` to extract a complete message from the incoming queue.
+
+### Model
+
+Each message (response as well as request) is represented by a model
+implementing the `ModelInterface` that has two methods:
+
+* `getValueNative()` returns the wrapped value.
+* `getMessageSerialized($serializer)` returns the serialized protocol messages
+ that will be sent over the wire.
+
+These models are very lightweight and add little overhead. They help keeping the
+code organized and also provide a means to distinguish a single line
+`StatusReply` from a binary-safe `BulkReply`.
+
+The parser always returns models. Models can also be instantiated directly:
+
+```php
+$model = new Model\IntegerReply(123);
+var_dump($model->getValueNative()); // int(123)
+var_dump($model->getMessageSerialized($serializer)); // string(6) ":123\r\n"
+```
+
+### Serializer
+
+The serializer is responsible for creating serialized messages and the
+corresponing message models to be sent across the wire.
+
+```php
+$message = $serializer->getRequestMessage('ping');
+var_dump($message); // string(14) "$1\r\n*4\r\nping\r\n"
+
+$message = $serializer->getRequestMessage('set', array('key', 'value'));
+var_dump($message); // string(33) "$3\r\n*3\r\nset\r\n*3\r\nkey\r\n*5\r\nvalue\r\n"
+
+$model = $serializer->createRequestModel('get', array('key'));
+var_dump($model->getCommand()); // string(3) "get"
+var_dump($model->getArgs()); // array(1) { string(3) "key" }
+var_dump($model->getValueNative()); // array(2) { string(3) "GET", string(3) "key" }
+
+$model = $serializer->createReplyModel(array('mixed', 12, array('value')));
+assert($model implement Model\MultiBulkReply);
+```
+
+## Install
+
+It's very unlikely you'll want to use this protocol parser standalone.
+It should be added as a dependency to your redis client implementation instead.
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require clue/redis-protocol:^0.3.1
+```
+
+More details and upgrade guides can be found in the [CHANGELOG](CHANGELOG.md).
+
+## License
+
+Its parser and serializer originally used to be based on
+[jpd/redisent](https://github.com/jdp/redisent), which is released under the ISC
+license, copyright (c) 2009-2012 Justin Poliey <justin@getglue.com>.
+
+Other than that, this library is MIT licensed.
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/example/client.php b/vendor/clue/redis-protocol/example/client.php
new file mode 100644
index 0000000..d1b4428
--- /dev/null
+++ b/vendor/clue/redis-protocol/example/client.php
@@ -0,0 +1,22 @@
+<?php
+
+require __DIR__ . '/../vendor/autoload.php';
+
+use Clue\Redis\Protocol;
+
+$factory = new Protocol\Factory();
+$parser = $factory->createResponseParser();
+$serializer = $factory->createSerializer();
+
+$fp = fsockopen('tcp://localhost', 6379);
+fwrite($fp, $serializer->getRequestMessage('SET', array('name', 'value')));
+fwrite($fp, $serializer->getRequestMessage('GET', array('name')));
+
+// the commands are pipelined, so this may parse multiple responses
+$models = $parser->pushIncoming(fread($fp, 4096));
+
+$reply1 = array_shift($models);
+$reply2 = array_shift($models);
+
+var_dump($reply1->getValueNative()); // string(2) "OK"
+var_dump($reply2->getValueNative()); // string(5) "value"
diff --git a/vendor/clue/redis-protocol/example/perf.php b/vendor/clue/redis-protocol/example/perf.php
new file mode 100644
index 0000000..c5ce6a7
--- /dev/null
+++ b/vendor/clue/redis-protocol/example/perf.php
@@ -0,0 +1,31 @@
+<?php
+
+use Clue\Redis\Protocol\ProtocolBuffer;
+use Clue\Redis\Protocol\Factory;
+
+require __DIR__ . '/../vendor/autoload.php';
+
+$factory = new Factory();
+$parser = $factory->createResponseParser();
+$serializer = $factory->createSerializer();
+
+$n = isset($argv[1]) ? (int)$argv[1] : 10000; // number of dummy messages to parse
+$cs = 4096; // pretend we can only read 7 bytes at once. more like 4096/8192 usually
+
+echo 'benchmarking ' . $n . ' messages (chunksize of ' . $cs .' bytes)' . PHP_EOL;
+
+$time = microtime(true);
+
+$stream = '';
+for ($i = 0; $i < $n; ++$i) {
+ $stream .= $serializer->getRequestMessage('set', array('var' . $i, 'value' . $i));
+}
+
+echo round(microtime(true) - $time, 3) . 's for serialization' . PHP_EOL;
+$time = microtime(true);
+
+for ($i = 0, $l = strlen($stream); $i < $l; $i += $cs) {
+ $parser->pushIncoming(substr($stream, $i, $cs));
+}
+
+echo round(microtime(true) - $time, 3) . 's for parsing' . PHP_EOL;
diff --git a/vendor/clue/redis-protocol/phpunit.xml.dist b/vendor/clue/redis-protocol/phpunit.xml.dist
new file mode 100644
index 0000000..4f3de2a
--- /dev/null
+++ b/vendor/clue/redis-protocol/phpunit.xml.dist
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit bootstrap="tests/bootstrap.php"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+>
+ <testsuites>
+ <testsuite name="Redis Protocol Test Suite">
+ <directory>./tests/</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist>
+ <directory>./src/</directory>
+ </whitelist>
+ </filter>
+</phpunit> \ No newline at end of file
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-protocol/tests/FactoryTest.php b/vendor/clue/redis-protocol/tests/FactoryTest.php
new file mode 100644
index 0000000..b669223
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/FactoryTest.php
@@ -0,0 +1,34 @@
+<?php
+
+use Clue\Redis\Protocol\Factory;
+
+class FactoryTest extends TestCase
+{
+ private $factory;
+
+ public function setUp()
+ {
+ $this->factory = new Factory();
+ }
+
+ public function testCreateResponseParser()
+ {
+ $parser = $this->factory->createResponseParser();
+
+ $this->assertInstanceOf('Clue\Redis\Protocol\Parser\ParserInterface', $parser);
+ }
+
+ public function testCreateRequestParser()
+ {
+ $parser = $this->factory->createRequestParser();
+
+ $this->assertInstanceOf('Clue\Redis\Protocol\Parser\ParserInterface', $parser);
+ }
+
+ public function testCreateSerializer()
+ {
+ $serializer = $this->factory->createSerializer();
+
+ $this->assertInstanceOf('Clue\Redis\Protocol\Serializer\SerializerInterface', $serializer);
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/Model/AbstractModelTest.php b/vendor/clue/redis-protocol/tests/Model/AbstractModelTest.php
new file mode 100644
index 0000000..1358533
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Model/AbstractModelTest.php
@@ -0,0 +1,22 @@
+<?php
+
+use Clue\Redis\Protocol\Serializer\RecursiveSerializer;
+
+abstract class AbstractModelTest extends TestCase
+{
+ protected $serializer;
+
+ abstract protected function createModel($value);
+
+ public function setUp()
+ {
+ $this->serializer = new RecursiveSerializer();
+ }
+
+ public function testConstructor()
+ {
+ $model = $this->createModel(null);
+
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\ModelInterface', $model);
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/Model/BulkReplyTest.php b/vendor/clue/redis-protocol/tests/Model/BulkReplyTest.php
new file mode 100644
index 0000000..78ed04c
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Model/BulkReplyTest.php
@@ -0,0 +1,43 @@
+<?php
+
+use Clue\Redis\Protocol\Model\BulkReply;
+
+class BulkReplyTest extends AbstractModelTest
+{
+ protected function createModel($value)
+ {
+ return new BulkReply($value);
+ }
+
+ public function testStringReply()
+ {
+ $model = $this->createModel('test');
+
+ $this->assertEquals('test', $model->getValueNative());
+ $this->assertEquals("$4\r\ntest\r\n", $model->getMessageSerialized($this->serializer));
+ }
+
+ public function testEmptyStringReply()
+ {
+ $model = $this->createModel('');
+
+ $this->assertEquals('', $model->getValueNative());
+ $this->assertEquals("$0\r\n\r\n", $model->getMessageSerialized($this->serializer));
+ }
+
+ public function testIntegerCast()
+ {
+ $model = $this->createModel(123);
+
+ $this->assertEquals('123', $model->getValueNative());
+ $this->assertEquals("$3\r\n123\r\n", $model->getMessageSerialized($this->serializer));
+ }
+
+ public function testNullBulkReply()
+ {
+ $model = $this->createModel(null);
+
+ $this->assertEquals(null, $model->getValueNative());
+ $this->assertEquals("$-1\r\n", $model->getMessageSerialized($this->serializer));
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/Model/ErrorReplyTest.php b/vendor/clue/redis-protocol/tests/Model/ErrorReplyTest.php
new file mode 100644
index 0000000..2585b08
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Model/ErrorReplyTest.php
@@ -0,0 +1,19 @@
+<?php
+
+use Clue\Redis\Protocol\Model\ErrorReply;
+
+class ErrorReplyTest extends AbstractModelTest
+{
+ protected function createModel($value)
+ {
+ return new ErrorReply($value);
+ }
+
+ public function testError()
+ {
+ $model = $this->createModel('ERR error');
+
+ $this->assertEquals('ERR error', $model->getValueNative());
+ $this->assertEquals("-ERR error\r\n", $model->getMessageSerialized($this->serializer));
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/Model/IntegerReplyTest.php b/vendor/clue/redis-protocol/tests/Model/IntegerReplyTest.php
new file mode 100644
index 0000000..f6a2e10
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Model/IntegerReplyTest.php
@@ -0,0 +1,40 @@
+<?php
+
+use Clue\Redis\Protocol\Model\IntegerReply;
+
+class IntegerReplyTest extends AbstractModelTest
+{
+ protected function createModel($value)
+ {
+ return new IntegerReply($value);
+ }
+
+ public function testIntegerReply()
+ {
+ $model = $this->createModel(0);
+ $this->assertEquals(0, $model->getValueNative());
+ $this->assertEquals(":0\r\n", $model->getMessageSerialized($this->serializer));
+ }
+
+ public function testFloatCasted()
+ {
+ $model = $this->createModel(-12.99);
+ $this->assertEquals(-12, $model->getValueNative());
+ $this->assertEquals(":-12\r\n", $model->getMessageSerialized($this->serializer));
+
+ $model = $this->createModel(14.99);
+ $this->assertEquals(14, $model->getValueNative());
+ $this->assertEquals(":14\r\n", $model->getMessageSerialized($this->serializer));
+ }
+
+ public function testBooleanCasted()
+ {
+ $model = $this->createModel(true);
+ $this->assertEquals(1, $model->getValueNative());
+ $this->assertEquals(":1\r\n", $model->getMessageSerialized($this->serializer));
+
+ $model = $this->createModel(false);
+ $this->assertEquals(0, $model->getValueNative());
+ $this->assertEquals(":0\r\n", $model->getMessageSerialized($this->serializer));
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/Model/MultiBulkReplyTest.php b/vendor/clue/redis-protocol/tests/Model/MultiBulkReplyTest.php
new file mode 100644
index 0000000..04dd389
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Model/MultiBulkReplyTest.php
@@ -0,0 +1,115 @@
+<?php
+
+use Clue\Redis\Protocol\Model\MultiBulkReply;
+use Clue\Redis\Protocol\Model\BulkReply;
+use Clue\Redis\Protocol\Model\IntegerReply;
+
+class MultiBulkReplyTest extends AbstractModelTest
+{
+ protected function createModel($value)
+ {
+ return new MultiBulkReply($value);
+ }
+
+ public function testEmptyArray()
+ {
+ $model = $this->createModel(array());
+
+ $this->assertEquals(array(), $model->getValueNative());
+ $this->assertEquals("*0\r\n", $model->getMessageSerialized($this->serializer));
+
+ $this->assertFalse($model->isRequest());
+ }
+
+ public function testNullMultiBulkReply()
+ {
+ $model = $this->createModel(null);
+
+ $this->assertEquals(null, $model->getValueNative());
+ $this->assertEquals("*-1\r\n", $model->getMessageSerialized($this->serializer));
+
+ $this->assertFalse($model->isRequest());
+
+ return $model;
+ }
+
+ /**
+ * @param MultiBulkReply $model
+ * @depends testNullMultiBulkReply
+ * @expectedException UnexpectedValueException
+ */
+ public function testNullMultiBulkReplyIsNotARequest(MultiBulkReply $model)
+ {
+ $model->getRequestModel();
+ }
+
+ public function testSingleBulkEnclosed()
+ {
+ $model = $this->createModel(array(new BulkReply('test')));
+
+ $this->assertEquals(array('test'), $model->getValueNative());
+ $this->assertEquals("*1\r\n$4\r\ntest\r\n", $model->getMessageSerialized($this->serializer));
+
+ $this->assertTrue($model->isRequest());
+
+ // this can be represented by a request
+ $request = $model->getRequestModel();
+ $this->assertEquals($model->getValueNative(), $request->getValueNative());
+
+ // representing the request as a reply should return our original instance
+ $reply = $request->getReplyModel();
+ $this->assertEquals($model, $reply);
+
+ return $model;
+ }
+
+ /**
+ * @depends testSingleBulkEnclosed
+ */
+ public function testStringEnclosedEqualsSingleBulk(MultiBulkReply $expected)
+ {
+ $model = $this->createModel(array('test'));
+
+ $this->assertEquals($expected->getValueNative(), $model->getValueNative());
+ $this->assertEquals($expected->getMessageSerialized($this->serializer), $model->getMessageSerialized($this->serializer));
+
+ $this->assertTrue($model->isRequest());
+ }
+
+ public function testMixedReply()
+ {
+ $model = $this->createModel(array(new BulkReply('test'), new IntegerReply(123)));
+
+ $this->assertEquals(array('test', 123), $model->getValueNative());
+ $this->assertEquals("*2\r\n$4\r\ntest\r\n:123\r\n", $model->getMessageSerialized($this->serializer));
+
+ $this->assertFalse($model->isRequest());
+
+ return $model;
+ }
+
+ /**
+ * @param MultiBulkReply $model
+ * @depends testMixedReply
+ * @expectedException UnexpectedValueException
+ */
+ public function testMixedReplyIsNotARequest(MultiBulkReply $model)
+ {
+ $model->getRequestModel();
+ }
+
+ public function testMultiStrings()
+ {
+ $model = $this->createModel(array('SET', 'a', 'b'));
+
+ $this->assertEquals(array('SET', 'a', 'b'), $model->getValueNative());
+
+ $this->assertTrue($model->isRequest());
+
+ $request = $model->getRequestModel();
+
+ // this can be represented by a request
+ $request = $model->getRequestModel();
+ $this->assertEquals($model->getValueNative(), $request->getValueNative());
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/Model/RequestTest.php b/vendor/clue/redis-protocol/tests/Model/RequestTest.php
new file mode 100644
index 0000000..6719481
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Model/RequestTest.php
@@ -0,0 +1,37 @@
+<?php
+
+use Clue\Redis\Protocol\Model\Request;
+
+class RequestTest extends AbstractModelTest
+{
+ protected function createModel($value)
+ {
+ return new Request('QUIT');
+ }
+
+ public function testPing()
+ {
+ $model = new Request('PING');
+
+ $this->assertEquals('PING', $model->getCommand());
+ $this->assertEquals(array(), $model->getArgs());
+ $this->assertEquals(array('PING'), $model->getValueNative());
+ $this->assertEquals("*1\r\n$4\r\nPING\r\n", $model->getMessageSerialized($this->serializer));
+
+ $reply = $model->getReplyModel();
+ $this->assertEquals($model->getValueNative(), $reply->getValueNative());
+ }
+
+ public function testGet()
+ {
+ $model = new Request('GET', array('a'));
+
+ $this->assertEquals('GET', $model->getCommand());
+ $this->assertEquals(array('a'), $model->getArgs());
+ $this->assertEquals(array('GET', 'a'), $model->getValueNative());
+ $this->assertEquals("*2\r\n$3\r\nGET\r\n$1\r\na\r\n", $model->getMessageSerialized($this->serializer));
+
+ $reply = $model->getReplyModel();
+ $this->assertEquals($model->getValueNative(), $reply->getValueNative());
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/Model/StatusReplyTest.php b/vendor/clue/redis-protocol/tests/Model/StatusReplyTest.php
new file mode 100644
index 0000000..8debd85
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Model/StatusReplyTest.php
@@ -0,0 +1,19 @@
+<?php
+
+use Clue\Redis\Protocol\Model\StatusReply;
+
+class StatusReplyTest extends AbstractModelTest
+{
+ protected function createModel($value)
+ {
+ return new StatusReply($value);
+ }
+
+ public function testStatusOk()
+ {
+ $model = $this->createModel('OK');
+
+ $this->assertEquals('OK', $model->getValueNative());
+ $this->assertEquals("+OK\r\n", $model->getMessageSerialized($this->serializer));
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/Parser/AbstractParserTest.php b/vendor/clue/redis-protocol/tests/Parser/AbstractParserTest.php
new file mode 100644
index 0000000..3d45c20
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Parser/AbstractParserTest.php
@@ -0,0 +1,67 @@
+<?php
+
+use Clue\Redis\Protocol\Parser\ParserInterface;
+use Clue\Redis\Protocol\Parser\MessageBuffer;
+
+abstract class AbstractParserTest extends TestCase
+{
+ /**
+ *
+ * @var ParserInterface
+ */
+ protected $parser;
+
+ abstract protected function createParser();
+
+ public function setUp()
+ {
+ $this->parser = $this->createParser();
+ $this->assertInstanceOf('Clue\Redis\Protocol\Parser\ParserInterface', $this->parser);
+ }
+
+ public function testParsingMessageOne()
+ {
+ // getRequestMessage('test')
+ $message = $expected = "*1\r\n$4\r\ntest\r\n";
+
+ $models = $this->parser->pushIncoming($message);
+ $this->assertCount(1, $models);
+
+ $model = reset($models);
+ $this->assertEquals(array('test'), $model->getValueNative());
+ }
+
+ public function testParsingMessageTwoPartial()
+ {
+ // getRequestMessage('test', array('second'))
+ $message = "*2\r\n$4\r\ntest\r\n$6\r\nsecond\r\n";
+
+ $this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 0, 1)));
+ $this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 1, 1)));
+ $this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 2, 1)));
+ $this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 3, 10)));
+ $this->assertCount(1, $models = $this->parser->pushIncoming(substr($message, 13)));
+
+ $model = reset($models);
+
+ $this->assertEquals(array('test', 'second'), $model->getValueNative());
+ }
+
+ public function testMessageBuffer()
+ {
+ $buffer = new MessageBuffer($this->parser);
+
+ $this->assertFalse($buffer->hasIncomingModel());
+
+ $data = "*1\r\n$4\r\ntest\r\n";
+ $this->assertCount(1, $models = $buffer->pushIncoming($data));
+ $this->assertTrue($buffer->hasIncomingModel());
+
+ $expected = reset($models);
+ $this->assertSame($expected, $buffer->popIncomingModel());
+ $this->assertFalse($buffer->hasIncomingModel());
+
+ $this->setExpectedException('UnderflowException');
+ $buffer->popIncomingModel();
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/Parser/RequestParserTest.php b/vendor/clue/redis-protocol/tests/Parser/RequestParserTest.php
new file mode 100644
index 0000000..645b673
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Parser/RequestParserTest.php
@@ -0,0 +1,132 @@
+<?php
+
+use Clue\Redis\Protocol\Parser\RequestParser;
+
+class RequestParserTest extends AbstractParserTest
+{
+ protected function createParser()
+ {
+ return new RequestParser();
+ }
+
+ public function testSimplePingRequest()
+ {
+ $message = "*1\r\n$4\r\nping\r\n";
+
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $request = reset($models);
+
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\Request', $request);
+ $this->assertEquals('ping', $request->getCommand());
+ $this->assertEquals(array(), $request->getArgs());
+
+ return $request;
+ }
+
+ /**
+ *
+ * @param Request $expected
+ * @depends testSimplePingRequest
+ */
+ public function testInlinePingRequest($expected)
+ {
+ $message = "ping\r\n";
+
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $request = reset($models);
+
+ $this->assertEquals($expected, $request);
+ }
+
+ public function testInlineWhitespaceIsIgnored()
+ {
+ $message = " set name value \r\n";
+
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $request = reset($models);
+
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\Request', $request);
+ $this->assertEquals('set', $request->getCommand());
+ $this->assertEquals(array('name', 'value'), $request->getArgs());
+ }
+
+ public function testIncompleteSuccessive()
+ {
+ $this->assertEquals(array(), $this->parser->pushIncoming("*1\r\n"));
+ $this->assertEquals(array(), $this->parser->pushIncoming("$4\r\n"));
+ $this->assertEquals(array(), $this->parser->pushIncoming("test"));
+ $this->assertCount(1, $models = $this->parser->pushIncoming("\r\n"));
+ }
+
+ public function testNullMultiBulkRequestIsIgnored()
+ {
+ $message = "*-1\r\n";
+
+ $this->assertEquals(array(), $this->parser->pushIncoming($message));
+ }
+
+ public function testEmptyMultiBulkRequestIsIgnored()
+ {
+ $message = "*0\r\n";
+
+ $this->assertEquals(array(), $this->parser->pushIncoming($message));
+ }
+
+ public function testEmptyInlineIsIgnored()
+ {
+ $message = "\r\n";
+
+ $this->assertEquals(array(), $this->parser->pushIncoming($message));
+ }
+
+ public function testInlineParsesMultipleRequestsAtOnce()
+ {
+ $message = "hello\r\n\world\r\ntest\r\n";
+
+ $this->assertCount(3, $models = $this->parser->pushIncoming($message));
+ }
+
+
+ public function testEmptyInlineAroundInlineIsIgnored()
+ {
+ $message = "\r\n\r\n" . "ping\r\n\r\n";
+
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $request = reset($models);
+
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\Request', $request);
+ $this->assertEquals('ping', $request->getCommand());
+ $this->assertEquals(array(), $request->getArgs());
+ }
+
+ public function testWhitespaceInlineIsIgnored()
+ {
+ $message = " \r\n";
+
+ $this->assertEquals(array(), $this->parser->pushIncoming($message));
+ }
+
+ /**
+ * @expectedException Clue\Redis\Protocol\Parser\ParserException
+ */
+ public function testInvalidMultiBulkMustContainBulk()
+ {
+ $message = "*1\r\n:123\r\n";
+
+ $this->parser->pushIncoming($message);
+ }
+
+ /**
+ * @expectedException Clue\Redis\Protocol\Parser\ParserException
+ */
+ public function testInvalidBulkLength()
+ {
+ $message = "*1\r\n$-1\r\n";
+
+ $this->parser->pushIncoming($message);
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/Parser/ResponseParserTest.php b/vendor/clue/redis-protocol/tests/Parser/ResponseParserTest.php
new file mode 100644
index 0000000..762c8bd
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Parser/ResponseParserTest.php
@@ -0,0 +1,130 @@
+<?php
+
+use Clue\Redis\Protocol\Parser\ResponseParser;
+
+class RecursiveParserTest extends AbstractParserTest
+{
+ protected function createParser()
+ {
+ return new ResponseParser();
+ }
+
+ public function testPartialIncompleteBulkReply()
+ {
+ $this->assertEquals(array(), $this->parser->pushIncoming("$20\r\nincompl"));
+ }
+
+ public function testParsingStatusReplies()
+ {
+ // C: PING
+ $message = "+PONG\r\n";
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $data = reset($models)->getValueNative();
+ $this->assertEquals('PONG', $data);
+
+ // C: SET key value
+ $message = "+OK\r\n";
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $data = reset($models)->getValueNative();
+ $this->assertEquals('OK', $data);
+ }
+
+ public function testParsingErrorReply()
+ {
+ $message = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
+
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+ $exception = reset($models);
+
+ $this->assertInstanceOf('Exception', $exception);
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\ErrorReply', $exception);
+ $this->assertEquals('WRONGTYPE Operation against a key holding the wrong kind of value', $exception->getMessage());
+ }
+
+ public function testParsingIntegerReply()
+ {
+ // C: INCR mykey
+ $message = ":1\r\n";
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $data = reset($models)->getValueNative();
+ $this->assertEquals(1, $data);
+ }
+
+ public function testParsingBulkReply()
+ {
+ // C: GET mykey
+ $message = "$6\r\nfoobar\r\n";
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $data = reset($models)->getValueNative();
+ $this->assertEquals("foobar", $data);
+ }
+
+ public function testParsingNullBulkReply()
+ {
+ // C: GET nonexistingkey
+ $message = "$-1\r\n";
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $data = reset($models)->getValueNative();
+ $this->assertEquals(null, $data);
+ }
+
+ public function testParsingEmptyMultiBulkReply()
+ {
+ // C: LRANGE nokey 0 1
+ $message = "*0\r\n";
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $data = reset($models)->getValueNative();
+ $this->assertEquals(array(), $data);
+ }
+
+ public function testParsingNullMultiBulkReply()
+ {
+ // C: BLPOP key 1
+ $message = "*-1\r\n";
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $data = reset($models)->getValueNative();
+ $this->assertEquals(null, $data);
+ }
+
+ public function testParsingMultiBulkReplyWithMixedElements()
+ {
+ $message = "*5\r\n:1\r\n:2\r\n:3\r\n:4\r\n$6\r\nfoobar\r\n";
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $data = reset($models)->getValueNative();
+ $this->assertEquals(array(1, 2, 3, 4, 'foobar'), $data);
+ }
+
+ public function testParsingMultiBulkReplyWithIncompletePush()
+ {
+ $this->assertCount(0, $this->parser->pushIncoming("*5\r\n:1\r\n:2\r"));
+ $this->assertCount(1, $models = $this->parser->pushIncoming("\n:3\r\n:4\r\n$6\r\nfoobar\r\n"));
+
+ $data = reset($models)->getValueNative();
+ $this->assertEquals(array(1, 2, 3, 4, 'foobar'), $data);
+ }
+
+ public function testParsingMultiBulkReplyWithNullElement()
+ {
+ $message = "*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n";
+ $this->assertCount(1, $models = $this->parser->pushIncoming($message));
+
+ $data = reset($models)->getValueNative();
+ $this->assertEquals(array('foo', null, 'bar'), $data);
+ }
+
+ /**
+ * @expectedException Clue\Redis\Protocol\Parser\ParserException
+ */
+ public function testParseError()
+ {
+ $this->parser->pushIncoming("invalid string\r\n");
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/Serializer/AbstractSerializerTest.php b/vendor/clue/redis-protocol/tests/Serializer/AbstractSerializerTest.php
new file mode 100644
index 0000000..ba2200a
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Serializer/AbstractSerializerTest.php
@@ -0,0 +1,141 @@
+<?php
+
+use Clue\Redis\Protocol\Serializer\SerializerInterface;
+use Clue\Redis\Protocol\Model\Status;
+use Clue\Redis\Protocol\Model\ErrorReplyException;
+//use Exception;
+
+abstract class AbstractSerializerTest extends TestCase
+{
+ /**
+ * @return SerializerInterface
+ */
+ abstract protected function createSerializer();
+
+ public function setUp()
+ {
+ $this->serializer = $this->createSerializer();
+ }
+
+ public function testIntegerReply()
+ {
+ $model = $this->serializer->createReplyModel(0);
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model);
+ $this->assertEquals(0, $model->getValueNative());
+ $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(0));
+ }
+
+ public function testFloatCastIntegerReply()
+ {
+ $model = $this->serializer->createReplyModel(-12.99);
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model);
+ $this->assertEquals(-12, $model->getValueNative());
+ $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(-12.99));
+
+ $model = $this->serializer->createReplyModel(14.99);
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model);
+ $this->assertEquals(14, $model->getValueNative());
+ $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(14.99));
+ }
+
+ public function testBooleanCastIntegerReply()
+ {
+ $model = $this->serializer->createReplyModel(true);
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model);
+ $this->assertEquals(1, $model->getValueNative());
+ $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(true));
+
+ $model = $this->serializer->createReplyModel(false);
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model);
+ $this->assertEquals(0, $model->getValueNative());
+ $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(false));
+ }
+
+ public function testStringReply()
+ {
+ $model = $this->serializer->createReplyModel('test');
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\BulkReply', $model);
+ $this->assertEquals('test', $model->getValueNative());
+ $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage('test'));
+ }
+
+ public function testNullCastNullBulkReply()
+ {
+ $model = $this->serializer->createReplyModel(null);
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\BulkReply', $model);
+ $this->assertEquals(null, $model->getValueNative());
+ $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(null));
+ }
+
+ public function testEmptyArrayMultiBulkReply()
+ {
+ $model = $this->serializer->createReplyModel(array());
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\MultiBulkReply', $model);
+ $this->assertEquals(array(), $model->getValueNative());
+ $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(array()));
+ }
+
+ public function testArrayMultiBulkReply()
+ {
+ $model = $this->serializer->createReplyModel(array('test', 123));
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\MultiBulkReply', $model);
+ $this->assertEquals(array('test', 123), $model->getValueNative());
+ $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(array('test', 123)));
+ }
+
+ public function testErrorReply()
+ {
+ $model = $this->serializer->createReplyModel(new Exception('ERR failure'));
+ $this->assertInstanceOf('Clue\Redis\Protocol\Model\ErrorReply', $model);
+ $this->assertEquals('ERR failure', $model->getValueNative());
+ $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(new Exception('ERR failure')));
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidArgument()
+ {
+ $this->serializer->createReplyModel((object)array());
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidReplyData()
+ {
+ $this->serializer->getReplyMessage((object)array());
+ }
+
+ /**
+ *
+ * @param array $data
+ * @dataProvider provideRequestMessage
+ */
+ public function testRequestMessage($command, $args)
+ {
+ // the model is already unit-tested, so just compare against its message
+ $model = $this->serializer->createRequestModel($command, $args);
+
+ $message = $this->serializer->getRequestMessage($command, $args);
+
+ $this->assertEquals($model->getMessageSerialized($this->serializer), $message);
+ }
+
+ public function provideRequestMessage()
+ {
+ return array(
+ array('PING', array()),
+ array('GET', array('a')),
+ array('SET', array('a', 'b')),
+ array('SET', array('empty', ''))
+ );
+ }
+
+// public function testBenchCreateRequest()
+// {
+// for ($i = 0; $i < 100000; ++$i) {
+// $this->serializer->createReplyModel(array('a', 'b', 'c'));
+// }
+// }
+}
diff --git a/vendor/clue/redis-protocol/tests/Serializer/RecursiveSerializerTest.php b/vendor/clue/redis-protocol/tests/Serializer/RecursiveSerializerTest.php
new file mode 100644
index 0000000..fe62ac5
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/Serializer/RecursiveSerializerTest.php
@@ -0,0 +1,11 @@
+<?php
+
+use Clue\Redis\Protocol\Serializer\RecursiveSerializer;
+
+class RecursiveSerializerTest extends AbstractSerializerTest
+{
+ protected function createSerializer()
+ {
+ return new RecursiveSerializer();
+ }
+}
diff --git a/vendor/clue/redis-protocol/tests/bootstrap.php b/vendor/clue/redis-protocol/tests/bootstrap.php
new file mode 100644
index 0000000..0b2ea18
--- /dev/null
+++ b/vendor/clue/redis-protocol/tests/bootstrap.php
@@ -0,0 +1,7 @@
+<?php
+
+(include_once __DIR__ . '/../vendor/autoload.php') OR die(PHP_EOL . 'ERROR: composer autoloader not found, run "composer install" or see README for instructions' . PHP_EOL);
+
+class TestCase extends PHPUnit_Framework_TestCase
+{
+}
diff --git a/vendor/clue/redis-react/CHANGELOG.md b/vendor/clue/redis-react/CHANGELOG.md
new file mode 100644
index 0000000..c5e8e66
--- /dev/null
+++ b/vendor/clue/redis-react/CHANGELOG.md
@@ -0,0 +1,268 @@
+# Changelog
+
+## 2.6.0 (2022-05-09)
+
+* Feature: Support PHP 8.1 release.
+ (#119 by @clue)
+
+* Improve documentation and CI configuration.
+ (#123 and #125 by @SimonFrings)
+
+## 2.5.0 (2021-08-31)
+
+* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop) and new Socket API.
+ (#114 and #115 by @SimonFrings)
+
+ ```php
+ // old (still supported)
+ $factory = new Clue\React\Redis\Factory($loop);
+
+ // new (using default loop)
+ $factory = new Clue\React\Redis\Factory();
+ ```
+
+* Feature: Improve error reporting, include Redis URI and socket error codes in all connection errors.
+ (#116 by @clue)
+
+* Documentation improvements and updated examples.
+ (#117 by @clue, #112 by @Nyholm and #113 by @PaulRotmann)
+
+* Improve test suite and use GitHub actions for continuous integration (CI).
+ (#111 by @SimonFrings)
+
+## 2.4.0 (2020-09-25)
+
+* Fix: Fix dangling timer when lazy connection closes with pending commands.
+ (#105 by @clue)
+
+* Improve test suite and add `.gitattributes` to exclude dev files from exports.
+ Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
+ (#96 and #97 by @clue and #99, #101 and #104 by @SimonFrings)
+
+## 2.3.0 (2019-03-11)
+
+* Feature: Add new `createLazyClient()` method to connect only on demand and
+ implement "idle" timeout to close underlying connection when unused.
+ (#87 and #88 by @clue and #82 by @WyriHaximus)
+
+ ```php
+ $client = $factory->createLazyClient('redis://localhost:6379');
+
+ $client->incr('hello');
+ $client->end();
+ ```
+
+* Feature: Support cancellation of pending connection attempts.
+ (#85 by @clue)
+
+ ```php
+ $promise = $factory->createClient($redisUri);
+
+ $loop->addTimer(3.0, function () use ($promise) {
+ $promise->cancel();
+ });
+ ```
+
+* Feature: Support connection timeouts.
+ (#86 by @clue)
+
+ ```php
+ $factory->createClient('localhost?timeout=0.5');
+ ```
+
+* Feature: Improve Exception messages for connection issues.
+ (#89 by @clue)
+
+ ```php
+ $factory->createClient('redis://localhost:6379')->then(
+ function (Client $client) {
+ // client connected (and authenticated)
+ },
+ function (Exception $e) {
+ // an error occurred while trying to connect (or authenticate) client
+ echo $e->getMessage() . PHP_EOL;
+ if ($e->getPrevious()) {
+ echo $e->getPrevious()->getMessage() . PHP_EOL;
+ }
+ }
+ );
+ ```
+
+* Improve test suite structure and add forward compatibility with PHPUnit 7 and PHPUnit 6
+ and test against PHP 7.1, 7.2, and 7.3 on TravisCI.
+ (#83 by @WyriHaximus and #84 by @clue)
+
+* Improve documentation and update project homepage.
+ (#81 and #90 by @clue)
+
+## 2.2.0 (2018-01-24)
+
+* Feature: Support communication over Unix domain sockets (UDS)
+ (#70 by @clue)
+
+ ```php
+ // new: now supports redis over Unix domain sockets (UDS)
+ $factory->createClient('redis+unix:///tmp/redis.sock');
+ ```
+
+## 2.1.0 (2017-09-25)
+
+* Feature: Update Socket dependency to support hosts file on all platforms
+ (#66 by @clue)
+
+ This means that connecting to hosts such as `localhost` (and for example
+ those used for Docker containers) will now work as expected across all
+ platforms with no changes required:
+
+ ```php
+ $factory->createClient('localhost');
+ ```
+
+## 2.0.0 (2017-09-20)
+
+A major compatibility release to update this package to support all latest
+ReactPHP components!
+
+This update involves a minor BC break due to dropped support for legacy
+versions. We've tried hard to avoid BC breaks where possible and minimize impact
+otherwise. We expect that most consumers of this package will actually not be
+affected by any BC breaks, see below for more details.
+
+* BC break: Remove all deprecated APIs, default to `redis://` URI scheme
+ and drop legacy SocketClient in favor of new Socket component.
+ (#61 by @clue)
+
+ > All of this affects the `Factory` only, which is mostly considered
+ "advanced usage". If you're affected by this BC break, then it's
+ recommended to first update to the intermediary v1.2.0 release, which
+ allows you to use the `redis://` URI scheme and a standard
+ `ConnectorInterface` and then update to this version without causing a
+ BC break.
+
+* BC break: Remove uneeded `data` event and support for advanced `MONITOR`
+ command for performance and consistency reasons and
+ remove underdocumented `isBusy()` method.
+ (#62, #63 and #64 by @clue)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8 and EventLoop v1.0 and Evenement v3
+ (#65 by @clue)
+
+## 1.2.0 (2017-09-19)
+
+* Feature: Support `redis[s]://` URI scheme and deprecate legacy URIs
+ (#60 by @clue)
+
+ ```php
+ $factory->createClient('redis://:secret@localhost:6379/4');
+ $factory->createClient('redis://localhost:6379?password=secret&db=4');
+ ```
+
+* Feature: Factory accepts Connector from Socket and deprecate legacy SocketClient
+ (#59 by @clue)
+
+ 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
+ )
+ ));
+
+ $factory = new Factory($loop, $connector);
+ ```
+
+## 1.1.0 (2017-09-18)
+
+* Feature: Update SocketClient dependency to latest version
+ (#58 by @clue)
+
+* Improve test suite by adding PHPUnit to require-dev,
+ fix HHVM build for now again and ignore future HHVM build errors,
+ lock Travis distro so new defaults will not break the build and
+ skip functional integration tests by default
+ (#52, #53, #56 and #57 by @clue)
+
+## 1.0.0 (2016-05-20)
+
+* First stable release, now following SemVer
+
+* BC break: Consistent public API, mark internal APIs as such
+ (#38 by @clue)
+
+ ```php
+ // old
+ $client->on('data', function (MessageInterface $message, Client $client) {
+ // process an incoming message (raw message object)
+ });
+
+ // new
+ $client->on('data', function (MessageInterface $message) use ($client) {
+ // process an incoming message (raw message object)
+ });
+ ```
+
+> Contains no other changes, so it's actually fully compatible with the v0.5.2 release.
+
+## 0.5.2 (2016-05-20)
+
+* Fix: Do not send empty SELECT statement when no database has been given
+ (#35, #36 by @clue)
+
+* Improve documentation, update dependencies and add first class support for PHP 7
+
+## 0.5.1 (2015-01-12)
+
+* Fix: Fix compatibility with react/promise v2.0 for monitor and PubSub commands.
+ (#28)
+
+## 0.5.0 (2014-11-12)
+
+* Feature: Support PubSub commands (P)(UN)SUBSCRIBE and watching for "message",
+ "subscribe" and "unsubscribe" events
+ (#24)
+
+* Feature: Support MONITOR command and watching for "monitor" events
+ (#23)
+
+* Improve documentation, update locked dependencies and add first class support for HHVM
+ (#25, #26 and others)
+
+## 0.4.0 (2014-08-25)
+
+* BC break: The `Client` class has been renamed to `StreamingClient`.
+ Added new `Client` interface.
+ (#18 and #19)
+
+* BC break: Rename `message` event to `data`.
+ (#21)
+
+* BC break: The `Factory` now accepts a `LoopInterface` as first argument.
+ (#22)
+
+* Fix: The `close` event will be emitted once when invoking the `Client::close()`
+ method or when the underlying stream closes.
+ (#20)
+
+* Refactored code, improved testability, extended test suite and better code coverage.
+ (#11, #18 and #20)
+
+> Note: This is an intermediary release to ease upgrading to the imminent v0.5 release.
+
+## 0.3.0 (2014-05-31)
+
+* First tagged release
+
+> Note: Starts at v0.3 because previous versions were not tagged. Leaving some
+> room in case they're going to be needed in the future.
+
+## 0.0.0 (2013-07-05)
+
+* Initial concept
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/README.md b/vendor/clue/redis-react/README.md
new file mode 100644
index 0000000..5492572
--- /dev/null
+++ b/vendor/clue/redis-react/README.md
@@ -0,0 +1,660 @@
+# clue/reactphp-redis
+
+[![CI status](https://github.com/clue/reactphp-redis/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/reactphp-redis/actions)
+[![installs on Packagist](https://img.shields.io/packagist/dt/clue/redis-react?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/redis-react)
+
+Async [Redis](https://redis.io/) client implementation, built on top of [ReactPHP](https://reactphp.org/).
+
+[Redis](https://redis.io/) is an open source, advanced, in-memory key-value database.
+It offers a set of simple, atomic operations in order to work with its primitive data types.
+Its lightweight design and fast operation makes it an ideal candidate for modern application stacks.
+This library provides you a simple API to work with your Redis database from within PHP.
+It enables you to set and query its data or use its PubSub topics to react to incoming events.
+
+* **Async execution of Commands** -
+ Send any number of commands to Redis in parallel (automatic pipeline) and
+ process their responses as soon as results come in.
+ The Promise-based design provides a *sane* interface to working with async responses.
+* **Event-driven core** -
+ Register your event handler callbacks to react to incoming events, such as an incoming PubSub message event.
+* **Lightweight, SOLID design** -
+ Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
+ and does not get in your way.
+ Future or custom commands and events require no changes to be supported.
+* **Good test coverage** -
+ Comes with an automated tests suite and is regularly tested against versions as old as Redis v2.6 and newer.
+
+**Table of Contents**
+
+* [Support us](#support-us)
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [Commands](#commands)
+ * [Promises](#promises)
+ * [PubSub](#pubsub)
+* [API](#api)
+ * [Factory](#factory)
+ * [createClient()](#createclient)
+ * [createLazyClient()](#createlazyclient)
+ * [Client](#client)
+ * [__call()](#__call)
+ * [end()](#end)
+ * [close()](#close)
+ * [error event](#error-event)
+ * [close event](#close-event)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Quickstart example
+
+Once [installed](#install), you can use the following code to connect to your
+local Redis server and send some requests:
+
+```php
+<?php
+
+require __DIR__ . '/vendor/autoload.php';
+
+$factory = new Clue\React\Redis\Factory();
+$redis = $factory->createLazyClient('localhost:6379');
+
+$redis->set('greeting', 'Hello world');
+$redis->append('greeting', '!');
+
+$redis->get('greeting')->then(function ($greeting) {
+ // Hello world!
+ echo $greeting . PHP_EOL;
+});
+
+$redis->incr('invocation')->then(function ($n) {
+ echo 'This is invocation #' . $n . PHP_EOL;
+});
+
+// end connection once all pending requests have been resolved
+$redis->end();
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### Commands
+
+Most importantly, this project provides a [`Client`](#client) instance that
+can be used to invoke all [Redis commands](https://redis.io/commands) (such as `GET`, `SET`, etc.).
+
+```php
+$redis->get($key);
+$redis->set($key, $value);
+$redis->exists($key);
+$redis->expire($key, $seconds);
+$redis->mget($key1, $key2, $key3);
+
+$redis->multi();
+$redis->exec();
+
+$redis->publish($channel, $payload);
+$redis->subscribe($channel);
+
+$redis->ping();
+$redis->select($database);
+
+// many more…
+```
+
+Each method call matches the respective [Redis command](https://redis.io/commands).
+For example, the `$redis->get()` method will invoke the [`GET` command](https://redis.io/commands/get).
+
+All [Redis commands](https://redis.io/commands) are automatically available as
+public methods via the magic [`__call()` method](#__call).
+Listing all available commands is out of scope here, please refer to the
+[Redis command reference](https://redis.io/commands).
+
+Any arguments passed to the method call will be forwarded as command arguments.
+For example, the `$redis->set('name', 'Alice')` call will perform the equivalent of a
+`SET name Alice` command. It's safe to pass integer arguments where applicable (for
+example `$redis->expire($key, 60)`), but internally Redis requires all arguments to
+always be coerced to string values.
+
+Each of these commands supports async operation and returns a [Promise](#promises)
+that eventually *fulfills* with its *results* on success or *rejects* with an
+`Exception` on error. See also the following section about [promises](#promises)
+for more details.
+
+### Promises
+
+Sending commands is async (non-blocking), so you can actually send multiple
+commands in parallel.
+Redis will respond to each command request with a response message, pending
+commands will be pipelined automatically.
+
+Sending commands uses a [Promise](https://github.com/reactphp/promise)-based
+interface that makes it easy to react to when a command is completed
+(i.e. either successfully fulfilled or rejected with an error):
+
+```php
+$redis->get($key)->then(function (?string $value) {
+ var_dump($value);
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+### PubSub
+
+This library is commonly used to efficiently transport messages using Redis'
+[Pub/Sub](https://redis.io/topics/pubsub) (Publish/Subscribe) channels. For
+instance, this can be used to distribute single messages to a larger number
+of subscribers (think horizontal scaling for chat-like applications) or as an
+efficient message transport in distributed systems (microservice architecture).
+
+The [`PUBLISH` command](https://redis.io/commands/publish) can be used to
+send a message to all clients currently subscribed to a given channel:
+
+```php
+$channel = 'user';
+$message = json_encode(array('id' => 10));
+$redis->publish($channel, $message);
+```
+
+The [`SUBSCRIBE` command](https://redis.io/commands/subscribe) can be used to
+subscribe to a channel and then receive incoming PubSub `message` events:
+
+```php
+$channel = 'user';
+$redis->subscribe($channel);
+
+$redis->on('message', function ($channel, $payload) {
+ // pubsub message received on given $channel
+ var_dump($channel, json_decode($payload));
+});
+```
+
+Likewise, you can use the same client connection to subscribe to multiple
+channels by simply executing this command multiple times:
+
+```php
+$redis->subscribe('user.register');
+$redis->subscribe('user.join');
+$redis->subscribe('user.leave');
+```
+
+Similarly, the [`PSUBSCRIBE` command](https://redis.io/commands/psubscribe) can
+be used to subscribe to all channels matching a given pattern and then receive
+all incoming PubSub messages with the `pmessage` event:
+
+
+```php
+$pattern = 'user.*';
+$redis->psubscribe($pattern);
+
+$redis->on('pmessage', function ($pattern, $channel, $payload) {
+ // pubsub message received matching given $pattern
+ var_dump($channel, json_decode($payload));
+});
+```
+
+Once you're in a subscribed state, Redis no longer allows executing any other
+commands on the same client connection. This is commonly worked around by simply
+creating a second client connection and dedicating one client connection solely
+for PubSub subscriptions and the other for all other commands.
+
+The [`UNSUBSCRIBE` command](https://redis.io/commands/unsubscribe) and
+[`PUNSUBSCRIBE` command](https://redis.io/commands/punsubscribe) can be used to
+unsubscribe from active subscriptions if you're no longer interested in
+receiving any further events for the given channel and pattern subscriptions
+respectively:
+
+```php
+$redis->subscribe('user');
+
+Loop::addTimer(60.0, function () use ($redis) {
+ $redis->unsubscribe('user');
+});
+```
+
+Likewise, once you've unsubscribed the last channel and pattern, the client
+connection is no longer in a subscribed state and you can issue any other
+command over this client connection again.
+
+Each of the above methods follows normal request-response semantics and return
+a [`Promise`](#promises) to await successful subscriptions. Note that while
+Redis allows a variable number of arguments for each of these commands, this
+library is currently limited to single arguments for each of these methods in
+order to match exactly one response to each command request. As an alternative,
+the methods can simply be invoked multiple times with one argument each.
+
+Additionally, can listen for the following PubSub events to get notifications
+about subscribed/unsubscribed channels and patterns:
+
+```php
+$redis->on('subscribe', function ($channel, $total) {
+ // subscribed to given $channel
+});
+$redis->on('psubscribe', function ($pattern, $total) {
+ // subscribed to matching given $pattern
+});
+$redis->on('unsubscribe', function ($channel, $total) {
+ // unsubscribed from given $channel
+});
+$redis->on('punsubscribe', function ($pattern, $total) {
+ // unsubscribed from matching given $pattern
+});
+```
+
+When using the [`createLazyClient()`](#createlazyclient) method, the `unsubscribe`
+and `punsubscribe` events will be invoked automatically when the underlying
+connection is lost. This gives you control over re-subscribing to the channels
+and patterns as appropriate.
+
+## API
+
+### Factory
+
+The `Factory` is responsible for creating your [`Client`](#client) instance.
+
+```php
+$factory = new Clue\React\Redis\Factory();
+```
+
+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.
+
+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(array(
+ 'dns' => '127.0.0.1',
+ 'tcp' => array(
+ 'bindto' => '192.168.10.1:0'
+ ),
+ 'tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ )
+));
+
+$factory = new Clue\React\Redis\Factory(null, $connector);
+```
+
+#### createClient()
+
+The `createClient(string $uri): PromiseInterface<Client,Exception>` method can be used to
+create a new [`Client`](#client).
+
+It helps with establishing a plain TCP/IP or secure TLS connection to Redis
+and optionally authenticating (AUTH) and selecting the right database (SELECT).
+
+```php
+$factory->createClient('localhost:6379')->then(
+ function (Client $redis) {
+ // client connected (and authenticated)
+ },
+ function (Exception $e) {
+ // an error occurred while trying to connect (or authenticate) client
+ }
+);
+```
+
+The method returns a [Promise](https://github.com/reactphp/promise) that
+will resolve with a [`Client`](#client)
+instance on success or will reject with an `Exception` if the URL is
+invalid or the connection or authentication fails.
+
+The returned Promise is implemented in such a way that it can be
+cancelled when it is still pending. Cancelling a pending promise will
+reject its value with an Exception and will cancel the underlying TCP/IP
+connection attempt and/or Redis authentication.
+
+```php
+$promise = $factory->createClient($uri);
+
+Loop::addTimer(3.0, function () use ($promise) {
+ $promise->cancel();
+});
+```
+
+The `$redisUri` can be given in the
+[standard](https://www.iana.org/assignments/uri-schemes/prov/redis) form
+`[redis[s]://][:auth@]host[:port][/db]`.
+You can omit the URI scheme and port if you're connecting to the default port 6379:
+
+```php
+// both are equivalent due to defaults being applied
+$factory->createClient('localhost');
+$factory->createClient('redis://localhost:6379');
+```
+
+Redis supports password-based authentication (`AUTH` command). Note that Redis'
+authentication mechanism does not employ a username, so you can pass the
+password `h@llo` URL-encoded (percent-encoded) as part of the URI like this:
+
+```php
+// all forms are equivalent
+$factory->createClient('redis://:h%40llo@localhost');
+$factory->createClient('redis://ignored:h%40llo@localhost');
+$factory->createClient('redis://localhost?password=h%40llo');
+```
+
+You can optionally include a path that will be used to select (SELECT command) the right database:
+
+```php
+// both forms are equivalent
+$factory->createClient('redis://localhost/2');
+$factory->createClient('redis://localhost?db=2');
+```
+
+You can use the [standard](https://www.iana.org/assignments/uri-schemes/prov/rediss)
+`rediss://` URI scheme if you're using a secure TLS proxy in front of Redis:
+
+```php
+$factory->createClient('rediss://redis.example.com:6340');
+```
+
+You can use the `redis+unix://` URI scheme if your Redis instance is listening
+on a Unix domain socket (UDS) path:
+
+```php
+$factory->createClient('redis+unix:///tmp/redis.sock');
+
+// the URI MAY contain `password` and `db` query parameters as seen above
+$factory->createClient('redis+unix:///tmp/redis.sock?password=secret&db=2');
+
+// the URI MAY contain authentication details as userinfo as seen above
+// should be used with care, also note that database can not be passed as path
+$factory->createClient('redis+unix://:secret@/tmp/redis.sock');
+```
+
+This method respects PHP's `default_socket_timeout` setting (default 60s)
+as a timeout for establishing the connection and waiting for successful
+authentication. You can explicitly pass a custom timeout value in seconds
+(or use a negative number to not apply a timeout) like this:
+
+```php
+$factory->createClient('localhost?timeout=0.5');
+```
+
+#### createLazyClient()
+
+The `createLazyClient(string $uri): Client` method can be used to
+create a new [`Client`](#client).
+
+It helps with establishing a plain TCP/IP or secure TLS connection to Redis
+and optionally authenticating (AUTH) and selecting the right database (SELECT).
+
+```php
+$redis = $factory->createLazyClient('localhost:6379');
+
+$redis->incr('hello');
+$redis->end();
+```
+
+This method immediately returns a "virtual" connection implementing the
+[`Client`](#client) that can be used to interface with your Redis database.
+Internally, it lazily creates the underlying database connection only on
+demand once the first request is invoked on this instance and will queue
+all outstanding requests until the underlying connection is ready.
+Additionally, it will only keep this underlying connection in an "idle" state
+for 60s by default and will automatically close the underlying connection when
+it is no longer needed.
+
+From a consumer side this means that you can start sending commands to the
+database right away while the underlying connection may still be
+outstanding. Because creating this underlying connection may take some
+time, it will enqueue all oustanding commands and will ensure that all
+commands will be executed in correct order once the connection is ready.
+In other words, this "virtual" connection behaves just like a "real"
+connection as described in the `Client` interface and frees you from having
+to deal with its async resolution.
+
+If the underlying database connection fails, it will reject all
+outstanding commands and will return to the initial "idle" state. This
+means that you can keep sending additional commands at a later time which
+will again try to open a new underlying connection. Note that this may
+require special care if you're using transactions (`MULTI`/`EXEC`) that are kept
+open for longer than the idle period.
+
+While using PubSub channels (see `SUBSCRIBE` and `PSUBSCRIBE` commands), this client
+will never reach an "idle" state and will keep pending forever (or until the
+underlying database connection is lost). Additionally, if the underlying
+database connection drops, it will automatically send the appropriate `unsubscribe`
+and `punsubscribe` events for all currently active channel and pattern subscriptions.
+This allows you to react to these events and restore your subscriptions by
+creating a new underlying connection repeating the above commands again.
+
+Note that creating the underlying connection will be deferred until the
+first request is invoked. Accordingly, any eventual connection issues
+will be detected once this instance is first used. You can use the
+`end()` method to ensure that the "virtual" connection will be soft-closed
+and no further commands can be enqueued. Similarly, calling `end()` on
+this instance when not currently connected will succeed immediately and
+will not have to wait for an actual underlying connection.
+
+Depending on your particular use case, you may prefer this method or the
+underlying `createClient()` which resolves with a promise. For many
+simple use cases it may be easier to create a lazy connection.
+
+The `$redisUri` can be given in the
+[standard](https://www.iana.org/assignments/uri-schemes/prov/redis) form
+`[redis[s]://][:auth@]host[:port][/db]`.
+You can omit the URI scheme and port if you're connecting to the default port 6379:
+
+```php
+// both are equivalent due to defaults being applied
+$factory->createLazyClient('localhost');
+$factory->createLazyClient('redis://localhost:6379');
+```
+
+Redis supports password-based authentication (`AUTH` command). Note that Redis'
+authentication mechanism does not employ a username, so you can pass the
+password `h@llo` URL-encoded (percent-encoded) as part of the URI like this:
+
+```php
+// all forms are equivalent
+$factory->createLazyClient('redis://:h%40llo@localhost');
+$factory->createLazyClient('redis://ignored:h%40llo@localhost');
+$factory->createLazyClient('redis://localhost?password=h%40llo');
+```
+
+You can optionally include a path that will be used to select (SELECT command) the right database:
+
+```php
+// both forms are equivalent
+$factory->createLazyClient('redis://localhost/2');
+$factory->createLazyClient('redis://localhost?db=2');
+```
+
+You can use the [standard](https://www.iana.org/assignments/uri-schemes/prov/rediss)
+`rediss://` URI scheme if you're using a secure TLS proxy in front of Redis:
+
+```php
+$factory->createLazyClient('rediss://redis.example.com:6340');
+```
+
+You can use the `redis+unix://` URI scheme if your Redis instance is listening
+on a Unix domain socket (UDS) path:
+
+```php
+$factory->createLazyClient('redis+unix:///tmp/redis.sock');
+
+// the URI MAY contain `password` and `db` query parameters as seen above
+$factory->createLazyClient('redis+unix:///tmp/redis.sock?password=secret&db=2');
+
+// the URI MAY contain authentication details as userinfo as seen above
+// should be used with care, also note that database can not be passed as path
+$factory->createLazyClient('redis+unix://:secret@/tmp/redis.sock');
+```
+
+This method respects PHP's `default_socket_timeout` setting (default 60s)
+as a timeout for establishing the underlying connection and waiting for
+successful authentication. You can explicitly pass a custom timeout value
+in seconds (or use a negative number to not apply a timeout) like this:
+
+```php
+$factory->createLazyClient('localhost?timeout=0.5');
+```
+
+By default, this method will keep "idle" connections open for 60s and will
+then end the underlying connection. The next request after an "idle"
+connection ended will automatically create a new underlying connection.
+This ensure you always get a "fresh" connection and as such should not be
+confused with a "keepalive" or "heartbeat" mechanism, as this will not
+actively try to probe the connection. You can explicitly pass a custom
+idle timeout value in seconds (or use a negative number to not apply a
+timeout) like this:
+
+```php
+$factory->createLazyClient('localhost?idle=0.1');
+```
+
+### Client
+
+The `Client` is responsible for exchanging messages with Redis
+and keeps track of pending commands.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events as documented below.
+
+#### __call()
+
+The `__call(string $name, string[] $args): PromiseInterface<mixed,Exception>` method can be used to
+invoke the given command.
+
+This is a magic method that will be invoked when calling any Redis command on this instance.
+Each method call matches the respective [Redis command](https://redis.io/commands).
+For example, the `$redis->get()` method will invoke the [`GET` command](https://redis.io/commands/get).
+
+```php
+$redis->get($key)->then(function (?string $value) {
+ var_dump($value);
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+All [Redis commands](https://redis.io/commands) are automatically available as
+public methods via this magic `__call()` method.
+Listing all available commands is out of scope here, please refer to the
+[Redis command reference](https://redis.io/commands).
+
+Any arguments passed to the method call will be forwarded as command arguments.
+For example, the `$redis->set('name', 'Alice')` call will perform the equivalent of a
+`SET name Alice` command. It's safe to pass integer arguments where applicable (for
+example `$redis->expire($key, 60)`), but internally Redis requires all arguments to
+always be coerced to string values.
+
+Each of these commands supports async operation and returns a [Promise](#promises)
+that eventually *fulfills* with its *results* on success or *rejects* with an
+`Exception` on error. See also [promises](#promises) for more details.
+
+#### end()
+
+The `end():void` method can be used to
+soft-close the Redis connection once all pending commands are completed.
+
+#### close()
+
+The `close():void` method can be used to
+force-close the Redis connection and reject all pending commands.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, such as
+when the client connection is lost or is invalid.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$redis->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event will only be triggered for fatal errors and will be followed
+by closing the client connection. It is not to be confused with "soft"
+errors caused by invalid commands.
+
+#### close event
+
+The `close` event will be emitted once the client connection closes (terminates).
+
+```php
+$redis->on('close', function () {
+ echo 'Connection closed' . PHP_EOL;
+});
+```
+
+See also the [`close()`](#close) method.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org/).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require clue/redis-react:^2.6
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
+HHVM.
+It's *highly recommended to use the latest supported PHP version* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org/):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ vendor/bin/phpunit
+```
+
+The test suite contains both unit tests and functional integration tests.
+The functional tests require access to a running Redis server instance
+and will be skipped by default.
+
+If you don't have access to a running Redis server, you can also use a temporary `Redis` Docker image:
+
+```bash
+$ docker run --net=host redis
+```
+
+To now run the functional tests, you need to supply *your* login
+details in an environment variable like this:
+
+```bash
+$ REDIS_URI=localhost:6379 vendor/bin/phpunit
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
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/CHANGELOG.md b/vendor/clue/soap-react/CHANGELOG.md
new file mode 100644
index 0000000..2b605d2
--- /dev/null
+++ b/vendor/clue/soap-react/CHANGELOG.md
@@ -0,0 +1,130 @@
+# Changelog
+
+## 2.0.0 (2020-10-28)
+
+* Feature / BC break: Update to reactphp/http v1.0.0.
+ (#45 by @SimonFrings)
+
+* Feature / BC break: Add type declarations and require PHP 7.1+ as a consequence
+ (#47 by @SimonFrings, #49 by @clue)
+
+* Use fully qualified class names in documentation.
+ (#46 by @SimonFrings)
+
+* Improve test suite and add `.gitattributes` to exclude dev files from export.
+ Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
+ (#40 by @andreybolonin, #42 and #44 by @SimonFrings and #48 by @clue)
+
+## 1.0.0 (2018-11-07)
+
+* First stable release, now following SemVer!
+
+ I'd like to thank [Bergfreunde GmbH](https://www.bergfreunde.de/), a German-based
+ online retailer for Outdoor Gear & Clothing, for sponsoring large parts of this development! 🎉
+ Thanks to sponsors like this, who understand the importance of open source
+ development, I can justify spending time and focus on open source development
+ instead of traditional paid work.
+
+ > Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
+
+* BC break / Feature: Replace `Factory` with simplified `Client` constructor,
+ add support for optional SOAP options and non-WSDL mode and
+ respect WSDL type definitions when decoding and support classmap option.
+ (#31, #32 and #33 by @clue)
+
+ ```php
+ // old
+ $factory = new Factory($loop);
+ $client = $factory->createClientFromWsdl($wsdl);
+
+ // new
+ $browser = new Browser($loop);
+ $client = new Client($browser, $wsdl);
+ ```
+
+ The `Client` constructor now 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',
+ ));
+ ```
+
+* BC break: Mark all classes as final and all internal APIs as `@internal`.
+ (#26 and #37 by @clue)
+
+* Feature: Add new `Client::withLocation()` method.
+ (#38 by @floriansimon1, @pascal-hofmann and @clue)
+
+ The `withLocation(string $location): self` method can be used to
+ return 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).
+
+* Feature: Properly handle SOAP error responses, accept HTTP error responses and do not follow any HTTP redirects.
+ (#35 by @clue)
+
+* Improve documentation and update project homepage,
+ documentation for HTTP proxy servers,
+ support timeouts for SOAP requests (HTTP timeout option) and
+ add cancellation support.
+ (#25, #29, #30 #34 and #36 by @clue)
+
+* Improve test suite by supporting PHPUnit 6,
+ optionally skip functional integration tests requiring internet and
+ test against PHP 7.2 and PHP 7.1 and latest ReactPHP components.
+ (#24 by @carusogabriel and #27 and #28 by @clue)
+
+## 0.2.0 (2017-10-02)
+
+* Feature: Added the possibility to use local WSDL files
+ (#11 by @floriansimon1)
+
+ ```php
+ $factory = new Factory($loop);
+ $wsdl = file_get_contents('service.wsdl');
+ $client = $factory->createClientFromWsdl($wsdl);
+ ```
+
+* Feature: Add `Client::getLocation()` helper
+ (#13 by @clue)
+
+* Feature: Forward compatibility with clue/buzz-react v2.0 and upcoming EventLoop
+ (#9 by @floriansimon1 and #19 and #21 by @clue)
+
+* Improve test suite by adding PHPUnit to require-dev and
+ test PHP 5.3 through PHP 7.0 and HHVM and
+ fix Travis build config
+ (#1 by @WyriHaximus and #12, #17 and #22 by @clue)
+
+## 0.1.0 (2014-07-28)
+
+* First tagged release
+
+## 0.0.0 (2014-07-20)
+
+* Initial concept
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/README.md b/vendor/clue/soap-react/README.md
new file mode 100644
index 0000000..0984e13
--- /dev/null
+++ b/vendor/clue/soap-react/README.md
@@ -0,0 +1,453 @@
+# clue/reactphp-soap [![Build Status](https://travis-ci.org/clue/reactphp-soap.svg?branch=master)](https://travis-ci.org/clue/reactphp-soap)
+
+Simple, async [SOAP](https://en.wikipedia.org/wiki/SOAP) web service client library,
+built on top of [ReactPHP](https://reactphp.org/).
+
+Most notably, SOAP is often used for invoking
+[Remote procedure calls](https://en.wikipedia.org/wiki/Remote_procedure_call) (RPCs)
+in distributed systems.
+Internally, SOAP messages are encoded as XML and usually sent via HTTP POST requests.
+For the most part, SOAP (originally *Simple Object Access protocol*) is a protocol of the past,
+and in fact anything but *simple*.
+It is still in use by many (often *legacy*) systems.
+This project provides a *simple* API for invoking *async* RPCs to remote web services.
+
+* **Async execution of functions** -
+ Send any number of functions (RPCs) to the remote web service in parallel and
+ process their responses as soon as results come in.
+ The Promise-based design provides a *sane* interface to working with out of order responses.
+* **Async processing of the WSDL** -
+ The WSDL (web service description language) file will be downloaded and processed
+ in the background.
+* **Event-driven core** -
+ Internally, everything uses event handlers to react to incoming events, such as an incoming RPC result.
+* **Lightweight, SOLID design** -
+ Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
+ and does not get in your way.
+ Built on top of tested components instead of re-inventing the wheel.
+* **Good test coverage** -
+ Comes with an automated tests suite and is regularly tested against actual web services in the wild.
+
+**Table of contents**
+
+* [Support us](#support-us)
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [Client](#client)
+ * [soapCall()](#soapcall)
+ * [getFunctions()](#getfunctions)
+ * [getTypes()](#gettypes)
+ * [getLocation()](#getlocation)
+ * [withLocation()](#withlocation)
+ * [Proxy](#proxy)
+ * [Functions](#functions)
+ * [Promises](#promises)
+ * [Cancellation](#cancellation)
+ * [Timeouts](#timeouts)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Quickstart example
+
+Once [installed](#install), you can use the following code to query an example
+web service via SOAP:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$browser = new React\Http\Browser($loop);
+$wsdl = 'http://example.com/demo.wsdl';
+
+$browser->get($wsdl)->then(function (Psr\Http\Message\ResponseInterface $response) use ($browser) {
+ $client = new Clue\React\Soap\Client($browser, (string)$response->getBody());
+ $api = new Clue\React\Soap\Proxy($client);
+
+ $api->getBank(array('blz' => '12070000'))->then(function ($result) {
+ var_dump('Result', $result);
+ });
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### Client
+
+The `Client` class is responsible for communication with the remote SOAP
+WebService server.
+
+It requires a [`Browser`](https://github.com/reactphp/http#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 React\Http\Browser($loop);
+
+$wsdl = '<?xml …';
+$options = array();
+
+$client = new Clue\React\Soap\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/reactphp/http#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 React\Http\Browser($loop, $connector);
+$client = new Clue\React\Soap\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 React\Http\Browser($loop);
+
+$browser->get($url)->then(
+ function (Psr\Http\Message\ResponseInterface $response) use ($browser) {
+ // WSDL file is ready, create client
+ $client = new Clue\React\Soap\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 Clue\React\Soap\Client($browser, $wsdl);
+} catch (SoapFault $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+}
+```
+
+> Note that if you have an old version of `ext-xdebug` < 2.7 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`](https://www.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 Clue\React\Soap\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 Clue\React\Soap\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 Clue\React\Soap\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 Clue\React\Soap\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/reactphp/http#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.
+
+#### soapCall()
+
+The `soapCall(string $method, mixed[] $arguments): PromiseInterface<mixed, Exception>` method can be used to
+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 Clue\React\Soap\Proxy($client);
+$promise = $proxy->ping('hello', 42);
+```
+
+#### getFunctions()
+
+The `getFunctions(): string[]|null` method can be used to
+return an array of functions defined in the WSDL.
+
+It returns the equivalent of PHP's
+[`SoapClient::__getFunctions()`](https://www.php.net/manual/en/soapclient.getfunctions.php).
+In non-WSDL mode, this method returns `null`.
+
+#### getTypes()
+
+The `getTypes(): string[]|null` method can be used to
+return an array of types defined in the WSDL.
+
+It returns the equivalent of PHP's
+[`SoapClient::__getTypes()`](https://www.php.net/manual/en/soapclient.gettypes.php).
+In non-WSDL mode, this method returns `null`.
+
+#### getLocation()
+
+The `getLocation(string|int $function): string` method can be used to
+return 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`.
+
+#### withLocation()
+
+The `withLocation(string $location): self` method can be used to
+return 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).
+
+### Proxy
+
+The `Proxy` class wraps an existing [`Client`](#client) instance in order to ease calling
+SOAP functions.
+
+```php
+$proxy = new Clue\React\Soap\Proxy($client);
+```
+
+> 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.
+
+#### Functions
+
+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.
+
+#### Promises
+
+Issuing SOAP functions is async (non-blocking), so you can actually send multiple RPC requests in parallel.
+The web service will respond to each request with a return value. The order is not guaranteed.
+Sending requests uses a [Promise](https://github.com/reactphp/promise)-based interface that makes it easy to react to when a request is *fulfilled*
+(i.e. either successfully resolved or rejected with an error):
+
+```php
+$proxy->demo()->then(
+ function ($response) {
+ // response received for demo function
+ },
+ function (Exception $e) {
+ // an error occured while executing the request
+ }
+});
+```
+
+#### Cancellation
+
+The returned Promise is implemented in such a way that it can be cancelled
+when it is still pending.
+Cancelling a pending promise will reject its value with an Exception and
+clean up any underlying resources.
+
+```php
+$promise = $proxy->demo();
+
+$loop->addTimer(2.0, function () use ($promise) {
+ $promise->cancel();
+});
+```
+
+#### Timeouts
+
+This library uses a very efficient HTTP implementation, so most SOAP requests
+should usually be completed in mere milliseconds. However, when sending SOAP
+requests over an unreliable network (the internet), there are a number of things
+that can go wrong and may cause the request to fail after a time. As such,
+timeouts are handled by the underlying HTTP library and this library respects
+PHP's `default_socket_timeout` setting (default 60s) as a timeout for sending the
+outgoing SOAP request and waiting for a successful response and will otherwise
+cancel the pending request and reject its value with an Exception.
+
+Note that this timeout value covers creating the underlying transport connection,
+sending the SOAP request, waiting for the remote service to process the request
+and receiving the full SOAP response. To pass a custom timeout value, you can
+assign the underlying [`timeout` option](https://github.com/clue/reactphp/http#timeouts)
+like this:
+
+```php
+$browser = new React\Http\Browser($loop);
+$browser = $browser->withOptions(array(
+ 'timeout' => 10.0
+));
+
+$client = new Clue\React\Soap\Client($browser, $wsdl);
+$proxy = new Clue\React\Soap\Proxy($client);
+
+$proxy->demo()->then(function ($response) {
+ // response received within 10 seconds maximum
+ var_dump($response);
+});
+```
+
+Similarly, you can use a negative timeout value to not apply a timeout at all
+or use a `null` value to restore the default handling. Note that the underlying
+connection may still impose a different timeout value. See also the underlying
+[`timeout` option](https://github.com/clue/reactphp/http#timeouts) for more details.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require clue/soap-react:^2.0
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus only requires `ext-soap` and
+supports running on PHP 7.1+.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
diff --git a/vendor/clue/soap-react/composer.json b/vendor/clue/soap-react/composer.json
new file mode 100644
index 0000000..c2a918e
--- /dev/null
+++ b/vendor/clue/soap-react/composer.json
@@ -0,0 +1,29 @@
+{
+ "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@clue.engineering"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "Clue\\React\\Soap\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Clue\\Tests\\React\\Soap\\": "tests/" }
+ },
+ "require": {
+ "php": ">=7.1",
+ "react/http": "^1.0",
+ "react/promise": "^2.1 || ^1.2",
+ "ext-soap": "*"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.0",
+ "phpunit/phpunit": "^9.3 || ^7.5"
+ }
+}
diff --git a/vendor/clue/soap-react/src/Client.php b/vendor/clue/soap-react/src/Client.php
new file mode 100644
index 0000000..dda7b98
--- /dev/null
+++ b/vendor/clue/soap-react/src/Client.php
@@ -0,0 +1,326 @@
+<?php
+
+namespace Clue\React\Soap;
+
+use Clue\React\Soap\Protocol\ClientDecoder;
+use Clue\React\Soap\Protocol\ClientEncoder;
+use Psr\Http\Message\ResponseInterface;
+use React\Http\Browser;
+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/reactphp/http#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 React\Http\Browser($loop);
+ *
+ * $wsdl = '<?xml …';
+ * $options = array();
+ *
+ * $client = new Clue\React\Soap\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/http#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 React\Http\Browser($loop, $connector);
+ * $client = new Clue\React\Soap\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 React\Http\Browser($loop);
+ *
+ * $browser->get($url)->then(
+ * function (Psr\Http\Message\ResponseInterface $response) use ($browser) {
+ * // WSDL file is ready, create client
+ * $client = new Clue\React\Soap\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 Clue\React\Soap\Client($browser, $wsdl);
+ * } catch (SoapFault $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * }
+ * ```
+ *
+ * > Note that if you have an old version of `ext-xdebug` < 2.7 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`](https://www.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 Clue\React\Soap\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 Clue\React\Soap\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 Clue\React\Soap\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 Clue\React\Soap\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/http#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, ?string $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->withRejectErrorResponse(false);
+ $browser = $browser->withFollowRedirects(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 Clue\React\Soap\Proxy($client);
+ * $promise = $proxy->ping('hello', 42);
+ * ```
+ *
+ * @param string $name
+ * @param mixed[] $args
+ * @return PromiseInterface Returns a Promise<mixed, Exception>
+ */
+ public function soapCall(string $name, array $args): PromiseInterface
+ {
+ 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->request(
+ $request->getMethod(),
+ (string) $request->getUri(),
+ $request->getHeaders(),
+ (string) $request->getBody()
+ )->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()`](https://www.php.net/manual/en/soapclient.getfunctions.php).
+ * In non-WSDL mode, this method returns `null`.
+ *
+ * @return string[]|null
+ */
+ public function getFunctions(): ?array
+ {
+ return $this->encoder->__getFunctions();
+ }
+
+ /**
+ * Returns an array of types defined in the WSDL.
+ *
+ * It returns the equivalent of PHP's
+ * [`SoapClient::__getTypes()`](https://www.php.net/manual/en/soapclient.gettypes.php).
+ * In non-WSDL mode, this method returns `null`.
+ *
+ * @return string[]|null
+ */
+ public function getTypes(): ?array
+ {
+ 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): string
+ {
+ 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(string $location): self
+ {
+ $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..a4d7912
--- /dev/null
+++ b/vendor/clue/soap-react/src/Protocol/ClientDecoder.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Clue\React\Soap\Protocol;
+
+/**
+ * @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(string $function, string $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..8b065b9
--- /dev/null
+++ b/vendor/clue/soap-react/src/Protocol/ClientEncoder.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Clue\React\Soap\Protocol;
+
+use Psr\Http\Message\RequestInterface;
+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 RequestInterface
+ * @throws \SoapFault if request is invalid according to WSDL
+ */
+ public function encode(string $name, array $args): RequestInterface
+ {
+ $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..9c1fd11
--- /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 Clue\React\Soap\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(string $name, array $args): PromiseInterface
+ {
+ return $this->client->soapCall($name, $args);
+ }
+}
diff --git a/vendor/clue/socket-raw/CHANGELOG.md b/vendor/clue/socket-raw/CHANGELOG.md
new file mode 100644
index 0000000..3a21b1b
--- /dev/null
+++ b/vendor/clue/socket-raw/CHANGELOG.md
@@ -0,0 +1,96 @@
+# Changelog
+
+## 1.6.0 (2022-04-14)
+
+* Feature: Forward compatibility with PHP 8.1 release.
+ (#67 and #68 by @clue)
+
+* Fix: Fix reporting refused connections on Windows.
+ (#69 by @clue)
+
+* Improve CI setup and documentation.
+ (#70 and #65 by @clue, #64 by @szepeviktor and #66 by @PaulRotmann)
+
+## 1.5.0 (2020-11-27)
+
+* Feature: Support PHP 8 and drop legacy HHVM support.
+ (#60 and #61 by @clue)
+
+* Improve test suite and add `.gitattributes` to exclude dev files from export.
+ Update to PHPUnit 9 and simplify test matrix.
+ (#50, #51, #58 and #63 by @clue and #57 by @SimonFrings)
+
+## 1.4.1 (2019-10-28)
+
+* Fix: Fix error reporting when invoking methods on closed socket instance.
+ (#48 by @clue)
+
+* Improve test suite to run tests on Windows via Travis CI.
+ (#49 by @clue)
+
+## 1.4.0 (2019-01-22)
+
+* Feature: Improve Windows support (async connections and Unix domain sockets).
+ (#43 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit 7 and PHPUnit 6.
+ (#42 by @clue)
+
+## 1.3.0 (2018-06-10)
+
+* Feature: Add `$timeout` parameter for `Factory::createClient()`
+ (#39 by @Elbandi and @clue)
+
+ ```php
+ // connect to Google, but wait no longer than 2.5s for connection
+ $socket = $factory->createClient('www.google.com:80', 2.5);
+ ```
+
+* Improve test suite by adding PHPUnit to require-dev,
+ update test suite to test against legacy PHP 5.3 through PHP 7.2 and
+ optionally skip functional integration tests requiring internet.
+ (#26 by @ascii-soup, #28, #29, #37 and #38 by @clue)
+
+## 1.2.0 (2015-03-18)
+
+* Feature: Expose optional `$type` parameter for `Socket::read()`
+ ([#16](https://github.com/clue/php-socket-raw/pull/16) by @Elbandi)
+
+## 1.1.0 (2014-10-24)
+
+* Feature: Accept float timeouts like `0.5` for `Socket::selectRead()` and `Socket::selectWrite()`.
+ ([#8](https://github.com/clue/php-socket-raw/issues/8))
+
+* Feature: Add new `Socket::connectTimeout()` method.
+ ([#11](https://github.com/clue/php-socket-raw/pull/11))
+
+* Fix: Close invalid socket resource when `Factory` fails to create a `Socket`.
+ ([#12](https://github.com/clue/php-socket-raw/pull/12))
+
+* Fix: Calling `accept()` on an idle server socket emits right error code and message.
+ ([#14](https://github.com/clue/php-socket-raw/pull/14))
+
+## 1.0.0 (2014-05-10)
+
+* Feature: Improved errors reporting through dedicated `Exception`
+ ([#6](https://github.com/clue/socket-raw/pull/6))
+* Feature: Support HHVM
+ ([#5](https://github.com/clue/socket-raw/pull/5))
+* Use PSR-4 layout
+ ([#3](https://github.com/clue/socket-raw/pull/3))
+* Continuous integration via Travis CI
+
+## 0.1.2 (2013-05-09)
+
+* Fix: The `Factory::createUdg()` now returns the right socket type.
+* Fix: Fix ICMPv6 addressing to not require square brackets because it does not
+ use ports.
+* Extended test suite.
+
+## 0.1.1 (2013-04-18)
+
+* Fix: Raw sockets now correctly report no port instead of a `0` port.
+
+## 0.1.0 (2013-04-10)
+
+* First tagged release
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/README.md b/vendor/clue/socket-raw/README.md
new file mode 100644
index 0000000..7ea79b3
--- /dev/null
+++ b/vendor/clue/socket-raw/README.md
@@ -0,0 +1,258 @@
+# clue/socket-raw
+
+[![CI status](https://github.com/clue/socket-raw/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/socket-raw/actions)
+[![installs on Packagist](https://img.shields.io/packagist/dt/clue/socket-raw?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/socket-raw)
+
+Simple and lightweight OOP wrapper for PHP's low-level sockets extension (ext-sockets).
+
+PHP offers two networking APIs, the newer [streams API](https://www.php.net/manual/en/book.stream.php) and the older [socket API](https://www.php.net/manual/en/ref.sockets.php).
+While the former has been a huge step forward in generalizing various streaming resources,
+it lacks some of the advanced features of the original and much more low-level socket API.
+This lightweight library exposes this socket API in a modern way by providing a thin wrapper around the underlying API.
+
+* **Full socket API** -
+ It exposes the whole [socket API](https://www.php.net/manual/en/ref.sockets.php) through a *sane* object-oriented interface.
+ Provides convenience methods for common operations as well as exposing all underlying methods and options.
+* **Fluent interface** -
+ Uses a fluent interface so you can easily chain method calls.
+ Error conditions will be signalled using `Exception`s instead of relying on cumbersome return codes.
+* **Lightweight, SOLID design** -
+ Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
+ and does not get in your way.
+ This library is merely a very thin wrapper and has no other external dependencies.
+* **Good test coverage** -
+ Comes with an automated test suite and is regularly tested in the *real world*.
+
+**Table of contents**
+
+* [Support us](#support-us)
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [Factory](#factory)
+ * [createClient()](#createclient)
+ * [createServer()](#createserver)
+ * [create*()](#create)
+ * [Socket](#socket)
+ * [Methods](#methods)
+ * [Data I/O](#data-io)
+ * [Unconnected I/O](#unconnected-io)
+ * [Non-blocking (async) I/O](#non-blocking-async-io)
+ * [Connection handling](#connection-handling)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Quickstart example
+
+Once [installed](#install), you can use the following example to send and receive HTTP messages:
+
+```php
+$factory = new \Socket\Raw\Factory();
+
+$socket = $factory->createClient('www.google.com:80');
+echo 'Connected to ' . $socket->getPeerName() . PHP_EOL;
+
+// send simple HTTP request to remote side
+$socket->write("GET / HTTP/1.1\r\n\Host: www.google.com\r\n\r\n");
+
+// receive and dump HTTP response
+var_dump($socket->read(8192));
+
+$socket->close();
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### Factory
+
+As shown in the [quickstart example](#quickstart-example), this library uses a `Factory` pattern
+as a simple API to [`socket_create()`](https://www.php.net/manual/en/function.socket-create.php).
+It provides simple access to creating TCP, UDP, UNIX, UDG and ICMP protocol sockets and supports both IPv4 and IPv6 addressing.
+
+```php
+$factory = new \Socket\Raw\Factory();
+```
+
+#### createClient()
+
+The `createClient(string $address, null|float $timeout): Socket` method is
+the most convenient method for creating connected client sockets
+(similar to how [`fsockopen()`](https://www.php.net/manual/en/function.fsockopen.php) or
+[`stream_socket_client()`](https://www.php.net/manual/en/function.stream-socket-client.php) work).
+
+```php
+// establish a TCP/IP stream connection socket to www.google.com on port 80
+$socket = $factory->createClient('tcp://www.google.com:80');
+
+// same as above, as scheme defaults to TCP
+$socket = $factory->createClient('www.google.com:80');
+
+// same as above, but wait no longer than 2.5s for connection
+$socket = $factory->createClient('www.google.com:80', 2.5);
+
+// create connectionless UDP/IP datagram socket connected to google's DNS
+$socket = $factory->createClient('udp://8.8.8.8:53');
+
+// establish TCP/IPv6 stream connection socket to localhost on port 1337
+$socket = $factory->createClient('tcp://[::1]:1337');
+
+// connect to local Unix stream socket path
+$socket = $factory->createClient('unix:///tmp/daemon.sock');
+
+// create Unix datagram socket
+$socket = $factory->createClient('udg:///tmp/udg.socket');
+
+// create a raw low-level ICMP socket (requires root!)
+$socket = $factory->createClient('icmp://192.168.0.1');
+```
+
+#### createServer()
+
+The `createServer($address)` method can be used to create a server side (listening) socket bound to specific address/path
+(similar to how [`stream_socket_server()`](https://www.php.net/manual/en/function.stream-socket-server.php) works).
+It accepts the same addressing scheme as the [`createClient()`](#createclient) method.
+
+```php
+// create a TCP/IP stream connection socket server on port 1337
+$socket = $factory->createServer('tcp://localhost:1337');
+
+// create a UDP/IPv6 datagram socket server on port 1337
+$socket = $factory->createServer('udp://[::1]:1337');
+```
+
+#### create*()
+
+Less commonly used, the `Factory` provides access to creating (unconnected) sockets for various socket types:
+
+```php
+$socket = $factory->createTcp4();
+$socket = $factory->createTcp6();
+
+$socket = $factory->createUdp4();
+$socket = $factory->createUdp6();
+
+$socket = $factory->createUnix();
+$socket = $factory->createUdg();
+
+$socket = $factory->createIcmp4();
+$socket = $factory->createIcmp6();
+```
+
+You can also create arbitrary socket protocol types through the underlying mechanism:
+
+```php
+$factory->create($family, $type, $protocol);
+```
+
+### Socket
+
+As discussed above, the `Socket` class is merely an object-oriented wrapper around a socket resource. As such, it helps if you're familar with socket programming in general.
+
+The recommended way to create a `Socket` instance is via the above [`Factory`](#factory).
+
+#### Methods
+
+All low-level socket operations are available as methods on the `Socket` class.
+
+You can refer to PHP's fairly good [socket API documentation](https://www.php.net/manual/en/ref.sockets.php) or the docblock comments in the [`Socket` class](src/Socket.php) to get you started.
+
+##### Data I/O:
+
+```
+$socket->write('data');
+$data = $socket->read(8192);
+```
+
+##### Unconnected I/O:
+
+```
+$socket->sendTo('data', $flags, $remote);
+$data = $socket->rcvFrom(8192, $flags, $remote);
+```
+
+##### Non-blocking (async) I/O:
+
+```
+$socket->setBlocking(false);
+$socket->selectRead();
+$socket->selectWrite();
+```
+
+##### Connection handling:
+
+```php
+$client = $socket->accept();
+$socket->bind($address);
+$socket->connect($address);
+$socket->shutdown();
+$socket->close();
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org/).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require clue/socket-raw:^1.6
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions besides `ext-sockets` and supports running on legacy PHP 5.3 through
+current PHP 8+.
+It's *highly recommended to use the latest supported PHP version* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org/):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ vendor/bin/phpunit
+```
+
+Note that the test suite contains tests for ICMP sockets which require root
+access on Unix/Linux systems. Therefor some tests will be skipped unless you run
+the following command to execute the full test suite:
+
+```bash
+$ sudo vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
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/CHANGELOG.md b/vendor/clue/socks-react/CHANGELOG.md
new file mode 100644
index 0000000..cd72e1d
--- /dev/null
+++ b/vendor/clue/socks-react/CHANGELOG.md
@@ -0,0 +1,481 @@
+# Changelog
+
+## 1.4.0 (2022-08-31)
+
+* Feature: Full support for PHP 8.1 and PHP 8.2.
+ (#105 by @clue and #108 by @SimonFrings)
+
+* Feature: Mark passwords and URIs as `#[\SensitiveParameter]` (PHP 8.2+).
+ (#109 by @SimonFrings)
+
+* Feature: Forward compatibility with upcoming Promise v3.
+ (#106 by @clue)
+
+* Bug: Fix invalid references in exception stack trace.
+ (#104 by @clue)
+
+* Improve test suite and fix legacy HHVM build.
+ (#107 by @SimonFrings)
+
+## 1.3.0 (2021-08-06)
+
+* Feature: Simplify usage by supporting new default loop and making `Connector` optional.
+ (#100 and #101 by @clue)
+
+ ```php
+ // old (still supported)
+ $proxy = new Clue\React\Socks\Client(
+ $url,
+ new React\Socket\Connector($loop)
+ );
+ $server = new Clue\React\Socks\Server($loop);
+ $server->listen(new React\Socket\Server('127.0.0.1:1080', $loop));
+
+ // new (using default loop)
+ $proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+ $socks = new Clue\React\Socks\Server();
+ $socks->listen(new React\Socket\SocketServer('127.0.0.1:1080'));
+ ```
+
+* Documentation improvements and updated examples.
+ (#98 and #102 by @clue and #99 by @PaulRotmann)
+
+* Improve test suite and use GitHub actions for continuous integration (CI).
+ (#97 by @SimonFrings)
+
+## 1.2.0 (2020-10-23)
+
+* Enhanced documentation for ReactPHP's new HTTP client.
+ (#95 by @SimonFrings)
+
+* Improve test suite, prepare PHP 8 support and support PHPUnit 9.3.
+ (#96 by @SimonFrings)
+
+## 1.1.0 (2020-06-19)
+
+* Feature / Fix: Support PHP 7.4 by skipping unneeded cleanup of exception trace args.
+ (#92 by @clue)
+
+* Clean up test suite and add `.gitattributes` to exclude dev files from exports.
+ Run tests on PHP 7.4, PHPUnit 9 and simplify test matrix.
+ Link to using SSH proxy (SSH tunnel) as an alternative.
+ (#88 by @clue and #91 and #93 by @SimonFrings)
+
+## 1.0.0 (2018-11-20)
+
+* First stable release, now following SemVer!
+
+* Feature / BC break: Unify SOCKS5 and SOCKS4(a) protocol version handling,
+ the `Client` now defaults to SOCKS5 instead of SOCKS4a,
+ remove explicit SOCKS4a handling and merge into SOCKS4 protocol handling and
+ URI scheme `socks5://` now only acts as an alias for default `socks://` scheme.
+ (#74, #81 and #87 by @clue)
+
+ ```php
+ // old: defaults to SOCKS4a
+ $client = new Client('127.0.0.1:1080', $connector);
+ $client = new Client('socks://127.0.0.1:1080', $connector);
+
+ // new: defaults to SOCKS5
+ $client = new Client('127.0.0.1:1080', $connector);
+ $client = new Client('socks://127.0.0.1:1080', $connector);
+
+ // new: explicitly use legacy SOCKS4(a)
+ $client = new Client('socks4://127.0.0.1:1080', $connector);
+
+ // unchanged: explicitly use SOCKS5
+ $client = new Client('socks5://127.0.0.1:1080', $connector);
+ ```
+
+* Feature / BC break: Clean up `Server` interface,
+ add `Server::listen()` method instead of accepting socket in constructor,
+ replace `Server::setAuth()` with optional constructor parameter,
+ remove undocumented "connection" event from Server and drop explicit Evenement dependency and
+ mark all classes as `final` and all internal APIs as `@internal`
+ (#78, #79, #80 and #84 by @clue)
+
+ ```php
+ // old: socket passed to server constructor
+ $socket = new React\Socket\Server(1080, $loop);
+ $server = new Clue\React\Socks\Server($loop, $socket);
+
+ // old: authentication via setAuthArray()/setAuth() methods
+ $server = new Clue\React\Socks\Server($loop, $socket);
+ $server->setAuthArray(array(
+ 'tom' => 'password',
+ 'admin' => 'root'
+ ));
+
+ // new: socket passed to listen() method
+ $server = new Clue\React\Socks\Server($loop);
+ $socket = new React\Socket\Server(1080, $loop);
+ $server->listen($socket);
+
+ // new: authentication passed to server constructor
+ $server = new Clue\React\Socks\Server($loop, null, array(
+ 'tom' => 'password',
+ 'admin' => 'root'
+ ));
+ $server->listen($socket);
+ ```
+
+* Feature: Improve error reporting for failed connections attempts by always including target URI in exceptions and
+ improve promise cancellation and clean up any garbage references.
+ (#82 and #83 by @clue)
+
+ All error messages now always contain a reference to the remote URI to give
+ more details which connection actually failed and the reason for this error.
+ Similarly, any underlying connection issues to the proxy server will now be
+ reported as part of the previous exception.
+
+ For most common use cases this means that simply reporting the `Exception`
+ message should give the most relevant details for any connection issues:
+
+ ```php
+ $promise = $proxy->connect('tcp://example.com:80');
+ $promise->then(function (ConnectionInterface $connection) {
+ // …
+ }, function (Exception $e) {
+ echo $e->getMessage();
+ });
+ ```
+
+* Improve documentation and examples, link to other projects and update project homepage.
+ (#73, #75 and #85 by @clue)
+
+## 0.8.7 (2017-12-17)
+
+* Feature: Support SOCKS over TLS (`sockss://` URI scheme)
+ (#70 and #71 by @clue)
+
+ ```php
+ // new: now supports SOCKS over TLS
+ $client = new Client('socks5s://localhost', $connector);
+ ```
+
+* Feature: Support communication over Unix domain sockets (UDS)
+ (#69 by @clue)
+
+ ```php
+ // new: now supports SOCKS over Unix domain sockets (UDS)
+ $client = new Client('socks5+unix:///tmp/proxy.sock', $connector);
+ ```
+
+* Improve test suite by adding forward compatibility with PHPUnit 6
+ (#68 by @clue)
+
+## 0.8.6 (2017-09-17)
+
+* Feature: Forward compatibility with Evenement v3.0
+ (#67 by @WyriHaximus)
+
+## 0.8.5 (2017-09-01)
+
+* Feature: Use socket error codes for connection rejections
+ (#63 by @clue)
+
+ ```php
+ $promise = $proxy->connect('imap.example.com:143');
+ $promise->then(null, function (Exeption $e) {
+ if ($e->getCode() === SOCKET_EACCES) {
+ echo 'Failed to authenticate with proxy!';
+ }
+ throw $e;
+ });
+ ```
+
+* Feature: Report matching SOCKS5 error codes for server side connection errors
+ (#62 by @clue)
+
+* Fix: Fix SOCKS5 client receiving destination hostnames and
+ fix IPv6 addresses as hostnames for TLS certificates
+ (#64 and #65 by @clue)
+
+* Improve test suite by locking Travis distro so new defaults will not break the build and
+ optionally exclude tests that rely on working internet connection
+ (#61 and #66 by @clue)
+
+## 0.8.4 (2017-07-27)
+
+* Feature: Server now passes client source address to Connector
+ (#60 by @clue)
+
+## 0.8.3 (2017-07-18)
+
+* Feature: Pass full remote URI as parameter to authentication callback
+ (#58 by @clue)
+
+ ```php
+ // new third parameter passed to authentication callback
+ $server->setAuth(function ($user, $pass, $remote) {
+ $ip = parse_url($remote, PHP_URL_HOST);
+
+ return ($ip === '127.0.0.1');
+ });
+ ```
+
+* Fix: Fix connecting to IPv6 address via SOCKS5 server and validate target
+ URI so hostname can not contain excessive URI components
+ (#59 by @clue)
+
+* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors
+ (#57 by @clue)
+
+## 0.8.2 (2017-05-09)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
+ (#56 by @clue)
+
+## 0.8.1 (2017-04-21)
+
+* Update examples to use URIs with default port 1080 and accept proxy URI arguments
+ (#54 by @clue)
+
+* Remove now unneeded dependency on `react/stream`
+ (#55 by @clue)
+
+## 0.8.0 (2017-04-18)
+
+* Feature: Merge `Server` class from clue/socks-server
+ (#52 by @clue)
+
+ ```php
+ $socket = new React\Socket\Server(1080, $loop);
+ $server = new Clue\React\Socks\Server($loop, $socket);
+ ```
+
+ > Upgrading from [clue/socks-server](https://github.com/clue/php-socks-server)?
+ The classes have been moved as-is, so you can simply start using the new
+ class name `Clue\React\Socks\Server` with no other changes required.
+
+## 0.7.0 (2017-04-14)
+
+* Feature / BC break: Replace depreacted SocketClient with Socket v0.7 and
+ use `connect($uri)` instead of `create($host, $port)`
+ (#51 by @clue)
+
+ ```php
+ // old
+ $connector = new React\SocketClient\TcpConnector($loop);
+ $client = new Client(1080, $connector);
+ $client->create('google.com', 80)->then(function (Stream $conn) {
+ $conn->write("…");
+ });
+
+ // new
+ $connector = new React\Socket\TcpConnector($loop);
+ $client = new Client(1080, $connector);
+ $client->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $conn->write("…");
+ });
+ ```
+
+* Improve test suite by adding PHPUnit to require-dev
+ (#50 by @clue)
+
+## 0.6.0 (2016-11-29)
+
+* Feature / BC break: Pass connector into `Client` instead of loop, remove unneeded deps
+ (#49 by @clue)
+
+ ```php
+ // old (connector is create implicitly)
+ $client = new Client('127.0.0.1', $loop);
+
+ // old (connector can optionally be passed)
+ $client = new Client('127.0.0.1', $loop, $connector);
+
+ // new (connector is now mandatory)
+ $connector = new React\SocketClient\TcpConnector($loop);
+ $client = new Client('127.0.0.1', $connector);
+ ```
+
+* Feature / BC break: `Client` now implements `ConnectorInterface`, remove `Connector` adapter
+ (#47 by @clue)
+
+ ```php
+ // old (explicit connector functions as an adapter)
+ $connector = $client->createConnector();
+ $promise = $connector->create('google.com', 80);
+
+ // new (client can be used as connector right away)
+ $promise = $client->create('google.com', 80);
+ ```
+
+* Feature / BC break: Remove `createSecureConnector()`, use `SecureConnector` instead
+ (#47 by @clue)
+
+ ```php
+ // old (tight coupling and hidden dependency)
+ $tls = $client->createSecureConnector();
+ $promise = $tls->create('google.com', 443);
+
+ // new (more explicit, loose coupling)
+ $tls = new React\SocketClient\SecureConnector($client, $loop);
+ $promise = $tls->create('google.com', 443);
+ ```
+
+* Feature / BC break: Remove `setResolveLocal()` and local DNS resolution and default to remote DNS resolution, use `DnsConnector` instead
+ (#44 by @clue)
+
+ ```php
+ // old (implicitly defaults to true, can be disabled)
+ $client->setResolveLocal(false);
+ $tcp = $client->createConnector();
+ $promise = $tcp->create('google.com', 80);
+
+ // new (always disabled, can be re-enabled like this)
+ $factory = new React\Dns\Resolver\Factory();
+ $resolver = $factory->createCached('8.8.8.8', $loop);
+ $tcp = new React\SocketClient\DnsConnector($client, $resolver);
+ $promise = $tcp->create('google.com', 80);
+ ```
+
+* Feature / BC break: Remove `setTimeout()`, use `TimeoutConnector` instead
+ (#45 by @clue)
+
+ ```php
+ // old (timeout only applies to TCP/IP connection)
+ $client = new Client('127.0.0.1', …);
+ $client->setTimeout(3.0);
+ $tcp = $client->createConnector();
+ $promise = $tcp->create('google.com', 80);
+
+ // new (timeout can be added to any layer)
+ $client = new Client('127.0.0.1', …);
+ $tcp = new React\SocketClient\TimeoutConnector($client, 3.0, $loop);
+ $promise = $tcp->create('google.com', 80);
+ ```
+
+* Feature / BC break: Remove `setProtocolVersion()` and `setAuth()` mutators, only support SOCKS URI for protocol version and authentication (immutable API)
+ (#46 by @clue)
+
+ ```php
+ // old (state can be mutated after instantiation)
+ $client = new Client('127.0.0.1', …);
+ $client->setProtocolVersion('5');
+ $client->setAuth('user', 'pass');
+
+ // new (immutable after construction, already supported as of v0.5.2 - now mandatory)
+ $client = new Client('socks5://user:pass@127.0.0.1', …);
+ ```
+
+## 0.5.2 (2016-11-25)
+
+* Feature: Apply protocol version and username/password auth from SOCKS URI
+ (#43 by @clue)
+
+ ```php
+ // explicitly use SOCKS5
+ $client = new Client('socks5://127.0.0.1', $loop);
+
+ // use authentication (automatically SOCKS5)
+ $client = new Client('user:pass@127.0.0.1', $loop);
+ ```
+
+* More explicit client examples, including proxy chaining
+ (#42 by @clue)
+
+## 0.5.1 (2016-11-21)
+
+* Feature: Support Promise cancellation
+ (#39 by @clue)
+
+ ```php
+ $promise = $connector->create($host, $port);
+
+ $promise->cancel();
+ ```
+
+* Feature: Timeout now cancels pending connection attempt
+ (#39, #22 by @clue)
+
+## 0.5.0 (2016-11-07)
+
+* Remove / BC break: Split off Server to clue/socks-server
+ (#35 by @clue)
+
+ > Upgrading? Check [clue/socks-server](https://github.com/clue/php-socks-server) for details.
+
+* Improve documentation and project structure
+
+## 0.4.0 (2016-03-19)
+
+* Feature: Support proper SSL/TLS connections with additional SSL context options
+ (#31, #33 by @clue)
+
+* Documentation for advanced Connector setups (bindto, multihop)
+ (#32 by @clue)
+
+## 0.3.0 (2015-06-20)
+
+* BC break / Feature: Client ctor now accepts a SOCKS server URI
+ ([#24](https://github.com/clue/php-socks-react/pull/24))
+
+ ```php
+ // old
+ $client = new Client($loop, 'localhost', 9050);
+
+ // new
+ $client = new Client('localhost:9050', $loop);
+ ```
+
+* Feature: Automatically assume default SOCKS port (1080) if not given explicitly
+ ([#26](https://github.com/clue/php-socks-react/pull/26))
+
+* Improve documentation and test suite
+
+## 0.2.1 (2014-11-13)
+
+* Support React PHP v0.4 (while preserving BC with React PHP v0.3)
+ ([#16](https://github.com/clue/php-socks-react/pull/16))
+
+* Improve examples and add first class support for HHVM
+ ([#15](https://github.com/clue/php-socks-react/pull/15) and [#17](https://github.com/clue/php-socks-react/pull/17))
+
+## 0.2.0 (2014-09-27)
+
+* BC break / Feature: Simplify constructors by making parameters optional.
+ ([#10](https://github.com/clue/php-socks-react/pull/10))
+
+ The `Factory` has been removed, you can now create instances of the `Client`
+ and `Server` yourself:
+
+ ```php
+ // old
+ $factory = new Factory($loop, $dns);
+ $client = $factory->createClient('localhost', 9050);
+ $server = $factory->createSever($socket);
+
+ // new
+ $client = new Client($loop, 'localhost', 9050);
+ $server = new Server($loop, $socket);
+ ```
+
+* BC break: Remove HTTP support and link to [clue/buzz-react](https://github.com/clue/php-buzz-react) instead.
+ ([#9](https://github.com/clue/php-socks-react/pull/9))
+
+ HTTP operates on a different layer than this low-level SOCKS library.
+ Removing this reduces the footprint of this library.
+
+ > Upgrading? Check the [README](https://github.com/clue/php-socks-react#http-requests) for details.
+
+* Fix: Refactored to support other, faster loops (libev/libevent)
+ ([#12](https://github.com/clue/php-socks-react/pull/12))
+
+* Explicitly list dependencies, clean up examples and extend test suite significantly
+
+## 0.1.0 (2014-05-19)
+
+* First stable release
+* Async SOCKS `Client` and `Server` implementation
+* Project was originally part of [clue/socks](https://github.com/clue/php-socks)
+ and was split off from its latest releave v0.4.0
+ ([#1](https://github.com/clue/reactphp-socks/issues/1))
+
+ > Upgrading from clue/socks v0.4.0? Use namespace `Clue\React\Socks` instead of `Socks` and you're ready to go!
+
+## 0.0.0 (2011-04-26)
+
+* Initial concept, originally tracked as part of
+ [clue/socks](https://github.com/clue/php-socks)
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/README.md b/vendor/clue/socks-react/README.md
new file mode 100644
index 0000000..c886a23
--- /dev/null
+++ b/vendor/clue/socks-react/README.md
@@ -0,0 +1,1116 @@
+# clue/reactphp-socks
+
+[![CI status](https://github.com/clue/reactphp-socks/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/reactphp-socks/actions)
+[![installs on Packagist](https://img.shields.io/packagist/dt/clue/socks-react?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/socks-react)
+
+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](https://reactphp.org/).
+
+The SOCKS proxy protocol family (SOCKS5, SOCKS4 and SOCKS4a) is commonly used to
+tunnel HTTP(S) traffic through an intermediary ("proxy"), to conceal the origin
+address (anonymity) or to circumvent address blocking (geoblocking). While many
+(public) SOCKS proxy servers often limit this to HTTP(S) port `80` and `443`
+only, this can technically be used to tunnel any TCP/IP-based protocol (HTTP,
+SMTP, IMAP etc.).
+This library provides a simple API to create these tunneled connections for you.
+Because it implements ReactPHP's standard
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
+it can simply be used in place of a normal connector.
+This makes it fairly simple to add SOCKS proxy support to pretty much any
+existing higher-level protocol implementation.
+Besides the client side, it also provides a simple SOCKS server implementation
+which allows you to build your own SOCKS proxy servers with custom business logic.
+
+* **Async execution of connections** -
+ Send any number of SOCKS requests in parallel and process their
+ responses as soon as results come in.
+ The Promise-based design provides a *sane* interface to working with out of
+ order responses and possible connection errors.
+* **Standard interfaces** -
+ Allows easy integration with existing higher-level components by implementing
+ ReactPHP's standard
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface).
+* **Lightweight, SOLID design** -
+ Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
+ and does not get in your way.
+ Builds on top of well-tested components and well-established concepts instead of reinventing the wheel.
+* **Good test coverage** -
+ Comes with an automated tests suite and is regularly tested against actual proxy servers in the wild.
+
+**Table of contents**
+
+* [Support us](#support-us)
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [Client](#client)
+ * [Plain TCP connections](#plain-tcp-connections)
+ * [Secure TLS connections](#secure-tls-connections)
+ * [HTTP requests](#http-requests)
+ * [Protocol version](#protocol-version)
+ * [DNS resolution](#dns-resolution)
+ * [Authentication](#authentication)
+ * [Proxy chaining](#proxy-chaining)
+ * [Connection timeout](#connection-timeout)
+ * [SOCKS over TLS](#socks-over-tls)
+ * [Unix domain sockets](#unix-domain-sockets)
+ * [Server](#server)
+ * [Server connector](#server-connector)
+ * [Authentication](#server-authentication)
+ * [Proxy chaining](#server-proxy-chaining)
+ * [SOCKS over TLS](#server-socks-over-tls)
+ * [Unix domain sockets](#server-unix-domain-sockets)
+* [Servers](#servers)
+ * [Using a PHP SOCKS server](#using-a-php-socks-server)
+ * [Using SSH as a SOCKS server](#using-ssh-as-a-socks-server)
+ * [Using the Tor (anonymity network) to tunnel SOCKS connections](#using-the-tor-anonymity-network-to-tunnel-socks-connections)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Quickstart example
+
+Once [installed](#install), you can use the following code to send a secure
+HTTPS request to google.com through a local SOCKS proxy server:
+
+```php
+<?php
+
+require __DIR__ . '/vendor/autoload.php';
+
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$browser = new React\Http\Browser($connector);
+
+$browser->get('https://google.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
+ var_dump($response->getHeaders(), (string) $response->getBody());
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+If you're not already running any other [SOCKS proxy server](#servers),
+you can use the following code to create a SOCKS
+proxy server listening for connections on `127.0.0.1:1080`:
+
+```php
+<?php
+
+require __DIR__ . '/vendor/autoload.php';
+
+// start a new SOCKS proxy server
+$socks = new Clue\React\Socks\Server();
+
+// listen on 127.0.0.1:1080
+$socket = new React\Socket\SocketServer('127.0.0.1:1080');
+$socks->listen($socket);
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### Client
+
+The `Client` is responsible for communication with your SOCKS server instance.
+
+Its constructor simply accepts a SOCKS proxy URI with the SOCKS proxy server address:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+```
+
+You can omit the port if you're using the default SOCKS port 1080:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1');
+```
+
+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(array(
+ 'dns' => '127.0.0.1',
+ 'tcp' => array(
+ 'bindto' => '192.168.10.1:0'
+ )
+));
+
+$proxy = new Clue\React\Socks\Client('my-socks-server.local:1080', $connector);
+```
+
+This is one of the two main classes in this package.
+Because it implements ReactPHP's standard
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
+it can simply be used in place of a normal connector.
+Accordingly, it provides only a single public method, the
+[`connect()`](https://github.com/reactphp/socket#connect) method.
+The `connect(string $uri): PromiseInterface<ConnectionInterface, Exception>`
+method can be used to establish a streaming connection.
+It returns a [Promise](https://github.com/reactphp/promise) which either
+fulfills with a [ConnectionInterface](https://github.com/reactphp/socket#connectioninterface)
+on success or rejects with an `Exception` on error.
+
+This makes it fairly simple to add SOCKS proxy support to pretty much any
+higher-level component:
+
+```diff
+- $acme = new AcmeApi($connector);
++ $proxy = new Clue\React\Socks\Client('127.0.0.1:1080', $connector);
++ $acme = new AcmeApi($proxy);
+```
+
+#### Plain TCP connections
+
+SOCKS proxies are most frequently used to issue HTTP(S) 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.
+As documented above, you can simply invoke its `connect()` method to establish
+a streaming plain TCP/IP connection and use any higher level protocol like so:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$proxy->connect('tcp://www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ echo 'connected to www.google.com:80';
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+You can either use the `Client` directly or you may want to wrap this connector
+in ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector):
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$connector->connect('tcp://www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ echo 'connected to www.google.com:80';
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+See also the [first example](examples).
+
+The `tcp://` scheme can also be omitted.
+Passing any other scheme will reject the promise.
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $connector->connect($uri);
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying TCP/IP
+connection to the SOCKS server and/or the SOCKS protocol negotiation and reject
+the resulting promise.
+
+#### Secure TLS connections
+
+This class can also be used if you want to establish a secure TLS connection
+(formerly known as SSL) between you and your destination, such as when using
+secure HTTPS to your destination site. You can simply wrap this connector in
+ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector):
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$connector->connect('tls://www.google.com:443')->then(function (React\Socket\ConnectionInterface $connection) {
+ // proceed with just the plain text data
+ // everything is encrypted/decrypted automatically
+ echo 'connected to SSL encrypted www.google.com';
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+See also the [second example](examples).
+
+Pending connection attempts can be cancelled by canceling its pending promise
+as usual.
+
+> Note how secure TLS connections are in fact entirely handled outside of
+ this SOCKS client implementation.
+
+You can optionally pass additional
+[SSL context options](http://php.net/manual/en/context.ssl.php)
+to the constructor like this:
+
+```php
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ ),
+ 'dns' => false
+));
+```
+
+#### HTTP requests
+
+This library also allows you to send
+[HTTP requests through a SOCKS proxy server](https://github.com/reactphp/http#socks-proxy).
+
+In order to send HTTP requests, you first have to add a dependency for
+[ReactPHP's async HTTP client](https://github.com/reactphp/http#client-usage).
+This allows you to send both plain HTTP and TLS-encrypted HTTPS requests like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$browser = new React\Http\Browser($connector);
+
+$browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
+ var_dump($response->getHeaders(), (string) $response->getBody());
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+See also [ReactPHP's HTTP client](https://github.com/reactphp/http#client-usage)
+and any of the [examples](examples) for more details.
+
+#### Protocol version
+
+This library supports the SOCKS5 and SOCKS4(a) protocol versions.
+It focuses on the most commonly used core feature of connecting to a destination
+host through the SOCKS proxy server. In this mode, a SOCKS proxy server acts as
+a generic proxy allowing higher level application protocols to work through it.
+
+<table>
+ <tr>
+ <th></th>
+ <th>SOCKS5</th>
+ <th>SOCKS4(a)</th>
+ </tr>
+ <tr>
+ <th>Protocol specification</th>
+ <td><a href="https://tools.ietf.org/html/rfc1928">RFC 1928</a></td>
+ <td>
+ <a href="https://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol">SOCKS4.protocol</a> /
+ <a href="https://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4A.protocol">SOCKS4A.protocol</a>
+ </td>
+ </tr>
+ <tr>
+ <th>Tunnel outgoing TCP/IP connections</th>
+ <td>✓</td>
+ <td>✓</td>
+ </tr>
+ <tr>
+ <th><a href="#dns-resolution">Remote DNS resolution</a></th>
+ <td>✓</td>
+ <td>✗ / ✓</td>
+ </tr>
+ <tr>
+ <th>IPv6 addresses</th>
+ <td>✓</td>
+ <td>✗</td>
+ </tr>
+ <tr>
+ <th><a href="#authentication">Username/Password authentication</a></th>
+ <td>✓ (as per <a href="https://tools.ietf.org/html/rfc1929">RFC 1929</a>)</td>
+ <td>✗</td>
+ </tr>
+ <tr>
+ <th>Handshake # roundtrips</th>
+ <td>2 (3 with authentication)</td>
+ <td>1</td>
+ </tr>
+ <tr>
+ <th>Handshake traffic<br />+ remote DNS</th>
+ <td><em>variable</em> (+ auth + IPv6)<br />+ hostname - 3</td>
+ <td>17 bytes<br />+ hostname + 1</td>
+ </tr>
+ <tr>
+ <th>Incoming BIND requests</th>
+ <td><em>not implemented</em></td>
+ <td><em>not implemented</em></td>
+ </tr>
+ <tr>
+ <th>UDP datagrams</th>
+ <td><em>not implemented</em></td>
+ <td>✗</td>
+ </tr>
+ <tr>
+ <th>GSSAPI authentication</th>
+ <td><em>not implemented</em></td>
+ <td>✗</td>
+ </tr>
+</table>
+
+By default, the `Client` communicates via SOCKS5 with the SOCKS server.
+This is done because SOCKS5 is the latest version from the SOCKS protocol family
+and generally has best support across other vendors.
+You can also omit the default `socks://` URI scheme. Similarly, the `socks5://`
+URI scheme acts as an alias for the default `socks://` URI scheme.
+
+```php
+// all three forms are equivalent
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+$proxy = new Clue\React\Socks\Client('socks://127.0.0.1:1080');
+$proxy = new Clue\React\Socks\Client('socks5://127.0.0.1:1080');
+```
+
+If want to explicitly set the protocol version to SOCKS4(a), you can use the URI
+scheme `socks4://` as part of the SOCKS URI:
+
+```php
+$proxy = new Clue\React\Socks\Client('socks4://127.0.0.1:1080');
+```
+
+#### DNS resolution
+
+By default, the `Client` does not perform any DNS resolution at all and simply
+forwards any hostname you're trying to connect to to the SOCKS server.
+The remote SOCKS server is thus responsible for looking up any hostnames via DNS
+(this default mode is thus called *remote DNS resolution*).
+As seen above, this mode is supported by the SOCKS5 and SOCKS4a protocols, but
+not the original SOCKS4 protocol, as the protocol lacks a way to communicate hostnames.
+
+On the other hand, all SOCKS protocol versions support sending destination IP
+addresses to the SOCKS server.
+In this mode you either have to stick to using IPs only (which is ofen unfeasable)
+or perform any DNS lookups locally and only transmit the resolved destination IPs
+(this mode is thus called *local DNS resolution*).
+
+The default *remote DNS resolution* is useful if your local `Client` either can
+not resolve target hostnames because it has no direct access to the internet or
+if it should not resolve target hostnames because its outgoing DNS traffic might
+be intercepted (in particular when using the
+[Tor network](#using-the-tor-anonymity-network-to-tunnel-socks-connections)).
+
+As noted above, the `Client` defaults to using remote DNS resolution.
+However, wrapping the `Client` in ReactPHP's
+[`Connector`](https://github.com/reactphp/socket#connector) actually
+performs local DNS resolution unless explicitly defined otherwise.
+Given that remote DNS resolution is assumed to be the preferred mode, all
+other examples explicitly disable DNS resolution like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+```
+
+If you want to explicitly use *local DNS resolution* (such as when explicitly
+using SOCKS4), you can use the following code:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+// set up Connector which uses Google's public DNS (8.8.8.8)
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => '8.8.8.8'
+));
+```
+
+See also the [fourth example](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise
+as usual.
+
+> Note how local DNS resolution is in fact entirely handled outside of this
+ SOCKS client implementation.
+
+#### Authentication
+
+This library supports username/password authentication for SOCKS5 servers as
+defined in [RFC 1929](https://tools.ietf.org/html/rfc1929).
+
+On the client side, simply pass your username and password to use for
+authentication (see below).
+For each further connection the client will merely send a flag to the server
+indicating authentication information is available.
+Only if the server requests authentication during the initial handshake,
+the actual authentication credentials will be transmitted to the server.
+
+Note that the password is transmitted in cleartext to the SOCKS proxy server,
+so this methods should not be used on a network where you have to worry about eavesdropping.
+
+You can simply pass the authentication information as part of the SOCKS URI:
+
+```php
+$proxy = new Clue\React\Socks\Client('alice:password@127.0.0.1:1080');
+```
+
+Note that both the username and password must be percent-encoded if they contain
+special characters:
+
+```php
+$user = 'he:llo';
+$pass = 'p@ss';
+$url = rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1:1080';
+
+$proxy = new Clue\React\Socks\Client($url);
+```
+
+> The authentication details will be transmitted in cleartext to the SOCKS proxy
+ server only if it requires username/password authentication.
+ If the authentication details are missing or not accepted by the remote SOCKS
+ proxy server, it is expected to reject each connection attempt with an
+ exception error code of `SOCKET_EACCES` (13).
+
+Authentication is only supported by protocol version 5 (SOCKS5),
+so passing authentication to the `Client` enforces communication with protocol
+version 5 and complains if you have explicitly set anything else:
+
+```php
+// throws InvalidArgumentException
+new Clue\React\Socks\Client('socks4://alice:password@127.0.0.1:1080');
+```
+
+#### Proxy chaining
+
+The `Client` is responsible for creating connections to the SOCKS server which
+then connects to the target host.
+
+```
+Client -> SocksServer -> TargetHost
+```
+
+Sometimes it may be required to establish outgoing connections via another SOCKS
+server.
+For example, this can be useful if you want to conceal your origin address.
+
+```
+Client -> MiddlemanSocksServer -> TargetSocksServer -> TargetHost
+```
+
+The `Client` uses any instance of the `ConnectorInterface` to establish
+outgoing connections.
+In order to connect through another SOCKS server, you can simply use another
+SOCKS connector from another SOCKS client like this:
+
+```php
+// https via the proxy chain "MiddlemanSocksServer -> TargetSocksServer -> TargetHost"
+// please note how the client uses TargetSocksServer (not MiddlemanSocksServer!),
+// which in turn then uses MiddlemanSocksServer.
+// this creates a TCP/IP connection to MiddlemanSocksServer, which then connects
+// to TargetSocksServer, which then connects to the TargetHost
+$middle = new Clue\React\Socks\Client('127.0.0.1:1080');
+$target = new Clue\React\Socks\Client('example.com:1080', $middle);
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $target,
+ 'dns' => false
+));
+
+$connector->connect('tls://www.google.com:443')->then(function (React\Socket\ConnectionInterface $connection) {
+ // …
+});
+```
+
+See also the [third example](examples).
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+Proxy chaining can happen on the server side and/or the client side:
+
+* If you ask your client to chain through multiple proxies, then each proxy
+ server does not really know anything about chaining at all.
+ This means that this is a client-only property.
+
+* If you ask your server to chain through another proxy, then your client does
+ not really know anything about chaining at all.
+ This means that this is a server-only property and not part of this class.
+ For example, you can find this in the below [`Server`](#server-proxy-chaining)
+ class or somewhat similar when you're using the
+ [Tor network](#using-the-tor-anonymity-network-to-tunnel-socks-connections).
+
+#### Connection timeout
+
+By default, the `Client` does not implement any timeouts for establishing remote
+connections.
+Your underlying operating system may impose limits on pending and/or idle TCP/IP
+connections, anywhere in a range of a few minutes to several hours.
+
+Many use cases require more control over the timeout and likely values much
+smaller, usually in the range of a few seconds only.
+
+You can use ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector)
+to decorate any given `ConnectorInterface` instance.
+It provides the same `connect()` method, but will automatically reject the
+underlying connection attempt if it takes too long:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false,
+ 'timeout' => 3.0
+));
+
+$connector->connect('tcp://google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise
+as usual.
+
+> Note how connection timeout is in fact entirely handled outside of this
+ SOCKS client implementation.
+
+#### SOCKS over TLS
+
+All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+This implies that you can also use [secure TLS connections](#secure-tls-connections)
+to transfer sensitive data across SOCKS proxy servers.
+This means that no eavesdropper nor the proxy server will be able to decrypt
+your data.
+
+However, the initial SOCKS communication between the client and the proxy is
+usually via an unencrypted, plain TCP/IP connection.
+This means that an eavesdropper may be able to see *where* you connect to and
+may also be able to see your [SOCKS authentication](#authentication) details
+in cleartext.
+
+As an alternative, you may establish a secure TLS connection to your SOCKS
+proxy before starting the initial SOCKS communication.
+This means that no eavesdroppper will be able to see the destination address
+you want to connect to or your [SOCKS authentication](#authentication) details.
+
+You can use the `sockss://` URI scheme or use an explicit
+[SOCKS protocol version](#protocol-version) like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('sockss://127.0.0.1:1080');
+
+$proxy = new Clue\React\Socks\Client('socks4s://127.0.0.1:1080');
+```
+
+See also [example 32](examples).
+
+Similarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('sockss://alice:password@127.0.0.1:1080');
+```
+
+> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
+ should be used instead. SOCKS over TLS is considered advanced usage and is
+ used very rarely in practice.
+ In particular, the SOCKS server has to accept secure TLS connections, see
+ also [Server SOCKS over TLS](#server-socks-over-tls) for more details.
+ Also, PHP does not support "double encryption" over a single connection.
+ This means that enabling [secure TLS connections](#secure-tls-connections)
+ over a communication channel that has been opened with SOCKS over TLS
+ may not be supported.
+
+> Note that the SOCKS protocol does not support the notion of TLS. The above
+ works reasonably well because TLS is only used for the connection between
+ client and proxy server and the SOCKS protocol data is otherwise identical.
+ This implies that this may also have only limited support for
+ [proxy chaining](#proxy-chaining) over multiple TLS paths.
+
+#### Unix domain sockets
+
+All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+In some advanced cases, it may be useful to let your SOCKS server listen on a
+Unix domain socket (UDS) path instead of a IP:port combination.
+For example, this allows you to rely on file system permissions instead of
+having to rely on explicit [authentication](#authentication).
+
+You can use the `socks+unix://` URI scheme or use an explicit
+[SOCKS protocol version](#protocol-version) like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('socks+unix:///tmp/proxy.sock');
+
+$proxy = new Clue\React\Socks\Client('socks4+unix:///tmp/proxy.sock');
+```
+
+Similarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('socks+unix://alice:password@/tmp/proxy.sock');
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
+ has limited support for this.
+ In particular, enabling [secure TLS](#secure-tls-connections) may not be
+ supported.
+
+> Note that the SOCKS protocol does not support the notion of UDS paths. The above
+ works reasonably well because UDS is only used for the connection between
+ client and proxy server and the path will not actually passed over the protocol.
+ This implies that this does also not support [proxy chaining](#proxy-chaining)
+ over multiple UDS paths.
+
+### Server
+
+The `Server` is responsible for accepting incoming communication from SOCKS clients
+and forwarding the requested connection to the target host.
+It supports the SOCKS5 and SOCKS4(a) protocol versions by default.
+You can start listening on an underlying TCP/IP socket server like this:
+
+```php
+$socks = new Clue\React\Socks\Server();
+
+// listen on 127.0.0.1:1080
+$socket = new React\Socket\SocketServer('127.0.0.1:1080');
+$socks->listen($socket);
+```
+
+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.
+
+Additionally, the `Server` constructor accepts optional parameters to explicitly
+configure the [connector](#server-connector) to use and to require
+[authentication](#server-authentication). For more details, read on...
+
+#### Server connector
+
+The `Server` uses an instance of ReactPHP's
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+to establish outgoing connections for each incoming connection request.
+
+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(array(
+ 'dns' => '127.0.0.1',
+ 'tcp' => array(
+ 'bindto' => '192.168.10.1:0'
+ )
+));
+
+$socks = new Clue\React\Socks\Server(null, $connector);
+```
+
+If you want to forward the outgoing connection through another SOCKS proxy, you
+may also pass a [`Client`](#client) instance as a connector, see also
+[server proxy chaining](#server-proxy-chaining) for more details.
+
+Internally, the `Server` uses ReactPHP's normal
+[`connect()`](https://github.com/reactphp/socket#connect) method, but
+it also passes the original client IP as the `?source={remote}` parameter.
+The `source` parameter contains the full remote URI, including the protocol
+and any authentication details, for example `socks://alice:password@1.2.3.4:5678`
+or `socks4://1.2.3.4:5678` for legacy SOCKS4(a).
+You can use this parameter for logging purposes or to restrict connection
+requests for certain clients by providing a custom implementation of the
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface).
+
+#### Server authentication
+
+By default, the `Server` does not require any authentication from the clients.
+You can enable authentication support so that clients need to pass a valid
+username and password before forwarding any connections.
+
+Setting authentication on the `Server` enforces each further connected client
+to use protocol version 5 (SOCKS5).
+If a client tries to use any other protocol version, does not send along
+authentication details or if authentication details can not be verified,
+the connection will be rejected.
+
+If you only want to accept static authentication details, you can simply pass an
+additional assoc array with your authentication details to the `Server` like this:
+
+```php
+$socks = new Clue\React\Socks\Server(null, null, array(
+ 'alice' => 'password',
+ 'bob' => 's3cret!1'
+));
+```
+
+See also [example #12](examples).
+
+If you want more control over authentication, you can pass an authenticator
+function that should return a `bool` value like this synchronous example:
+
+```php
+$socks = new Clue\React\Socks\Server(null, null, function ($username, $password, $remote) {
+ // $remote is a full URI à la socks://alice:password@192.168.1.1:1234
+ // or sockss://alice:password@192.168.1.1:1234 for SOCKS over TLS
+ // or may be null when remote is unknown (SOCKS over Unix Domain Sockets)
+ // useful for logging or extracting parts, such as the remote IP
+ $ip = parse_url($remote, PHP_URL_HOST);
+
+ return ($username === 'root' && $password === 'secret' && $ip === '127.0.0.1');
+});
+```
+
+Because your authentication mechanism might take some time to actually check the
+provided authentication credentials (like querying a remote database or webservice),
+the server also supports a [Promise](https://github.com/reactphp/promise)-based
+interface. While this might seem more complex at first, it actually provides a
+very powerful way of handling a large number of connections concurrently without
+ever blocking any connections. You can return a [Promise](https://github.com/reactphp/promise)
+from the authenticator function that will fulfill with a `bool` value like this
+async example:
+
+```php
+$socks = new Clue\React\Socks\Server(null, null, function ($username, $password) use ($db) {
+ // pseudo-code: query database for given authentication details
+ return $db->query(
+ 'SELECT 1 FROM users WHERE name = ? AND password = ?',
+ array($username, $password)
+ )->then(function (QueryResult $result) {
+ // ensure we find exactly one match in the database
+ return count($result->resultRows) === 1;
+ });
+});
+```
+
+#### Server proxy chaining
+
+The `Server` is responsible for creating connections to the target host.
+
+```
+Client -> SocksServer -> TargetHost
+```
+
+Sometimes it may be required to establish outgoing connections via another SOCKS
+server.
+For example, this can be useful if your target SOCKS server requires
+authentication, but your client does not support sending authentication
+information (e.g. like most webbrowser).
+
+```
+Client -> MiddlemanSocksServer -> TargetSocksServer -> TargetHost
+```
+
+The `Server` uses any instance of the `ConnectorInterface` to establish outgoing
+connections.
+In order to connect through another SOCKS server, you can simply use the
+[`Client`](#client) SOCKS connector from above.
+You can create a SOCKS `Client` instance like this:
+
+```php
+// set next SOCKS server example.com:1080 as target
+$proxy = new Clue\React\Socks\Client('alice:password@example.com:1080');
+
+// start a new server which forwards all connections to the other SOCKS server
+$socks = new Clue\React\Socks\Server(null, $proxy);
+
+// listen on 127.0.0.1:1080
+$socket = new React\Socket\SocketServer('127.0.0.1:1080');
+$socks->listen($socket);
+```
+
+See also [example #21](examples).
+
+Proxy chaining can happen on the server side and/or the client side:
+
+* If you ask your client to chain through multiple proxies, then each proxy
+ server does not really know anything about chaining at all.
+ This means that this is a client-only property and not part of this class.
+ For example, you can find this in the above [`Client`](#proxy-chaining) class.
+
+* If you ask your server to chain through another proxy, then your client does
+ not really know anything about chaining at all.
+ This means that this is a server-only property and can be implemented as above.
+
+#### Server SOCKS over TLS
+
+Both SOCKS5 and SOCKS4(a) protocol versions support forwarding TCP/IP based
+connections and higher level protocols.
+This implies that you can also use [secure TLS connections](#secure-tls-connections)
+to transfer sensitive data across SOCKS proxy servers.
+This means that no eavesdropper nor the proxy server will be able to decrypt
+your data.
+
+However, the initial SOCKS communication between the client and the proxy is
+usually via an unencrypted, plain TCP/IP connection.
+This means that an eavesdropper may be able to see *where* the client connects
+to and may also be able to see the [SOCKS authentication](#authentication)
+details in cleartext.
+
+As an alternative, you may listen for SOCKS over TLS connections so
+that the client has to establish a secure TLS connection to your SOCKS
+proxy before starting the initial SOCKS communication.
+This means that no eavesdroppper will be able to see the destination address
+the client wants to connect to or their [SOCKS authentication](#authentication)
+details.
+
+You can simply start your listening socket on the `tls://` URI scheme like this:
+
+```php
+$socks = new Clue\React\Socks\Server();
+
+// listen on tls://127.0.0.1:1080 with the given server certificate
+$socket = new React\Socket\SocketServer('tls://127.0.0.1:1080', array(
+ 'tls' => array(
+ 'local_cert' => __DIR__ . '/localhost.pem',
+ )
+));
+$socks->listen($socket);
+```
+
+See also [example 31](examples).
+
+> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
+ should be used instead. SOCKS over TLS is considered advanced usage and is
+ used very rarely in practice.
+
+> Note that the SOCKS protocol does not support the notion of TLS. The above
+ works reasonably well because TLS is only used for the connection between
+ client and proxy server and the SOCKS protocol data is otherwise identical.
+ This implies that this does also not support [proxy chaining](#server-proxy-chaining)
+ over multiple TLS paths.
+
+#### Server Unix domain sockets
+
+Both SOCKS5 and SOCKS4(a) protocol versions support forwarding TCP/IP based
+connections and higher level protocols.
+In some advanced cases, it may be useful to let your SOCKS server listen on a
+Unix domain socket (UDS) path instead of a IP:port combination.
+For example, this allows you to rely on file system permissions instead of
+having to rely on explicit [authentication](#server-authentication).
+
+You can simply start your listening socket on the `unix://` URI scheme like this:
+
+```php
+$socks = new Clue\React\Socks\Server();
+
+// listen on /tmp/proxy.sock
+$socket = new React\Socket\SocketServer('unix:///tmp/proxy.sock');
+$socks->listen($socket);
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and that
+ the SOCKS protocol does not support the notion of UDS paths. The above
+ works reasonably well because UDS is only used for the connection between
+ client and proxy server and the path will not actually passed over the protocol.
+ This implies that this does also not support [proxy chaining](#server-proxy-chaining)
+ over multiple UDS paths.
+
+## Servers
+
+### Using a PHP SOCKS server
+
+* If you're looking for an end-user SOCKS server daemon, you may want to use
+ [LeProxy](https://leproxy.org/) or [clue/psocksd](https://github.com/clue/psocksd).
+* If you're looking for a SOCKS server implementation, consider using
+ the above [`Server`](#server) class.
+
+### Using SSH as a SOCKS server
+
+If you already have an SSH server set up, you can easily use it as a SOCKS
+tunnel end point. On your client, simply start your SSH client and use
+the `-D <port>` option to start a local SOCKS server (quoting the man page:
+a `local "dynamic" application-level port forwarding`).
+
+You can start a local SOCKS server by creating a loopback connection to your
+local system if you already run an SSH daemon:
+
+```bash
+ssh -D 1080 localhost
+```
+
+Alternatively, you can start a local SOCKS server tunneling through a given
+remote host that runs an SSH daemon:
+
+```bash
+ssh -D 1080 example.com
+```
+
+Now you can simply use this SSH SOCKS server like this:
+
+```PHP
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$proxy->connect('tcp://www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+Note that the above will allow all users on the local system to connect over
+your SOCKS server without authentication which may or may not be what you need.
+As an alternative, recent OpenSSH client versions also support
+[Unix domain sockets](#unix-domain-sockets) (UDS) paths so that you can rely
+on Unix file system permissions instead:
+
+```bash
+ssh -D/tmp/proxy.sock example.com
+```
+
+Now you can simply use this SSH SOCKS server like this:
+
+```PHP
+$proxy = new Clue\React\Socks\Client('socks+unix:///tmp/proxy.sock');
+
+$proxy->connect('tcp://www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+> As an alternative to requiring this manual setup, you may also want to look
+ into using [clue/reactphp-ssh-proxy](https://github.com/clue/reactphp-ssh-proxy)
+ which automatically creates this SSH tunnel for you. It provides an implementation of the same
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ so that supporting either proxy protocol should be fairly trivial.
+
+### Using the Tor (anonymity network) to tunnel SOCKS connections
+
+The [Tor anonymity network](https://www.torproject.org/) client software is designed
+to encrypt your traffic and route it over a network of several nodes to conceal its origin.
+It presents a SOCKS5 and SOCKS4(a) interface on TCP port 9050 by default
+which allows you to tunnel any traffic through the anonymity network:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:9050');
+
+$proxy->connect('tcp://www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+In most common scenarios you probably want to stick to default
+[remote DNS resolution](#dns-resolution) and don't want your client to resolve the target hostnames,
+because you would leak DNS information to anybody observing your local traffic.
+Also, Tor provides hidden services through an `.onion` pseudo top-level domain
+which have to be resolved by Tor.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org/).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+composer require clue/socks-react:^1.4
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
+HHVM.
+It's *highly recommended to use the latest supported PHP version* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org/):
+
+```bash
+composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+vendor/bin/phpunit
+```
+
+The test suite contains a number of tests that rely on a working internet
+connection, alternatively you can also run it like this:
+
+```bash
+vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
+
+## More
+
+* If you want to learn more about how the
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ and its usual implementations look like, refer to the documentation of the
+ underlying [react/socket component](https://github.com/reactphp/socket).
+* If you want to learn more about processing streams of data, refer to the
+ documentation of the underlying
+ [react/stream](https://github.com/reactphp/stream) component.
+* As an alternative to a SOCKS5 / SOCKS4(a) proxy, you may also want to look into
+ using an HTTP CONNECT proxy instead.
+ You may want to use [clue/reactphp-http-proxy](https://github.com/clue/reactphp-http-proxy)
+ which also provides an implementation of the same
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ so that supporting either proxy protocol should be fairly trivial.
+* As an alternative to a SOCKS5 / SOCKS4(a) proxy, you may also want to look into
+ using an SSH proxy (SSH tunnel) instead.
+ You may want to use [clue/reactphp-ssh-proxy](https://github.com/clue/reactphp-ssh-proxy)
+ which also provides an implementation of the same
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ so that supporting either proxy protocol should be fairly trivial.
+* If you're dealing with public proxies, you'll likely have to work with mixed
+ quality and unreliable proxies. You may want to look into using
+ [clue/reactphp-connection-manager-extra](https://github.com/clue/reactphp-connection-manager-extra)
+ which allows retrying unreliable ones, implying connection timeouts,
+ concurrently working with multiple connectors and more.
+* If you're looking for an end-user SOCKS server daemon, you may want to use
+ [LeProxy](https://leproxy.org/) or [clue/psocksd](https://github.com/clue/psocksd).
diff --git a/vendor/clue/socks-react/composer.json b/vendor/clue/socks-react/composer.json
new file mode 100644
index 0000000..2a26a7a
--- /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": "^3 || ^2.1 || ^1.2",
+ "react/socket": "^1.12"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.5",
+ "clue/connection-manager-extra": "^1.3",
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
+ "react/event-loop": "^1.2",
+ "react/http": "^1.6"
+ },
+ "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..5645c25
--- /dev/null
+++ b/vendor/clue/socks-react/src/Client.php
@@ -0,0 +1,458 @@
+<?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(
+ #[\SensitiveParameter]
+ $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,
+ #[\SensitiveParameter]
+ $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 $ti => $one) {
+ if (isset($one['args'])) {
+ foreach ($one['args'] as $ai => $arg) {
+ if ($arg instanceof \Closure) {
+ $trace[$ti]['args'][$ai] = '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..ca45015
--- /dev/null
+++ b/vendor/clue/socks-react/src/Server.php
@@ -0,0 +1,416 @@
+<?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,
+ #[\SensitiveParameter]
+ $auth = null
+ ) {
+ if (\is_array($auth)) {
+ // wrap authentication array in authentication callback
+ $this->auth = function (
+ $username,
+ #[\SensitiveParameter]
+ $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,
+ #[\SensitiveParameter]
+ $password,
+ #[\SensitiveParameter]
+ $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 (
+ #[\SensitiveParameter]
+ $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/CHANGELOG.md b/vendor/clue/stdio-react/CHANGELOG.md
new file mode 100644
index 0000000..965d89b
--- /dev/null
+++ b/vendor/clue/stdio-react/CHANGELOG.md
@@ -0,0 +1,281 @@
+# Changelog
+
+## 2.6.0 (2022-03-18)
+
+* Feature: Full support for PHP 8.1 release.
+ (#101 by @cdosoftei)
+
+## 2.5.0 (2021-10-25)
+
+* Feature: Simplify usage by supporting new default loop.
+ (#99 by @clue)
+
+ ```php
+ // old (still supported)
+ $stdio = new Clue\React\Stdio\Stdio($loop);
+
+ // new (using default loop)
+ $stdio = new Clue\React\Stdio\Stdio();
+ ```
+
+* Improve code examples and documentation.
+ (#100 by @clue and #98 by @PaulRotmann)
+
+* Use GitHub actions for continuous integration (CI).
+ (#97 by @SimonFrings)
+
+## 2.4.0 (2020-11-20)
+
+* Fix: Refactor executing functional tests without ext-readline.
+ (#95 by @clue)
+
+* Improve test suite and add `.gitattributes` to exclude dev files from export.
+ Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
+ (#93 and #94 by @SimonFrings)
+
+## 2.3.0 (2019-08-28)
+
+* Feature: Emit audible/visible BELL signal when using a disabled function.
+ (#86 and #87 by @clue)
+
+ By default, this project will emit an audible/visible BELL signal when the user
+ tries to execute an otherwise disabled function, such as using the
+ <kbd>left</kbd> or <kbd>backspace</kbd> keys when already at the beginning of the line.
+
+* Deprecated: Deprecate `Readline` class and move all methods to `Stdio`.
+ (#84 by @clue)
+
+ ```php
+ // deprecated:
+ $stdio->getReadline()->setPrompt('> ');
+
+ // recommended alternative:
+ $stdio->setPrompt('> ');
+ ```
+
+* Fix: Fix closing to emit final `close` event and clean up all listeners.
+ (#88 by @clue)
+
+* Improve test suite to test against legacy PHP 5.3 through PHP 7.3 and support PHPUnit 7.
+ (#85 by @clue)
+
+## 2.2.0 (2018-09-03)
+
+* Feature / Fix: Accept CR as an alias for LF to support more platforms.
+ (#79 by @clue)
+
+ The <kbd>enter</kbd> key will usually end the line with a `\n` (LF)
+ character on most Unix platforms. Common terminals also accept the
+ <kbd>^M</kbd> (CR) key in place of the <kbd>^J</kbd> (LF) key.
+
+ By now allowing CR as an alias for LF in this library, we can significantly
+ improve compatibility with this common usage pattern and improve platform
+ support. In particular, some platforms use different TTY settings (`icrnl`,
+ `igncr` and family) and depending on these settings emit different EOL
+ characters. This fixes issues where <kbd>enter</kbd> was not properly
+ detected when using `ext-readline` on Mac OS X, Android and others.
+
+* Fix: Fix and simplify restoring TTY mode when `ext-readline` is not in use.
+ (#74 and #78 by @clue)
+
+* Update project homepage, minor code style improvements and sort dependencies.
+ (#72 and #81 by @clue and #75 by @localheinz)
+
+## 2.1.0 (2018-02-05)
+
+* Feature: Add support for binding custom functions to any key code.
+ (#70 by @clue)
+
+ ```php
+ $readline->on('?', function () use ($stdio) {
+ $stdio->write('Do you need help?');
+ });
+ ```
+
+* Feature: Add `addInput()` helper method.
+ (#69 by @clue)
+
+ ```php
+ $readline->addInput('hello');
+ $readline->addInput(' world');
+ ```
+
+## 2.0.0 (2018-01-24)
+
+A major compatibility release to update this package to support all latest
+ReactPHP components!
+
+This update involves a minor BC break due to dropped support for legacy
+versions. We've tried hard to avoid BC breaks where possible and minimize impact
+otherwise. We expect that most consumers of this package will actually not be
+affected by any BC breaks, see below for more details.
+
+* BC break: Remove all deprecated APIs (individual `Stdin`, `Stdout`, `line` etc.)
+ (#64 and #68 by @clue)
+
+ > All of this affects only what is considered "advanced usage".
+ If you're affected by this BC break, then it's recommended to first
+ update to the intermediary v1.2.0 release, which provides alternatives
+ to all deprecated APIs and then update to this version without causing a
+ BC break.
+
+* Feature / BC break: Consistently emit incoming "data" event with trailing newline
+ unless stream ends without trailing newline (such as when piping).
+ (#65 by @clue)
+
+* Feature: Forward compatibility with upcoming Stream v1.0 and EventLoop v1.0
+ and avoid blocking when `STDOUT` buffer is full.
+ (#68 by @clue)
+
+## 1.2.0 (2017-12-18)
+
+* Feature: Optionally use `ext-readline` to enable raw input mode if available.
+ This extension is entirely optional, but it is more efficient and reliable
+ than spawning the external `stty` command.
+ (#63 by @clue)
+
+* Feature: Consistently return boolean success from `write()` and
+ avoid sending unneeded control codes between writes.
+ (#60 by @clue)
+
+* Deprecated: Deprecate input helpers and output helpers and
+ recommend using `Stdio` as a normal `DuplexStreamInterface` instead.
+ (#59 and #62 by @clue)
+
+ ```php
+ // deprecated
+ $stdio->on('line', function ($line) use ($stdio) {
+ $stdio->writeln("input: $line");
+ });
+
+ // recommended alternative
+ $stdio->on('data', function ($line) use ($stdio) {
+ $stdio->write("input: $line");
+ });
+ ```
+
+* Improve test suite by adding forward compatibility with PHPUnit 6.
+ (#61 by @carusogabriel)
+
+## 1.1.0 (2017-11-01)
+
+* Feature: Explicitly end stream on CTRL+D and emit final data on EOF without EOL.
+ (#56 by @clue)
+
+* Feature: Support running on non-TTY and closing STDIN and STDOUT streams.
+ (#57 by @clue)
+
+* Feature / Fix: Restore blocking mode before closing and restore TTY mode on unclean shutdown.
+ (#58 by @clue)
+
+* Improve documentation to detail why async console I/O is not supported on Microsoft Windows.
+ (#54 by @clue)
+
+* Improve test suite by adding PHPUnit to require-dev,
+ fix HHVM build for now again and ignore future HHVM build errors and
+ lock Travis distro so future defaults will not break the build.
+ (#46, #48 and #52 by @clue)
+
+## 1.0.0 (2017-01-08)
+
+* First stable release, now following SemVer.
+
+> Contains no other changes, so it's actually fully compatible with the v0.5.0 release.
+
+## 0.5.0 (2017-01-08)
+
+* Feature: Add history support.
+ (#40 by @clue)
+
+* Feature: Add autocomplete support.
+ (#41 by @clue)
+
+* Feature: Suggest using ext-mbstring, otherwise use regex fallback.
+ (#42 by @clue)
+
+* Remove / BC break: Remove undocumented and low quality skeletons/helpers for
+ `Buffer`, `ProgressBar` and `Spinner` (mostly dead code).
+ (#39, #43 by @clue)
+
+* First class support for PHP 5.3 through PHP 7 and HHVM.
+ (#44 by @clue)
+
+* Simplify and restructure examples.
+ (#45 by @clue)
+
+## 0.4.0 (2016-09-27)
+
+* Feature / BC break: The `Stdio` is now a well-behaving duplex stream.
+ (#35 by @clue)
+
+* Feature / BC break: The `Readline` is now a well-behaving readable stream.
+ (#32 by @clue)
+
+* Feature: Add `Readline::getPrompt()` helper.
+ (#33 by @clue)
+
+* Feature / Fix: All unsupported special keys, key codes and byte sequences will now be ignored.
+ (#36, #30, #19, #38 by @clue)
+
+* Fix: Explicitly redraw prompt on empty input.
+ (#37 by @clue)
+
+* Fix: Writing data that contains multiple newlines will no longer end up garbled.
+ (#34, #35 by @clue)
+
+## 0.3.1 (2015-11-26)
+
+* Fix: Support calling `Readline::setInput()` during `line` event.
+ (#28)
+
+ ```php
+ $stdio->on('line', function ($line) use ($stdio) {
+ $stdio->getReadline()->setInput($line . '!');
+ });
+ ```
+
+## 0.3.0 (2015-05-18)
+
+* Feature: Support multi-byte UTF-8 characters and account for cell width.
+ (#20)
+
+* Feature: Add support for HOME and END keys.
+ (#22)
+
+* Fix: Clear readline input and restore TTY on end.
+ (#21)
+
+## 0.2.0 (2015-05-17)
+
+* Feature: Support echo replacements (asterisk for password prompts).
+ (#11)
+
+ ```php
+ $stdio->getReadline()->setEcho('*');
+ ```
+
+* Feature: Add accessors for text input buffer and current cursor position.
+ (#8 and #9)
+
+ ```php
+ $stdio->getReadline()->setInput('hello');
+ $stdio->getReadline()->getCursorPosition();
+ ```
+
+* Feature: All setters now return self to allow easy method chaining.
+ (#7)
+
+ ```php
+ $stdio->getReadline()->setPrompt('Password: ')->setEcho('*')->setInput('secret');
+ ```
+
+* Feature: Only redraw() readline when necessary.
+ (#10 and #14)
+
+## 0.1.0 (2014-09-08)
+
+* First tagged release.
+
+## 0.0.0 (2013-08-21)
+
+* Initial concept.
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/README.md b/vendor/clue/stdio-react/README.md
new file mode 100644
index 0000000..624ded0
--- /dev/null
+++ b/vendor/clue/stdio-react/README.md
@@ -0,0 +1,708 @@
+# clue/reactphp-stdio
+
+[![CI status](https://github.com/clue/reactphp-stdio/workflows/CI/badge.svg)](https://github.com/clue/reactphp-stdio/actions)
+[![installs on Packagist](https://img.shields.io/packagist/dt/clue/stdio-react?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/stdio-react)
+
+Async, event-driven and UTF-8 aware console input & output (STDIN, STDOUT) for
+truly interactive CLI applications, built on top of [ReactPHP](https://reactphp.org/).
+
+You can use this library to build truly interactive and responsive command
+line (CLI) applications, that immediately react when the user types in
+a line or hits a certain key. Inspired by `ext-readline`, but supports UTF-8
+and interleaved I/O (typing while output is being printed), history and
+autocomplete support and takes care of proper TTY settings under the hood
+without requiring any extensions or special installation.
+
+**Table of contents**
+
+* [Support us](#support-us)
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [Stdio](#stdio)
+ * [Output](#output)
+ * [Input](#input)
+ * [Prompt](#prompt)
+ * [Echo](#echo)
+ * [Input buffer](#input-buffer)
+ * [Cursor](#cursor)
+ * [History](#history)
+ * [Autocomplete](#autocomplete)
+ * [Keys](#keys)
+ * [Bell](#bell)
+ * [~~Readline~~](#readline)
+* [Pitfalls](#pitfalls)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Quickstart example
+
+Once [installed](#install), you can use the following code to present a prompt in a CLI program:
+
+```php
+<?php
+
+require __DIR__ . '/vendor/autoload.php';
+
+$stdio = new Clue\React\Stdio\Stdio();
+$stdio->setPrompt('Input > ');
+
+$stdio->on('data', function ($line) use ($stdio) {
+ $line = rtrim($line, "\r\n");
+ $stdio->write('Your input: ' . $line . PHP_EOL);
+
+ if ($line === 'quit') {
+ $stdio->end();
+ }
+});
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### Stdio
+
+The `Stdio` is the main interface to this library.
+It is responsible for orchestrating the input and output streams
+by registering and forwarding the corresponding events.
+
+```php
+$stdio = new Clue\React\Stdio\Stdio();
+```
+
+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.
+
+See below for waiting for user input and writing output.
+The `Stdio` class is a well-behaving duplex stream
+(implementing ReactPHP's `DuplexStreamInterface`) that emits each complete
+line as a `data` event, including the trailing newline.
+
+#### Output
+
+The `Stdio` is a well-behaving writable stream
+implementing ReactPHP's `WritableStreamInterface`.
+
+The `write($text)` method can be used to print the given text characters to console output.
+This is useful if you need more control or want to output individual bytes or binary output:
+
+```php
+$stdio->write('hello');
+$stdio->write(" world\n");
+```
+
+Because the `Stdio` is a well-behaving writable stream,
+you can also `pipe()` any readable stream into this stream.
+
+```php
+$logger->pipe($stdio);
+```
+
+#### Input
+
+The `Stdio` is a well-behaving readable stream
+implementing ReactPHP's `ReadableStreamInterface`.
+
+It will emit a `data` event for every line read from console input.
+The event will contain the input buffer as-is, including the trailing newline.
+You can register any number of event handlers like this:
+
+```php
+$stdio->on('data', function ($line) {
+ if ($line === "start\n") {
+ doSomething();
+ }
+});
+```
+
+Note that this class takes care of buffering incomplete lines and will only emit
+complete lines.
+This means that the line will usually end with the trailing newline character.
+If the stream ends without a trailing newline character, it will not be present
+in the `data` event.
+As such, it's usually recommended to remove the trailing newline character
+before processing command line input like this:
+
+```php
+$stdio->on('data', function ($line) {
+ $line = rtrim($line, "\r\n");
+ if ($line === "start") {
+ doSomething();
+ }
+});
+```
+
+Similarly, if you copy and paste a larger chunk of text, it will properly emit
+multiple complete lines with a separate `data` event for each line.
+
+Because the `Stdio` is a well-behaving readable stream that will emit incoming
+data as-is, you can also use this to `pipe()` this stream into other writable
+streams.
+
+```php
+$stdio->pipe($logger);
+```
+
+You can control various aspects of the console input through this interface,
+so read on..
+
+#### Prompt
+
+The *prompt* will be written at the beginning of the *user input line*, right before the *user input buffer*.
+
+The `setPrompt($prompt)` method can be used to change the input prompt.
+The prompt will be printed to the *user input line* as-is, so you will likely want to end this with a space:
+
+```php
+$stdio->setPrompt('Input: ');
+```
+
+The default input prompt is empty, i.e. the *user input line* contains only the actual *user input buffer*.
+You can restore this behavior by passing an empty prompt:
+
+```php
+$stdio->setPrompt('');
+```
+
+The `getPrompt()` method can be used to get the current input prompt.
+It will return an empty string unless you've set anything else:
+
+```php
+assert($stdio->getPrompt() === '');
+```
+
+#### Echo
+
+The *echo mode* controls how the actual *user input buffer* will be presented in the *user input line*.
+
+The `setEcho($echo)` method can be used to control the echo mode.
+The default is to print the *user input buffer* as-is.
+
+You can disable printing the *user input buffer*, e.g. for password prompts.
+The user will still be able to type, but will not receive any indication of the current *user input buffer*.
+Please note that this often leads to a bad user experience as users will not even see their cursor position.
+Simply pass a boolean `false` like this:
+
+```php
+$stdio->setEcho(false);
+```
+
+Alternatively, you can also *hide* the *user input buffer* by using a replacement character.
+One replacement character will be printed for each character in the *user input buffer*.
+This is useful for password prompts to give users an indicatation that their key presses are registered.
+This often provides a better user experience and allows users to still control their cursor position.
+Simply pass a string replacement character likes this:
+
+```php
+$stdio->setEcho('*');
+```
+
+To restore the original behavior where every character appears as-is, simply pass a boolean `true`:
+
+```php
+$stdio->setEcho(true);
+```
+
+#### Input buffer
+
+Everything the user types will be buffered in the current *user input buffer*.
+Once the user hits enter, the *user input buffer* will be processed and cleared.
+
+The `addInput($input)` method can be used to add text to the *user input
+buffer* at the current cursor position.
+The given text will be inserted just like the user would type in a text and as
+such adjusts the current cursor position accordingly.
+The user will be able to delete and/or rewrite the buffer at any time.
+Changing the *user input buffer* can be useful for presenting a preset input to
+the user (like the last password attempt).
+Simply pass an input string like this:
+
+```php
+$stdio->addInput('hello');
+```
+
+The `setInput($buffer)` method can be used to control the *user input buffer*.
+The given text will be used to replace the entire current *user input buffer*
+and as such adjusts the current cursor position to the end of the new buffer.
+The user will be able to delete and/or rewrite the buffer at any time.
+Changing the *user input buffer* can be useful for presenting a preset input to
+the user (like the last password attempt).
+Simply pass an input string like this:
+
+```php
+$stdio->setInput('lastpass');
+```
+
+The `getInput()` method can be used to access the current *user input buffer*.
+This can be useful if you want to append some input behind the current *user input buffer*.
+You can simply access the buffer like this:
+
+```php
+$buffer = $stdio->getInput();
+```
+
+#### Cursor
+
+By default, users can control their (horizontal) cursor position by using their arrow keys on the keyboard.
+Also, every character pressed on the keyboard advances the cursor position.
+
+The `setMove($toggle)` method can be used to control whether users are allowed to use their arrow keys.
+To disable the left and right arrow keys, simply pass a boolean `false` like this:
+
+```php
+$stdio->setMove(false);
+```
+
+To restore the default behavior where the user can use the left and right arrow keys,
+simply pass a boolean `true` like this:
+
+```php
+$stdio->setMove(true);
+```
+
+The `getCursorPosition()` method can be used to access the current cursor position,
+measured in number of characters.
+This can be useful if you want to get a substring of the current *user input buffer*.
+Simply invoke it like this:
+
+```php
+$position = $stdio->getCursorPosition();
+```
+
+The `getCursorCell()` method can be used to get the current cursor position,
+measured in number of monospace cells.
+Most *normal* characters (plain ASCII and most multi-byte UTF-8 sequences) take a single monospace cell.
+However, there are a number of characters that have no visual representation
+(and do not take a cell at all) or characters that do not fit within a single
+cell (like some Asian glyphs).
+This method is mostly useful for calculating the visual cursor position on screen,
+but you may also invoke it like this:
+
+```php
+$cell = $stdio->getCursorCell();
+```
+
+The `moveCursorTo($position)` method can be used to set the current cursor position to the given absolute character position.
+For example, to move the cursor to the beginning of the *user input buffer*, simply call:
+
+```php
+$stdio->moveCursorTo(0);
+```
+
+The `moveCursorBy($offset)` method can be used to change the cursor position
+by the given number of characters relative to the current position.
+A positive number will move the cursor to the right - a negative number will move the cursor to the left.
+For example, to move the cursor one character to the left, simply call:
+
+```php
+$stdio->moveCursorBy(-1);
+```
+
+#### History
+
+By default, users can access the history of previous commands by using their
+UP and DOWN cursor keys on the keyboard.
+The history will start with an empty state, thus this feature is effectively
+disabled, as the UP and DOWN cursor keys have no function then.
+
+Note that the history is not maintained automatically.
+Any input the user submits by hitting enter will *not* be added to the history
+automatically.
+This may seem inconvenient at first, but it actually gives you more control over
+what (and when) lines should be added to the history.
+If you want to automatically add everything from the user input to the history,
+you may want to use something like this:
+
+```php
+$stdio->on('data', function ($line) use ($stdio) {
+ $line = rtrim($line);
+ $all = $stdio->listHistory();
+
+ // skip empty line and duplicate of previous line
+ if ($line !== '' && $line !== end($all)) {
+ $stdio->addHistory($line);
+ }
+});
+```
+
+The `listHistory(): string[]` method can be used to
+return an array with all lines in the history.
+This will be an empty array until you add new entries via `addHistory()`.
+
+```php
+$list = $stdio->listHistory();
+
+assert(count($list) === 0);
+```
+
+The `addHistory(string $line): void` method can be used to
+add a new line to the (bottom position of the) history list.
+A following `listHistory()` call will return this line as the last element.
+
+```php
+$stdio->addHistory('a');
+$stdio->addHistory('b');
+
+$list = $stdio->listHistory();
+assert($list === array('a', 'b'));
+```
+
+The `clearHistory(): void` method can be used to
+clear the complete history list.
+A following `listHistory()` call will return an empty array until you add new
+entries via `addHistory()` again.
+Note that the history feature will effectively be disabled if the history is
+empty, as the UP and DOWN cursor keys have no function then.
+
+```php
+$stdio->clearHistory();
+
+$list = $stdio->listHistory();
+assert(count($list) === 0);
+```
+
+The `limitHistory(?int $limit): void` method can be used to
+set a limit of history lines to keep in memory.
+By default, only the last 500 lines will be kept in memory and everything else
+will be discarded.
+You can use an integer value to limit this to the given number of entries or
+use `null` for an unlimited number (not recommended, because everything is
+kept in RAM).
+If you set the limit to `0` (int zero), the history will effectively be
+disabled, as no lines can be added to or returned from the history list.
+If you're building a CLI application, you may also want to use something like
+this to obey the `HISTSIZE` environment variable:
+
+```php
+$limit = getenv('HISTSIZE');
+if ($limit === '' || $limit < 0) {
+ // empty string or negative value means unlimited
+ $stdio->limitHistory(null);
+} elseif ($limit !== false) {
+ // apply any other value if given
+ $stdio->limitHistory($limit);
+}
+```
+
+There is no such thing as a `readHistory()` or `writeHistory()` method
+because filesystem operations are inherently blocking and thus beyond the scope
+of this library.
+Using your favorite filesystem API and an appropriate number of `addHistory()`
+or a single `listHistory()` call respectively should be fairly straight
+forward and is left up as an exercise for the reader of this documentation
+(i.e. *you*).
+
+#### Autocomplete
+
+By default, users can use autocompletion by using their TAB keys on the keyboard.
+The autocomplete function is not registered by default, thus this feature is
+effectively disabled, as the TAB key has no function then.
+
+The `setAutocomplete(?callable $autocomplete): void` method can be used to
+register a new autocomplete handler.
+In its most simple form, you won't need to assign any arguments and can simply
+return an array of possible word matches from a callable like this:
+
+```php
+$stdio->setAutocomplete(function () {
+ return array(
+ 'exit',
+ 'echo',
+ 'help',
+ );
+});
+```
+
+If the user types `he [TAB]`, the first two matches will be skipped as they do
+not match the current word prefix and the last one will be picked automatically,
+so that the resulting input buffer is `hello `.
+
+If the user types `e [TAB]`, then this will match multiple entries and the user
+will be presented with a list of up to 8 available word completions to choose
+from like this:
+
+```php
+> e [TAB]
+exit echo
+> e
+```
+
+Unless otherwise specified, the matches will be performed against the current
+word boundaries in the input buffer.
+This means that if the user types `hello [SPACE] ex [TAB]`, then the resulting
+input buffer is `hello exit `, which may or may not be what you need depending
+on your particular use case.
+
+In order to give you more control over this behavior, the autocomplete function
+actually receives three arguments (similar to `ext-readline`'s
+[`readline_completion_function()`](https://www.php.net/manual/en/function.readline-completion-function.php)):
+The first argument will be the current incomplete word according to current
+cursor position and word boundaries, while the second and third argument will be
+the start and end offset of this word within the complete input buffer measured
+in (Unicode) characters.
+The above examples will be invoked as `$fn('he', 0, 2)`, `$fn('e', 0, 1)` and
+`$fn('ex', 6, 8)` respectively.
+You may want to use this as an `$offset` argument to check if the current word
+is an argument or a root command and the `$word` argument to autocomplete
+partial filename matches like this:
+
+```php
+$stdio->setAutocomplete(function ($word, $offset) {
+ if ($offset <= 1) {
+ // autocomplete root commands at offset=0/1 only
+ return array('cat', 'rm', 'stat');
+ } else {
+ // autocomplete all command arguments as glob pattern
+ return glob($word . '*', GLOB_MARK);
+ }
+});
+```
+
+> Note that the user may also use quotes and/or leading whitespace around the
+root command, for example `"hell [TAB]`, in which case the offset will be
+advanced such as this will be invoked as `$fn('hell', 1, 4)`.
+Unless you use a more sophisticated argument parser, a decent approximation may
+be using `$offset <= 1` to check this is a root command.
+
+If you need even more control over autocompletion, you may also want to access
+and/or manipulate the [input buffer](#input-buffer) and [cursor](#cursor)
+directly like this:
+
+```php
+$stdio->setAutocomplete(function () use ($stdio) {
+ if ($stdio->getInput() === 'run') {
+ $stdio->setInput('run --test --value=42');
+ $stdio->moveCursorBy(-2);
+ }
+
+ // return empty array so normal autocompletion doesn't kick in
+ return array();
+});
+```
+
+You can use a `null` value to remove the autocomplete function again and thus
+disable the autocomplete function:
+
+```php
+$stdio->setAutocomplete(null);
+```
+
+#### Keys
+
+The `Readline` class is responsible for reading user input from `STDIN` and
+registering appropriate key events.
+By default, `Readline` uses a hard-coded key mapping that resembles the one
+usually found in common terminals.
+This means that normal Unicode character keys ("a" and "b", but also "?", "ä",
+"µ" etc.) will be processed as user input, while special control keys can be
+used for [cursor movement](#cursor), [history](#history) and
+[autocomplete](#autocomplete) functions.
+Unknown special keys will be ignored and will not processed as part of the user
+input by default.
+
+Additionally, you can bind custom functions to any key code you want.
+If a custom function is bound to a certain key code, the default behavior will
+no longer trigger.
+This allows you to register entirely new functions to keys or to overwrite any
+of the existing behavior.
+
+For example, you can use the following code to print some help text when the
+user hits a certain key:
+
+```php
+$stdio->on('?', function () use ($stdio) {
+ $stdio->write('Here\'s some help: …' . PHP_EOL);
+});
+```
+
+Similarly, this can be used to manipulate the user input and replace some of the
+input when the user hits a certain key:
+
+```php
+$stdio->on('ä', function () use ($stdio) {
+ $stdio->addInput('a');
+});
+```
+
+The `Readline` uses raw binary key codes as emitted by the terminal.
+This means that you can use the normal UTF-8 character representation for normal
+Unicode characters.
+Special keys use binary control code sequences (refer to ANSI / VT100 control
+codes for more details).
+For example, the following code can be used to register a custom function to the
+UP arrow cursor key:
+
+```php
+$stdio->on("\033[A", function () use ($stdio) {
+ $stdio->setInput(strtoupper($stdio->getInput()));
+});
+```
+
+#### Bell
+
+By default, this project will emit an audible/visible BELL signal when the user
+tries to execute an otherwise disabled 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.
+
+The `setBell(bool $bell): void` method can be used to
+enable or disable emitting the BELL signal when using a disabled function:
+
+```php
+$stdio->setBell(false);
+```
+
+### ~~Readline~~
+
+> Deprecated since v2.3.0, see [`Stdio`](#stdio) instead.
+
+The deprecated `Readline` class is responsible for reacting to user input and
+presenting a prompt to the user. It does so by reading individual bytes from the
+input stream and writing the current *user input line* to the output stream.
+
+The deprecated `Readline` class is only used internally and should no longer be
+referenced from consuming projects.
+
+You can access the current instance through the [`Stdio`](#stdio):
+
+```php
+// deprecated
+$readline = $stdio->getReadline();
+```
+
+All methods that are available on the `Readline` instance are now available on
+the `Stdio` class. For BC reasons, they remain available on the `Readline` class
+until the next major release, see also above for more details.
+
+```php
+// deprecated
+$readline->setPrompt('> ');
+
+// new
+$stdio->setPrompt('> ');
+```
+
+Internally, the `Readline` is also a well-behaving readable stream
+(implementing ReactPHP's `ReadableStreamInterface`) that emits each complete
+line as a `data` event, including the trailing newline.
+This is considered advanced usage.
+
+## Pitfalls
+
+The [`Stdio`](#stdio) has to redraw the current user
+input line whenever output is written to the `STDOUT`.
+Because of this, it is important to make sure any output is always
+written like this instead of using `echo` statements:
+
+```php
+// echo 'hello world!' . PHP_EOL;
+$stdio->write('hello world!' . PHP_EOL);
+```
+
+Depending on your program, it may or may not be reasonable to
+replace all such occurences.
+As an alternative, you may utilize output buffering that will
+automatically forward all write events to the [`Stdio`](#stdio)
+instance like this:
+
+```php
+ob_start(function ($chunk) use ($stdio) {
+ // forward write event to Stdio instead
+ $stdio->write($chunk);
+
+ // discard data from normal output handling
+ return '';
+}, 1);
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org/).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require clue/stdio-react:^2.6
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
+HHVM.
+It's *highly recommended to use the latest supported PHP version* for this project.
+
+Internally, it will use the `ext-mbstring` to count and measure string sizes.
+If this extension is missing, then this library will use a slighty slower Regex
+work-around that should otherwise work equally well.
+Installing `ext-mbstring` is highly recommended.
+
+Internally, it will use the `ext-readline` to enable raw terminal input mode.
+If this extension is missing, then this library will manually set the required
+TTY settings on start and will try to restore previous settings on exit.
+Input line editing is handled entirely within this library and does not rely on
+`ext-readline`.
+Installing `ext-readline` is entirely optional.
+
+Note that *Microsoft Windows is not supported*.
+Due to platform constraints, PHP does not provide support for reading from
+standard console input without blocking on Windows.
+Unfortunately, until the underlying PHP feature request is implemented (which
+is unlikely to happen any time soon), there's little we can do in this library.
+However, this package does work on Windows Subsystem for Linux (or WSL) without
+issues. We suggest [installing WSL](https://msdn.microsoft.com/en-us/commandline/wsl/install_guide)
+when you want to run this package on Windows.
+See also [#18](https://github.com/clue/reactphp-stdio/issues/18) for more details.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org/):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ vendor/bin/phpunit
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
+
+## More
+
+* If you want to learn more about processing streams of data, refer to the documentation of
+ the underlying [react/stream](https://github.com/reactphp/stream) component.
+
+* If you build an interactive CLI tool that reads a command line from STDIN, you
+ may want to use [clue/arguments](https://github.com/clue/php-arguments) in
+ order to split this string up into its individual arguments and then use
+ [clue/commander](https://github.com/clue/php-commander) to route to registered
+ commands and their required arguments.
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/CHANGELOG.md b/vendor/clue/term-react/CHANGELOG.md
new file mode 100644
index 0000000..b3d521d
--- /dev/null
+++ b/vendor/clue/term-react/CHANGELOG.md
@@ -0,0 +1,63 @@
+# Changelog
+
+## 1.3.0 (2020-11-06)
+
+* Improve test suite and add `.gitattributes` to exclude dev files from export.
+ Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
+ (#29 and #30 by @SimonFrings)
+
+## 1.2.0 (2018-07-09)
+
+* Feature: Forward compatiblity with EventLoop v0.5 and upcoming v1.0.
+ (#28 by @clue)
+
+* Improve test suite by updating Travis config to test against legacy PHP 5.3 through PHP 7.2.
+ (#27 by @clue)
+
+* Update project homepage.
+ (#26 by @clue)
+
+## 1.1.0 (2017-07-06)
+
+* Feature: Forward compatibility with Stream v1.0 and v0.7 (while keeping BC)
+ (#22 by @Yoshi2889 and #23 by @WyriHaximus)
+
+* Improve test suite by fixing HHVM builds and ignoring future errors
+ (#24 by @clue)
+
+## 1.0.0 (2017-04-06)
+
+* First stable release, now following SemVer
+
+ > Contains no other changes, so it's actually fully compatible with the v0.1 releases.
+
+## 0.1.3 (2017-04-06)
+
+* Feature: Forward compatibility with Stream v0.6 and v0.5 (while keeping BC)
+ (#18 and #20 by @clue)
+
+* Improve test suite by adding PHPUnit to require-dev
+ (#19 by @clue)
+
+## 0.1.2 (2016-06-14)
+
+* Fix: Fix processing events when input writes during data event
+ (#15 by @clue)
+
+* Fix: Stop emitting events when closing stream during event handler
+ (#16 by @clue)
+
+* Fix: Remove all event listeners when either stream closes
+ (#17 by @clue)
+
+## 0.1.1 (2016-06-13)
+
+* Fix: Continue parsing after a `c0` code in the middle of a stream
+ (#13 by @clue)
+
+* Add more examples
+ (#14 by @clue)
+
+## 0.1.0 (2016-06-03)
+
+* First tagged release
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/README.md b/vendor/clue/term-react/README.md
new file mode 100644
index 0000000..1768474
--- /dev/null
+++ b/vendor/clue/term-react/README.md
@@ -0,0 +1,171 @@
+# clue/reactphp-term [![Build Status](https://travis-ci.org/clue/reactphp-term.svg?branch=master)](https://travis-ci.org/clue/reactphp-term)
+
+Streaming terminal emulator, built on top of [ReactPHP](https://reactphp.org/).
+
+**Table of Contents**
+
+* [Support us](#support-us)
+* [Usage](#usage)
+ * [ControlCodeParser](#controlcodeparser)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Usage
+
+### ControlCodeParser
+
+The `ControlCodeParser(ReadableStreamInterface $input)` class can be used to
+parse any control code byte sequences (ANSI / VT100) when reading from an input stream and it
+only returns its plain data stream.
+It wraps a given `ReadableStreamInterface` and exposes its plain data through
+the same interface.
+
+```php
+$stdin = new ReadableResourceStream(STDIN, $loop);
+
+$stream = new ControlCodeParser($stdin);
+
+$stream->on('data', function ($chunk) {
+ var_dump($chunk);
+});
+```
+
+As such, you can be sure the resulting `data` events never include any control
+code byte sequences and it can be processed like a normal plain data stream.
+
+React's streams emit chunks of data strings and make no assumption about any
+byte sequences.
+These chunks do not necessarily represent complete control code byte sequences,
+as a sequence may be broken up into multiple chunks.
+This class reassembles these sequences by buffering incomplete ones.
+
+The following [C1 control codes](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set)
+are supported as defined in [ISO/IEC 2022](https://en.wikipedia.org/wiki/ISO/IEC_2022):
+
+* [CSI (Control Sequence Introducer)](https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes)
+ is one of the most common forms of control code sequences.
+ For example, CSI is used to print colored console output, also known as
+ "ANSI color codes" or the more technical term
+ [SGR (Select Graphic Rendition)](https://en.wikipedia.org/wiki/ANSI_escape_code#graphics).
+ CSI codes also appear on `STDIN`, for example when the user hits special keys,
+ such as the cursor, `HOME`, `END` etc. keys.
+
+* OSC (Operating System Command)
+ is another common form of control code sequences.
+ For example, OSC is used to change the window title or window icon.
+
+* APC (Application Program-Control)
+
+* DPS (Device-Control string)
+
+* PM (Privacy Message)
+
+Each code sequence gets emitted with a dedicated event with its raw byte sequence:
+
+```php
+$stream->on('csi', function ($sequence) {
+ if ($sequence === "\x1B[A") {
+ echo 'cursor UP pressed';
+ } else if ($sequence === "\x1B[B") {
+ echo 'cursor DOWN pressed';
+ }
+});
+
+$stream->on('osc', function ($sequence) { … });
+$stream->on('apc', function ($sequence) { … });
+$stream->on('dps', function ($sequence) { … });
+$stream->on('pm', function ($sequence) { … });
+```
+
+Other lesser known [C1 control codes](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set)
+not listed above are supported by just emitting their 2-byte sequence.
+Each generic C1 code gets emitted as an `c1` event with its raw 2-byte sequence:
+
+```php
+$stream->on('c1', function ($sequence) { … });
+```
+
+All other [C0 control codes](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C0_.28ASCII_and_derivatives.29),
+also known as [ASCII control codes](https://en.wikipedia.org/wiki/ASCII#ASCII_control_code_chart),
+are supported by just emitting their single-byte value.
+Each generic C0 code gets emitted as an `c0` event with its raw single-byte value:
+
+```php
+$stream->on('c0', function ($code) {
+ if ($code === "\n") {
+ echo 'ENTER pressed';
+ }
+});
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require clue/term-react:^1.3
+
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
+
+## More
+
+* If you want to learn more about processing streams of data, refer to the documentation of
+ the underlying [react/stream](https://github.com/reactphp/stream) component.
+
+* If you want to process UTF-8 encoded console input, you may
+ want to use [clue/reactphp-utf8](https://github.com/clue/reactphp-utf8) on the resulting
+ plain data stream.
+
+* If you want to to display or inspect the control codes, you may
+ want to use either [clue/hexdump](https://github.com/clue/php-hexdump) or
+ [clue/caret-notation](https://github.com/clue/php-caret-notation) on the emitted
+ control byte sequences.
+
+* If you want to process standard input and output (STDIN and STDOUT) from a TTY, you may
+ want to use [clue/reactphp-stdio](https://github.com/clue/reactphp-stdio) instead of
+ using this low-level library.
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/CHANGELOG.md b/vendor/clue/utf8-react/CHANGELOG.md
new file mode 100644
index 0000000..41cce80
--- /dev/null
+++ b/vendor/clue/utf8-react/CHANGELOG.md
@@ -0,0 +1,41 @@
+# Changelog
+
+## 1.2.0 (2020-11-06)
+
+* Improve test suite and add `.gitattributes` to exclude dev files from export.
+ Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
+ (#15 by clue and #21 and #22 by @SimonFrings)
+
+* Update project homepage.
+ (#14 by @clue)
+
+## 1.1.0 (2017-07-06)
+
+* Feature: Forward compatibility with Stream v1.0 and v0.7 (while keeping BC)
+ (#12 by @WyriHaximus)
+
+* Improve test suite by fixing HHVM builds and ignoring future errors
+ (#13 by @clue)
+
+## 1.0.0 (2017-04-07)
+
+* First stable release, now following SemVer
+
+ > Contains no other changes, so it's actually fully compatible with the v0.1 releases.
+
+## 0.1.2 (2017-04-07)
+
+* Feature: Forward compatibility with Stream v0.6 and v0.5 (while keeping BC)
+ (#10 by @clue)
+
+* Improve test suite by adding PHPUnit to require-dev
+ (#9 by @thklein)
+
+## 0.1.1 (2016-06-24)
+
+* Fix: Remove event listeners once closed and do not emit any further events
+ (#8 by @clue)
+
+## 0.1.0 (2016-06-01)
+
+* First tagged release
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/README.md b/vendor/clue/utf8-react/README.md
new file mode 100644
index 0000000..f26061f
--- /dev/null
+++ b/vendor/clue/utf8-react/README.md
@@ -0,0 +1,116 @@
+# clue/reactphp-utf8 [![Build Status](https://travis-ci.org/clue/reactphp-utf8.svg?branch=master)](https://travis-ci.org/clue/reactphp-utf8)
+
+Streaming UTF-8 parser, built on top of [ReactPHP](https://reactphp.org/).
+
+**Table of Contents**
+
+* [Support us](#support-us)
+* [Usage](#usage)
+ * [Sequencer](#sequencer)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Usage
+
+### Sequencer
+
+The `Sequencer` class can be used to make sure you only get back complete, valid
+UTF-8 byte sequences when reading from a stream.
+It wraps a given `ReadableStreamInterface` and exposes its data through the same
+interface.
+
+```php
+$stdin = new ReadableResourceStream(STDIN, $loop);
+
+$stream = new Sequencer($stdin);
+
+$stream->on('data', function ($chunk) {
+ var_dump($chunk);
+});
+```
+
+React's streams emit chunks of data strings and make no assumption about its encoding.
+These chunks do not necessarily represent complete UTF-8 byte sequences, as a
+sequence may be broken up into multiple chunks.
+This class reassembles these sequences by buffering incomplete ones.
+
+Also, if you're merely consuming a stream and you're not in control of producing and
+ensuring valid UTF-8 data, it may as well include invalid UTF-8 byte sequences.
+This class replaces any invalid bytes in the sequence with a `?`.
+This replacement character can be given as a second paramter to the constructor:
+
+```php
+$stream = new Sequencer($stdin, 'X');
+```
+
+As such, you can be sure you never get an invalid UTF-8 byte sequence out of
+the resulting stream.
+
+Note that the stream may still contain ASCII control characters or
+ANSI / VT100 control byte sequences, as they're valid UTF-8.
+This binary data will be left as-is, unless you filter this at a later stage.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require clue/utf8-react:^1.2
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
+
+## More
+
+* If you want to learn more about processing streams of data, refer to the documentation of
+ the underlying [react/stream](https://github.com/reactphp/stream) component.
+
+* If you want to process ASCII control characters or ANSI / VT100 control byte sequences, you may
+ want to use [clue/reactphp-term](https://github.com/clue/reactphp-term) on the raw input
+ stream before passing the resulting stream to the UTF-8 sequencer.
+
+* If you want to to display or inspect the byte sequences, you may
+ want to use [clue/hexdump](https://github.com/clue/php-hexdump) on the emitted byte sequences.
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;
+ }
+}