summaryrefslogtreecommitdiffstats
path: root/vendor/react
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:38:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:38:42 +0000
commitc3ca98e1b35123f226c7f4c596b5dee78caa4223 (patch)
tree9b6eb109283da55e7d9064baa9fac795a40264cb /vendor/react
parentInitial commit. (diff)
downloadicinga-php-thirdparty-upstream.tar.xz
icinga-php-thirdparty-upstream.zip
Adding upstream version 0.11.0.upstream/0.11.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--vendor/react/cache/LICENSE21
-rw-r--r--vendor/react/cache/composer.json41
-rw-r--r--vendor/react/cache/src/ArrayCache.php181
-rw-r--r--vendor/react/cache/src/CacheInterface.php194
-rw-r--r--vendor/react/child-process/LICENSE21
-rw-r--r--vendor/react/child-process/composer.json45
-rw-r--r--vendor/react/child-process/src/Process.php567
-rw-r--r--vendor/react/datagram/LICENSE21
-rw-r--r--vendor/react/datagram/composer.json50
-rw-r--r--vendor/react/datagram/src/Buffer.php114
-rw-r--r--vendor/react/datagram/src/Factory.php149
-rw-r--r--vendor/react/datagram/src/Socket.php142
-rw-r--r--vendor/react/datagram/src/SocketInterface.php29
-rw-r--r--vendor/react/dns/LICENSE21
-rw-r--r--vendor/react/dns/composer.json45
-rw-r--r--vendor/react/dns/src/BadServerException.php7
-rw-r--r--vendor/react/dns/src/Config/Config.php137
-rw-r--r--vendor/react/dns/src/Config/HostsFile.php153
-rw-r--r--vendor/react/dns/src/Model/Message.php230
-rw-r--r--vendor/react/dns/src/Model/Record.php153
-rw-r--r--vendor/react/dns/src/Protocol/BinaryDumper.php199
-rw-r--r--vendor/react/dns/src/Protocol/Parser.php356
-rw-r--r--vendor/react/dns/src/Query/CachingExecutor.php88
-rw-r--r--vendor/react/dns/src/Query/CancellationException.php7
-rw-r--r--vendor/react/dns/src/Query/CoopExecutor.php91
-rw-r--r--vendor/react/dns/src/Query/ExecutorInterface.php43
-rw-r--r--vendor/react/dns/src/Query/FallbackExecutor.php49
-rw-r--r--vendor/react/dns/src/Query/HostsFileExecutor.php89
-rw-r--r--vendor/react/dns/src/Query/Query.php69
-rw-r--r--vendor/react/dns/src/Query/RetryExecutor.php86
-rw-r--r--vendor/react/dns/src/Query/SelectiveTransportExecutor.php85
-rw-r--r--vendor/react/dns/src/Query/TcpTransportExecutor.php367
-rw-r--r--vendor/react/dns/src/Query/TimeoutException.php7
-rw-r--r--vendor/react/dns/src/Query/TimeoutExecutor.php31
-rw-r--r--vendor/react/dns/src/Query/UdpTransportExecutor.php208
-rw-r--r--vendor/react/dns/src/RecordNotFoundException.php7
-rw-r--r--vendor/react/dns/src/Resolver/Factory.php214
-rw-r--r--vendor/react/dns/src/Resolver/Resolver.php147
-rw-r--r--vendor/react/dns/src/Resolver/ResolverInterface.php94
-rw-r--r--vendor/react/event-loop/LICENSE21
-rw-r--r--vendor/react/event-loop/composer.json49
-rw-r--r--vendor/react/event-loop/src/ExtEvLoop.php253
-rw-r--r--vendor/react/event-loop/src/ExtEventLoop.php275
-rw-r--r--vendor/react/event-loop/src/ExtLibevLoop.php201
-rw-r--r--vendor/react/event-loop/src/ExtLibeventLoop.php285
-rw-r--r--vendor/react/event-loop/src/ExtUvLoop.php342
-rw-r--r--vendor/react/event-loop/src/Factory.php75
-rw-r--r--vendor/react/event-loop/src/Loop.php225
-rw-r--r--vendor/react/event-loop/src/LoopInterface.php472
-rw-r--r--vendor/react/event-loop/src/SignalsHandler.php63
-rw-r--r--vendor/react/event-loop/src/StreamSelectLoop.php329
-rw-r--r--vendor/react/event-loop/src/Tick/FutureTickQueue.php60
-rw-r--r--vendor/react/event-loop/src/Timer/Timer.php55
-rw-r--r--vendor/react/event-loop/src/Timer/Timers.php112
-rw-r--r--vendor/react/event-loop/src/TimerInterface.php27
-rw-r--r--vendor/react/http-client/LICENSE19
-rw-r--r--vendor/react/http-client/composer.json30
-rw-r--r--vendor/react/http-client/src/ChunkedStreamDecoder.php207
-rw-r--r--vendor/react/http-client/src/Client.php28
-rw-r--r--vendor/react/http-client/src/Request.php294
-rw-r--r--vendor/react/http-client/src/RequestData.php125
-rw-r--r--vendor/react/http-client/src/Response.php174
-rw-r--r--vendor/react/http/LICENSE21
-rw-r--r--vendor/react/http/composer.json53
-rw-r--r--vendor/react/http/src/Browser.php790
-rw-r--r--vendor/react/http/src/Client/Client.php31
-rw-r--r--vendor/react/http/src/Client/Request.php237
-rw-r--r--vendor/react/http/src/Client/RequestData.php128
-rw-r--r--vendor/react/http/src/HttpServer.php351
-rw-r--r--vendor/react/http/src/Io/BufferedBody.php179
-rw-r--r--vendor/react/http/src/Io/ChunkedDecoder.php175
-rw-r--r--vendor/react/http/src/Io/ChunkedEncoder.php92
-rw-r--r--vendor/react/http/src/Io/CloseProtectionStream.php111
-rw-r--r--vendor/react/http/src/Io/EmptyBodyStream.php142
-rw-r--r--vendor/react/http/src/Io/HttpBodyStream.php182
-rw-r--r--vendor/react/http/src/Io/IniUtil.php48
-rw-r--r--vendor/react/http/src/Io/LengthLimitedStream.php108
-rw-r--r--vendor/react/http/src/Io/MiddlewareRunner.php61
-rw-r--r--vendor/react/http/src/Io/MultipartParser.php328
-rw-r--r--vendor/react/http/src/Io/PauseBufferStream.php188
-rw-r--r--vendor/react/http/src/Io/ReadableBodyStream.php153
-rw-r--r--vendor/react/http/src/Io/RequestHeaderParser.php281
-rw-r--r--vendor/react/http/src/Io/Sender.php160
-rw-r--r--vendor/react/http/src/Io/StreamingServer.php383
-rw-r--r--vendor/react/http/src/Io/Transaction.php303
-rw-r--r--vendor/react/http/src/Io/UploadedFile.php130
-rw-r--r--vendor/react/http/src/Message/Response.php291
-rw-r--r--vendor/react/http/src/Message/ResponseException.php43
-rw-r--r--vendor/react/http/src/Message/ServerRequest.php197
-rw-r--r--vendor/react/http/src/Middleware/LimitConcurrentRequestsMiddleware.php211
-rw-r--r--vendor/react/http/src/Middleware/RequestBodyBufferMiddleware.php70
-rw-r--r--vendor/react/http/src/Middleware/RequestBodyParserMiddleware.php46
-rw-r--r--vendor/react/http/src/Middleware/StreamingRequestMiddleware.php69
-rw-r--r--vendor/react/http/src/Server.php18
-rw-r--r--vendor/react/promise-stream/LICENSE21
-rw-r--r--vendor/react/promise-stream/composer.json47
-rw-r--r--vendor/react/promise-stream/src/UnwrapReadableStream.php137
-rw-r--r--vendor/react/promise-stream/src/UnwrapWritableStream.php164
-rw-r--r--vendor/react/promise-stream/src/functions.php370
-rw-r--r--vendor/react/promise-stream/src/functions_include.php7
-rw-r--r--vendor/react/promise-timer/LICENSE21
-rw-r--r--vendor/react/promise-timer/composer.json44
-rw-r--r--vendor/react/promise-timer/src/TimeoutException.php35
-rw-r--r--vendor/react/promise-timer/src/functions.php330
-rw-r--r--vendor/react/promise-timer/src/functions_include.php7
-rw-r--r--vendor/react/promise/LICENSE24
-rw-r--r--vendor/react/promise/composer.json48
-rw-r--r--vendor/react/promise/src/CancellablePromiseInterface.php17
-rw-r--r--vendor/react/promise/src/CancellationQueue.php55
-rw-r--r--vendor/react/promise/src/Deferred.php65
-rw-r--r--vendor/react/promise/src/Exception/LengthException.php7
-rw-r--r--vendor/react/promise/src/ExtendedPromiseInterface.php98
-rw-r--r--vendor/react/promise/src/FulfilledPromise.php71
-rw-r--r--vendor/react/promise/src/LazyPromise.php66
-rw-r--r--vendor/react/promise/src/Promise.php256
-rw-r--r--vendor/react/promise/src/PromiseInterface.php41
-rw-r--r--vendor/react/promise/src/PromisorInterface.php13
-rw-r--r--vendor/react/promise/src/RejectedPromise.php79
-rw-r--r--vendor/react/promise/src/UnhandledRejectionException.php31
-rw-r--r--vendor/react/promise/src/functions.php407
-rw-r--r--vendor/react/promise/src/functions_include.php5
-rw-r--r--vendor/react/socket/LICENSE21
-rw-r--r--vendor/react/socket/composer.json52
-rw-r--r--vendor/react/socket/src/Connection.php187
-rw-r--r--vendor/react/socket/src/ConnectionInterface.php119
-rw-r--r--vendor/react/socket/src/Connector.php236
-rw-r--r--vendor/react/socket/src/ConnectorInterface.php58
-rw-r--r--vendor/react/socket/src/DnsConnector.php117
-rw-r--r--vendor/react/socket/src/FdServer.php212
-rw-r--r--vendor/react/socket/src/FixedUriConnector.php41
-rw-r--r--vendor/react/socket/src/HappyEyeBallsConnectionBuilder.php333
-rw-r--r--vendor/react/socket/src/HappyEyeBallsConnector.php69
-rw-r--r--vendor/react/socket/src/LimitingServer.php203
-rw-r--r--vendor/react/socket/src/SecureConnector.php122
-rw-r--r--vendor/react/socket/src/SecureServer.php206
-rw-r--r--vendor/react/socket/src/Server.php114
-rw-r--r--vendor/react/socket/src/ServerInterface.php151
-rw-r--r--vendor/react/socket/src/SocketServer.php187
-rw-r--r--vendor/react/socket/src/StreamEncryption.php141
-rw-r--r--vendor/react/socket/src/TcpConnector.php159
-rw-r--r--vendor/react/socket/src/TcpServer.php258
-rw-r--r--vendor/react/socket/src/TimeoutConnector.php51
-rw-r--r--vendor/react/socket/src/UnixConnector.php51
-rw-r--r--vendor/react/socket/src/UnixServer.php154
-rw-r--r--vendor/react/stream/LICENSE21
-rw-r--r--vendor/react/stream/composer.json47
-rw-r--r--vendor/react/stream/src/CompositeStream.php83
-rw-r--r--vendor/react/stream/src/DuplexResourceStream.php227
-rw-r--r--vendor/react/stream/src/DuplexStreamInterface.php39
-rw-r--r--vendor/react/stream/src/ReadableResourceStream.php179
-rw-r--r--vendor/react/stream/src/ReadableStreamInterface.php362
-rw-r--r--vendor/react/stream/src/ThroughStream.php190
-rw-r--r--vendor/react/stream/src/Util.php75
-rw-r--r--vendor/react/stream/src/WritableResourceStream.php168
-rw-r--r--vendor/react/stream/src/WritableStreamInterface.php347
155 files changed, 21305 insertions, 0 deletions
diff --git a/vendor/react/cache/LICENSE b/vendor/react/cache/LICENSE
new file mode 100644
index 0000000..d6f8901
--- /dev/null
+++ b/vendor/react/cache/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/cache/composer.json b/vendor/react/cache/composer.json
new file mode 100644
index 0000000..ad0d9fe
--- /dev/null
+++ b/vendor/react/cache/composer.json
@@ -0,0 +1,41 @@
+{
+ "name": "react/cache",
+ "description": "Async, Promise-based cache interface for ReactPHP",
+ "keywords": ["cache", "caching", "promise", "ReactPHP"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "react/promise": "^3.0 || ^2.0 || ^1.1"
+ },
+ "autoload": {
+ "psr-4": { "React\\Cache\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Cache\\": "tests/" }
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/vendor/react/cache/src/ArrayCache.php b/vendor/react/cache/src/ArrayCache.php
new file mode 100644
index 0000000..81f25ef
--- /dev/null
+++ b/vendor/react/cache/src/ArrayCache.php
@@ -0,0 +1,181 @@
+<?php
+
+namespace React\Cache;
+
+use React\Promise;
+use React\Promise\PromiseInterface;
+
+class ArrayCache implements CacheInterface
+{
+ private $limit;
+ private $data = array();
+ private $expires = array();
+ private $supportsHighResolution;
+
+ /**
+ * The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
+ *
+ * ```php
+ * $cache = new ArrayCache();
+ *
+ * $cache->set('foo', 'bar');
+ * ```
+ *
+ * Its constructor accepts an optional `?int $limit` parameter to limit the
+ * maximum number of entries to store in the LRU cache. If you add more
+ * entries to this instance, it will automatically take care of removing
+ * the one that was least recently used (LRU).
+ *
+ * For example, this snippet will overwrite the first value and only store
+ * the last two entries:
+ *
+ * ```php
+ * $cache = new ArrayCache(2);
+ *
+ * $cache->set('foo', '1');
+ * $cache->set('bar', '2');
+ * $cache->set('baz', '3');
+ * ```
+ *
+ * This cache implementation is known to rely on wall-clock time to schedule
+ * future cache expiration times when using any version before PHP 7.3,
+ * because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you store a cache item with a TTL of 30s on PHP < 7.3
+ * and then adjust your system time forward by 20s, the cache item may
+ * expire in 10s. See also [`set()`](#set) for more details.
+ *
+ * @param int|null $limit maximum number of entries to store in the LRU cache
+ */
+ public function __construct($limit = null)
+ {
+ $this->limit = $limit;
+
+ // prefer high-resolution timer, available as of PHP 7.3+
+ $this->supportsHighResolution = \function_exists('hrtime');
+ }
+
+ public function get($key, $default = null)
+ {
+ // delete key if it is already expired => below will detect this as a cache miss
+ if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ if (!\array_key_exists($key, $this->data)) {
+ return Promise\resolve($default);
+ }
+
+ // remove and append to end of array to keep track of LRU info
+ $value = $this->data[$key];
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ return Promise\resolve($value);
+ }
+
+ public function set($key, $value, $ttl = null)
+ {
+ // unset before setting to ensure this entry will be added to end of array (LRU info)
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ // sort expiration times if TTL is given (first will expire first)
+ unset($this->expires[$key]);
+ if ($ttl !== null) {
+ $this->expires[$key] = $this->now() + $ttl;
+ \asort($this->expires);
+ }
+
+ // ensure size limit is not exceeded or remove first entry from array
+ if ($this->limit !== null && \count($this->data) > $this->limit) {
+ // first try to check if there's any expired entry
+ // expiration times are sorted, so we can simply look at the first one
+ \reset($this->expires);
+ $key = \key($this->expires);
+
+ // check to see if the first in the list of expiring keys is already expired
+ // if the first key is not expired, we have to overwrite by using LRU info
+ if ($key === null || $this->now() - $this->expires[$key] < 0) {
+ \reset($this->data);
+ $key = \key($this->data);
+ }
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function delete($key)
+ {
+ unset($this->data[$key], $this->expires[$key]);
+
+ return Promise\resolve(true);
+ }
+
+ public function getMultiple(array $keys, $default = null)
+ {
+ $values = array();
+
+ foreach ($keys as $key) {
+ $values[$key] = $this->get($key, $default);
+ }
+
+ return Promise\all($values);
+ }
+
+ public function setMultiple(array $values, $ttl = null)
+ {
+ foreach ($values as $key => $value) {
+ $this->set($key, $value, $ttl);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function deleteMultiple(array $keys)
+ {
+ foreach ($keys as $key) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function clear()
+ {
+ $this->data = array();
+ $this->expires = array();
+
+ return Promise\resolve(true);
+ }
+
+ public function has($key)
+ {
+ // delete key if it is already expired
+ if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ if (!\array_key_exists($key, $this->data)) {
+ return Promise\resolve(false);
+ }
+
+ // remove and append to end of array to keep track of LRU info
+ $value = $this->data[$key];
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ return Promise\resolve(true);
+ }
+
+ /**
+ * @return float
+ */
+ private function now()
+ {
+ return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
+ }
+}
diff --git a/vendor/react/cache/src/CacheInterface.php b/vendor/react/cache/src/CacheInterface.php
new file mode 100644
index 0000000..8e51c19
--- /dev/null
+++ b/vendor/react/cache/src/CacheInterface.php
@@ -0,0 +1,194 @@
+<?php
+
+namespace React\Cache;
+
+use React\Promise\PromiseInterface;
+
+interface CacheInterface
+{
+ /**
+ * Retrieves an item from the cache.
+ *
+ * This method will resolve with the cached value on success or with the
+ * given `$default` value when no item can be found or when an error occurs.
+ * Similarly, an expired cache item (once the time-to-live is expired) is
+ * considered a cache miss.
+ *
+ * ```php
+ * $cache
+ * ->get('foo')
+ * ->then('var_dump');
+ * ```
+ *
+ * This example fetches the value of the key `foo` and passes it to the
+ * `var_dump` function. You can use any of the composition provided by
+ * [promises](https://github.com/reactphp/promise).
+ *
+ * @param string $key
+ * @param mixed $default Default value to return for cache miss or null if not given.
+ * @return PromiseInterface<mixed>
+ */
+ public function get($key, $default = null);
+
+ /**
+ * Stores an item in the cache.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. If the cache implementation has to go over the network to store
+ * it, it may take a while.
+ *
+ * The optional `$ttl` parameter sets the maximum time-to-live in seconds
+ * for this cache item. If this parameter is omitted (or `null`), the item
+ * will stay in the cache for as long as the underlying implementation
+ * supports. Trying to access an expired cache item results in a cache miss,
+ * see also [`get()`](#get).
+ *
+ * ```php
+ * $cache->set('foo', 'bar', 60);
+ * ```
+ *
+ * This example eventually sets the value of the key `foo` to `bar`. If it
+ * already exists, it is overridden.
+ *
+ * This interface does not enforce any particular TTL resolution, so special
+ * care may have to be taken if you rely on very high precision with
+ * millisecond accuracy or below. Cache implementations SHOULD work on a
+ * best effort basis and SHOULD provide at least second accuracy unless
+ * otherwise noted. Many existing cache implementations are known to provide
+ * microsecond or millisecond accuracy, but it's generally not recommended
+ * to rely on this high precision.
+ *
+ * This interface suggests that cache implementations SHOULD use a monotonic
+ * time source if available. Given that a monotonic time source is only
+ * available as of PHP 7.3 by default, cache implementations MAY fall back
+ * to using wall-clock time.
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you store a cache item with a TTL of 30s and then
+ * adjust your system time forward by 20s, the cache item SHOULD still
+ * expire in 30s.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @param ?float $ttl
+ * @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function set($key, $value, $ttl = null);
+
+ /**
+ * Deletes an item from the cache.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. When no item for `$key` is found in the cache, it also resolves
+ * to `true`. If the cache implementation has to go over the network to
+ * delete it, it may take a while.
+ *
+ * ```php
+ * $cache->delete('foo');
+ * ```
+ *
+ * This example eventually deletes the key `foo` from the cache. As with
+ * `set()`, this may not happen instantly and a promise is returned to
+ * provide guarantees whether or not the item has been removed from cache.
+ *
+ * @param string $key
+ * @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function delete($key);
+
+ /**
+ * Retrieves multiple cache items by their unique keys.
+ *
+ * This method will resolve with an array of cached values on success or with the
+ * given `$default` value when an item can not be found or when an error occurs.
+ * Similarly, an expired cache item (once the time-to-live is expired) is
+ * considered a cache miss.
+ *
+ * ```php
+ * $cache->getMultiple(array('name', 'age'))->then(function (array $values) {
+ * $name = $values['name'] ?? 'User';
+ * $age = $values['age'] ?? 'n/a';
+ *
+ * echo $name . ' is ' . $age . PHP_EOL;
+ * });
+ * ```
+ *
+ * This example fetches the cache items for the `name` and `age` keys and
+ * prints some example output. You can use any of the composition provided
+ * by [promises](https://github.com/reactphp/promise).
+ *
+ * @param string[] $keys A list of keys that can obtained in a single operation.
+ * @param mixed $default Default value to return for keys that do not exist.
+ * @return PromiseInterface<array> Returns a promise which resolves to an `array` of cached values
+ */
+ public function getMultiple(array $keys, $default = null);
+
+ /**
+ * Persists a set of key => value pairs in the cache, with an optional TTL.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. If the cache implementation has to go over the network to store
+ * it, it may take a while.
+ *
+ * The optional `$ttl` parameter sets the maximum time-to-live in seconds
+ * for these cache items. If this parameter is omitted (or `null`), these items
+ * will stay in the cache for as long as the underlying implementation
+ * supports. Trying to access an expired cache items results in a cache miss,
+ * see also [`get()`](#get).
+ *
+ * ```php
+ * $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
+ * ```
+ *
+ * This example eventually sets the list of values - the key `foo` to 1 value
+ * and the key `bar` to 2. If some of the keys already exist, they are overridden.
+ *
+ * @param array $values A list of key => value pairs for a multiple-set operation.
+ * @param ?float $ttl Optional. The TTL value of this item.
+ * @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function setMultiple(array $values, $ttl = null);
+
+ /**
+ * Deletes multiple cache items in a single operation.
+ *
+ * @param string[] $keys A list of string-based keys to be deleted.
+ * @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function deleteMultiple(array $keys);
+
+ /**
+ * Wipes clean the entire cache.
+ *
+ * @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function clear();
+
+ /**
+ * Determines whether an item is present in the cache.
+ *
+ * This method will resolve with `true` on success or `false` when no item can be found
+ * or when an error occurs. Similarly, an expired cache item (once the time-to-live
+ * is expired) is considered a cache miss.
+ *
+ * ```php
+ * $cache
+ * ->has('foo')
+ * ->then('var_dump');
+ * ```
+ *
+ * This example checks if the value of the key `foo` is set in the cache and passes
+ * the result to the `var_dump` function. You can use any of the composition provided by
+ * [promises](https://github.com/reactphp/promise).
+ *
+ * NOTE: It is recommended that has() is only to be used for cache warming type purposes
+ * and not to be used within your live applications operations for get/set, as this method
+ * is subject to a race condition where your has() will return true and immediately after,
+ * another script can remove it making the state of your app out of date.
+ *
+ * @param string $key The cache item key.
+ * @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function has($key);
+}
diff --git a/vendor/react/child-process/LICENSE b/vendor/react/child-process/LICENSE
new file mode 100644
index 0000000..d6f8901
--- /dev/null
+++ b/vendor/react/child-process/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/child-process/composer.json b/vendor/react/child-process/composer.json
new file mode 100644
index 0000000..32aa713
--- /dev/null
+++ b/vendor/react/child-process/composer.json
@@ -0,0 +1,45 @@
+{
+ "name": "react/child-process",
+ "description": "Event-driven library for executing child processes with ReactPHP.",
+ "keywords": ["process", "event-driven", "ReactPHP"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "react/event-loop": "^1.2",
+ "react/stream": "^1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
+ "react/socket": "^1.8",
+ "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0"
+ },
+ "autoload": {
+ "psr-4": { "React\\ChildProcess\\": "src" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\ChildProcess\\": "tests" }
+ }
+}
diff --git a/vendor/react/child-process/src/Process.php b/vendor/react/child-process/src/Process.php
new file mode 100644
index 0000000..2a275e9
--- /dev/null
+++ b/vendor/react/child-process/src/Process.php
@@ -0,0 +1,567 @@
+<?php
+
+namespace React\ChildProcess;
+
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Stream\ReadableResourceStream;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\WritableResourceStream;
+use React\Stream\WritableStreamInterface;
+use React\Stream\DuplexResourceStream;
+use React\Stream\DuplexStreamInterface;
+
+/**
+ * Process component.
+ *
+ * This class borrows logic from Symfony's Process component for ensuring
+ * compatibility when PHP is compiled with the --enable-sigchild option.
+ *
+ * This class also implements the `EventEmitterInterface`
+ * which allows you to react to certain events:
+ *
+ * exit event:
+ * The `exit` event will be emitted whenever the process is no longer running.
+ * Event listeners will receive the exit code and termination signal as two
+ * arguments:
+ *
+ * ```php
+ * $process = new Process('sleep 10');
+ * $process->start();
+ *
+ * $process->on('exit', function ($code, $term) {
+ * if ($term === null) {
+ * echo 'exit with code ' . $code . PHP_EOL;
+ * } else {
+ * echo 'terminated with signal ' . $term . PHP_EOL;
+ * }
+ * });
+ * ```
+ *
+ * Note that `$code` is `null` if the process has terminated, but the exit
+ * code could not be determined (for example
+ * [sigchild compatibility](#sigchild-compatibility) was disabled).
+ * Similarly, `$term` is `null` unless the process has terminated in response to
+ * an uncaught signal sent to it.
+ * This is not a limitation of this project, but actual how exit codes and signals
+ * are exposed on POSIX systems, for more details see also
+ * [here](https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated).
+ *
+ * It's also worth noting that process termination depends on all file descriptors
+ * being closed beforehand.
+ * This means that all [process pipes](#stream-properties) will emit a `close`
+ * event before the `exit` event and that no more `data` events will arrive after
+ * the `exit` event.
+ * Accordingly, if either of these pipes is in a paused state (`pause()` method
+ * or internally due to a `pipe()` call), this detection may not trigger.
+ */
+class Process extends EventEmitter
+{
+ /**
+ * @var WritableStreamInterface|null|DuplexStreamInterface|ReadableStreamInterface
+ */
+ public $stdin;
+
+ /**
+ * @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface
+ */
+ public $stdout;
+
+ /**
+ * @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface
+ */
+ public $stderr;
+
+ /**
+ * Array with all process pipes (once started)
+ *
+ * Unless explicitly configured otherwise during construction, the following
+ * standard I/O pipes will be assigned by default:
+ * - 0: STDIN (`WritableStreamInterface`)
+ * - 1: STDOUT (`ReadableStreamInterface`)
+ * - 2: STDERR (`ReadableStreamInterface`)
+ *
+ * @var array<ReadableStreamInterface|WritableStreamInterface|DuplexStreamInterface>
+ */
+ public $pipes = array();
+
+ private $cmd;
+ private $cwd;
+ private $env;
+ private $fds;
+
+ private $enhanceSigchildCompatibility;
+ private $sigchildPipe;
+
+ private $process;
+ private $status;
+ private $exitCode;
+ private $fallbackExitCode;
+ private $stopSignal;
+ private $termSignal;
+
+ private static $sigchild;
+
+ /**
+ * Constructor.
+ *
+ * @param string $cmd Command line to run
+ * @param null|string $cwd Current working directory or null to inherit
+ * @param null|array $env Environment variables or null to inherit
+ * @param null|array $fds File descriptors to allocate for this process (or null = default STDIO streams)
+ * @throws \LogicException On windows or when proc_open() is not installed
+ */
+ public function __construct($cmd, $cwd = null, array $env = null, array $fds = null)
+ {
+ if (!\function_exists('proc_open')) {
+ throw new \LogicException('The Process class relies on proc_open(), which is not available on your PHP installation.');
+ }
+
+ $this->cmd = $cmd;
+ $this->cwd = $cwd;
+
+ if (null !== $env) {
+ $this->env = array();
+ foreach ($env as $key => $value) {
+ $this->env[(binary) $key] = (binary) $value;
+ }
+ }
+
+ if ($fds === null) {
+ $fds = array(
+ array('pipe', 'r'), // stdin
+ array('pipe', 'w'), // stdout
+ array('pipe', 'w'), // stderr
+ );
+ }
+
+ if (\DIRECTORY_SEPARATOR === '\\') {
+ foreach ($fds as $fd) {
+ if (isset($fd[0]) && $fd[0] === 'pipe') {
+ throw new \LogicException('Process pipes are not supported on Windows due to their blocking nature on Windows');
+ }
+ }
+ }
+
+ $this->fds = $fds;
+ $this->enhanceSigchildCompatibility = self::isSigchildEnabled();
+ }
+
+ /**
+ * Start the process.
+ *
+ * After the process is started, the standard I/O streams will be constructed
+ * and available via public properties.
+ *
+ * This method takes an optional `LoopInterface|null $loop` parameter that can be used to
+ * pass the event loop instance to use for this process. 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 Loop interface for stream construction
+ * @param float $interval Interval to periodically monitor process state (seconds)
+ * @throws \RuntimeException If the process is already running or fails to start
+ */
+ public function start(LoopInterface $loop = null, $interval = 0.1)
+ {
+ if ($this->isRunning()) {
+ throw new \RuntimeException('Process is already running');
+ }
+
+ $loop = $loop ?: Loop::get();
+ $cmd = $this->cmd;
+ $fdSpec = $this->fds;
+ $sigchild = null;
+
+ // Read exit code through fourth pipe to work around --enable-sigchild
+ if ($this->enhanceSigchildCompatibility) {
+ $fdSpec[] = array('pipe', 'w');
+ \end($fdSpec);
+ $sigchild = \key($fdSpec);
+
+ // make sure this is fourth or higher (do not mess with STDIO)
+ if ($sigchild < 3) {
+ $fdSpec[3] = $fdSpec[$sigchild];
+ unset($fdSpec[$sigchild]);
+ $sigchild = 3;
+ }
+
+ $cmd = \sprintf('(%s) ' . $sigchild . '>/dev/null; code=$?; echo $code >&' . $sigchild . '; exit $code', $cmd);
+ }
+
+ // on Windows, we do not launch the given command line in a shell (cmd.exe) by default and omit any error dialogs
+ // the cmd.exe shell can explicitly be given as part of the command as detailed in both documentation and tests
+ $options = array();
+ if (\DIRECTORY_SEPARATOR === '\\') {
+ $options['bypass_shell'] = true;
+ $options['suppress_errors'] = true;
+ }
+
+ $this->process = @\proc_open($cmd, $fdSpec, $pipes, $this->cwd, $this->env, $options);
+
+ if (!\is_resource($this->process)) {
+ $error = \error_get_last();
+ throw new \RuntimeException('Unable to launch a new process: ' . $error['message']);
+ }
+
+ // count open process pipes and await close event for each to drain buffers before detecting exit
+ $that = $this;
+ $closeCount = 0;
+ $streamCloseHandler = function () use (&$closeCount, $loop, $interval, $that) {
+ $closeCount--;
+
+ if ($closeCount > 0) {
+ return;
+ }
+
+ // process already closed => report immediately
+ if (!$that->isRunning()) {
+ $that->close();
+ $that->emit('exit', array($that->getExitCode(), $that->getTermSignal()));
+ return;
+ }
+
+ // close not detected immediately => check regularly
+ $loop->addPeriodicTimer($interval, function ($timer) use ($that, $loop) {
+ if (!$that->isRunning()) {
+ $loop->cancelTimer($timer);
+ $that->close();
+ $that->emit('exit', array($that->getExitCode(), $that->getTermSignal()));
+ }
+ });
+ };
+
+ if ($sigchild !== null) {
+ $this->sigchildPipe = $pipes[$sigchild];
+ unset($pipes[$sigchild]);
+ }
+
+ foreach ($pipes as $n => $fd) {
+ // use open mode from stream meta data or fall back to pipe open mode for legacy HHVM
+ $meta = \stream_get_meta_data($fd);
+ $mode = $meta['mode'] === '' ? ($this->fds[$n][1] === 'r' ? 'w' : 'r') : $meta['mode'];
+
+ if ($mode === 'r+') {
+ $stream = new DuplexResourceStream($fd, $loop);
+ $stream->on('close', $streamCloseHandler);
+ $closeCount++;
+ } elseif ($mode === 'w') {
+ $stream = new WritableResourceStream($fd, $loop);
+ } else {
+ $stream = new ReadableResourceStream($fd, $loop);
+ $stream->on('close', $streamCloseHandler);
+ $closeCount++;
+ }
+ $this->pipes[$n] = $stream;
+ }
+
+ $this->stdin = isset($this->pipes[0]) ? $this->pipes[0] : null;
+ $this->stdout = isset($this->pipes[1]) ? $this->pipes[1] : null;
+ $this->stderr = isset($this->pipes[2]) ? $this->pipes[2] : null;
+
+ // immediately start checking for process exit when started without any I/O pipes
+ if (!$closeCount) {
+ $streamCloseHandler();
+ }
+ }
+
+ /**
+ * Close the process.
+ *
+ * This method should only be invoked via the periodic timer that monitors
+ * the process state.
+ */
+ public function close()
+ {
+ if ($this->process === null) {
+ return;
+ }
+
+ foreach ($this->pipes as $pipe) {
+ $pipe->close();
+ }
+
+ if ($this->enhanceSigchildCompatibility) {
+ $this->pollExitCodePipe();
+ $this->closeExitCodePipe();
+ }
+
+ $exitCode = \proc_close($this->process);
+ $this->process = null;
+
+ if ($this->exitCode === null && $exitCode !== -1) {
+ $this->exitCode = $exitCode;
+ }
+
+ if ($this->exitCode === null && $this->status['exitcode'] !== -1) {
+ $this->exitCode = $this->status['exitcode'];
+ }
+
+ if ($this->exitCode === null && $this->fallbackExitCode !== null) {
+ $this->exitCode = $this->fallbackExitCode;
+ $this->fallbackExitCode = null;
+ }
+ }
+
+ /**
+ * Terminate the process with an optional signal.
+ *
+ * @param int $signal Optional signal (default: SIGTERM)
+ * @return bool Whether the signal was sent successfully
+ */
+ public function terminate($signal = null)
+ {
+ if ($this->process === null) {
+ return false;
+ }
+
+ if ($signal !== null) {
+ return \proc_terminate($this->process, $signal);
+ }
+
+ return \proc_terminate($this->process);
+ }
+
+ /**
+ * Get the command string used to launch the process.
+ *
+ * @return string
+ */
+ public function getCommand()
+ {
+ return $this->cmd;
+ }
+
+ /**
+ * Get the exit code returned by the process.
+ *
+ * This value is only meaningful if isRunning() has returned false. Null
+ * will be returned if the process is still running.
+ *
+ * Null may also be returned if the process has terminated, but the exit
+ * code could not be determined (e.g. sigchild compatibility was disabled).
+ *
+ * @return int|null
+ */
+ public function getExitCode()
+ {
+ return $this->exitCode;
+ }
+
+ /**
+ * Get the process ID.
+ *
+ * @return int|null
+ */
+ public function getPid()
+ {
+ $status = $this->getCachedStatus();
+
+ return $status !== null ? $status['pid'] : null;
+ }
+
+ /**
+ * Get the signal that caused the process to stop its execution.
+ *
+ * This value is only meaningful if isStopped() has returned true. Null will
+ * be returned if the process was never stopped.
+ *
+ * @return int|null
+ */
+ public function getStopSignal()
+ {
+ return $this->stopSignal;
+ }
+
+ /**
+ * Get the signal that caused the process to terminate its execution.
+ *
+ * This value is only meaningful if isTerminated() has returned true. Null
+ * will be returned if the process was never terminated.
+ *
+ * @return int|null
+ */
+ public function getTermSignal()
+ {
+ return $this->termSignal;
+ }
+
+ /**
+ * Return whether the process is still running.
+ *
+ * @return bool
+ */
+ public function isRunning()
+ {
+ if ($this->process === null) {
+ return false;
+ }
+
+ $status = $this->getFreshStatus();
+
+ return $status !== null ? $status['running'] : false;
+ }
+
+ /**
+ * Return whether the process has been stopped by a signal.
+ *
+ * @return bool
+ */
+ public function isStopped()
+ {
+ $status = $this->getFreshStatus();
+
+ return $status !== null ? $status['stopped'] : false;
+ }
+
+ /**
+ * Return whether the process has been terminated by an uncaught signal.
+ *
+ * @return bool
+ */
+ public function isTerminated()
+ {
+ $status = $this->getFreshStatus();
+
+ return $status !== null ? $status['signaled'] : false;
+ }
+
+ /**
+ * Return whether PHP has been compiled with the '--enable-sigchild' option.
+ *
+ * @see \Symfony\Component\Process\Process::isSigchildEnabled()
+ * @return bool
+ */
+ public final static function isSigchildEnabled()
+ {
+ if (null !== self::$sigchild) {
+ return self::$sigchild;
+ }
+
+ if (!\function_exists('phpinfo')) {
+ return self::$sigchild = false; // @codeCoverageIgnore
+ }
+
+ \ob_start();
+ \phpinfo(INFO_GENERAL);
+
+ return self::$sigchild = false !== \strpos(\ob_get_clean(), '--enable-sigchild');
+ }
+
+ /**
+ * Enable or disable sigchild compatibility mode.
+ *
+ * Sigchild compatibility mode is required to get the exit code and
+ * determine the success of a process when PHP has been compiled with
+ * the --enable-sigchild option.
+ *
+ * @param bool $sigchild
+ * @return void
+ */
+ public final static function setSigchildEnabled($sigchild)
+ {
+ self::$sigchild = (bool) $sigchild;
+ }
+
+ /**
+ * Check the fourth pipe for an exit code.
+ *
+ * This should only be used if --enable-sigchild compatibility was enabled.
+ */
+ private function pollExitCodePipe()
+ {
+ if ($this->sigchildPipe === null) {
+ return;
+ }
+
+ $r = array($this->sigchildPipe);
+ $w = $e = null;
+
+ $n = @\stream_select($r, $w, $e, 0);
+
+ if (1 !== $n) {
+ return;
+ }
+
+ $data = \fread($r[0], 8192);
+
+ if (\strlen($data) > 0) {
+ $this->fallbackExitCode = (int) $data;
+ }
+ }
+
+ /**
+ * Close the fourth pipe used to relay an exit code.
+ *
+ * This should only be used if --enable-sigchild compatibility was enabled.
+ */
+ private function closeExitCodePipe()
+ {
+ if ($this->sigchildPipe === null) {
+ return;
+ }
+
+ \fclose($this->sigchildPipe);
+ $this->sigchildPipe = null;
+ }
+
+ /**
+ * Return the cached process status.
+ *
+ * @return array
+ */
+ private function getCachedStatus()
+ {
+ if ($this->status === null) {
+ $this->updateStatus();
+ }
+
+ return $this->status;
+ }
+
+ /**
+ * Return the updated process status.
+ *
+ * @return array
+ */
+ private function getFreshStatus()
+ {
+ $this->updateStatus();
+
+ return $this->status;
+ }
+
+ /**
+ * Update the process status, stop/term signals, and exit code.
+ *
+ * Stop/term signals are only updated if the process is currently stopped or
+ * signaled, respectively. Otherwise, signal values will remain as-is so the
+ * corresponding getter methods may be used at a later point in time.
+ */
+ private function updateStatus()
+ {
+ if ($this->process === null) {
+ return;
+ }
+
+ $this->status = \proc_get_status($this->process);
+
+ if ($this->status === false) {
+ throw new \UnexpectedValueException('proc_get_status() failed');
+ }
+
+ if ($this->status['stopped']) {
+ $this->stopSignal = $this->status['stopsig'];
+ }
+
+ if ($this->status['signaled']) {
+ $this->termSignal = $this->status['termsig'];
+ }
+
+ if (!$this->status['running'] && -1 !== $this->status['exitcode']) {
+ $this->exitCode = $this->status['exitcode'];
+ }
+ }
+}
diff --git a/vendor/react/datagram/LICENSE b/vendor/react/datagram/LICENSE
new file mode 100644
index 0000000..61974a9
--- /dev/null
+++ b/vendor/react/datagram/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden
+
+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/react/datagram/composer.json b/vendor/react/datagram/composer.json
new file mode 100644
index 0000000..c5b5934
--- /dev/null
+++ b/vendor/react/datagram/composer.json
@@ -0,0 +1,50 @@
+{
+ "name": "react/datagram",
+ "description": "Event-driven UDP datagram socket client and server for ReactPHP",
+ "keywords": ["udp", "datagram", "dgram", "socket", "client", "server", "ReactPHP", "async"],
+ "homepage": "https://github.com/reactphp/datagram",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "React\\Datagram\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Datagram\\": "tests"
+ }
+ },
+ "require": {
+ "php": ">=5.3",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "react/event-loop": "^1.2",
+ "react/dns": "^1.7",
+ "react/promise": "~2.1|~1.2"
+ },
+ "require-dev": {
+ "clue/block-react": "~1.0",
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/vendor/react/datagram/src/Buffer.php b/vendor/react/datagram/src/Buffer.php
new file mode 100644
index 0000000..e5aa2f6
--- /dev/null
+++ b/vendor/react/datagram/src/Buffer.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace React\Datagram;
+
+use Evenement\EventEmitter;
+use React\EventLoop\LoopInterface;
+use \Exception;
+
+class Buffer extends EventEmitter
+{
+ protected $loop;
+ protected $socket;
+
+ private $listening = false;
+ private $outgoing = array();
+ private $writable = true;
+
+ public function __construct(LoopInterface $loop, $socket)
+ {
+ $this->loop = $loop;
+ $this->socket = $socket;
+ }
+
+ public function send($data, $remoteAddress = null)
+ {
+ if ($this->writable === false) {
+ return;
+ }
+
+ $this->outgoing []= array($data, $remoteAddress);
+
+ if (!$this->listening) {
+ $this->handleResume();
+ $this->listening = true;
+ }
+ }
+
+ public function onWritable()
+ {
+ list($data, $remoteAddress) = \array_shift($this->outgoing);
+
+ try {
+ $this->handleWrite($data, $remoteAddress);
+ }
+ catch (Exception $e) {
+ $this->emit('error', array($e, $this));
+ }
+
+ if (!$this->outgoing) {
+ if ($this->listening) {
+ $this->handlePause();
+ $this->listening = false;
+ }
+
+ if (!$this->writable) {
+ $this->close();
+ }
+ }
+ }
+
+ public function close()
+ {
+ if ($this->socket === false) {
+ return;
+ }
+
+ $this->emit('close', array($this));
+
+ if ($this->listening) {
+ $this->handlePause();
+ $this->listening = false;
+ }
+
+ $this->writable = false;
+ $this->socket = false;
+ $this->outgoing = array();
+ $this->removeAllListeners();
+ }
+
+ public function end()
+ {
+ $this->writable = false;
+
+ if (!$this->outgoing) {
+ $this->close();
+ }
+ }
+
+ protected function handlePause()
+ {
+ $this->loop->removeWriteStream($this->socket);
+ }
+
+ protected function handleResume()
+ {
+ $this->loop->addWriteStream($this->socket, array($this, 'onWritable'));
+ }
+
+ protected function handleWrite($data, $remoteAddress)
+ {
+ if ($remoteAddress === null) {
+ // do not use fwrite() as it obeys the stream buffer size and
+ // packets are not to be split at 8kb
+ $ret = @\stream_socket_sendto($this->socket, $data);
+ } else {
+ $ret = @\stream_socket_sendto($this->socket, $data, 0, $remoteAddress);
+ }
+
+ if ($ret < 0 || $ret === false) {
+ $error = \error_get_last();
+ throw new Exception('Unable to send packet: ' . \trim($error['message']));
+ }
+ }
+}
diff --git a/vendor/react/datagram/src/Factory.php b/vendor/react/datagram/src/Factory.php
new file mode 100644
index 0000000..add2569
--- /dev/null
+++ b/vendor/react/datagram/src/Factory.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace React\Datagram;
+
+use React\Datagram\Socket;
+use React\Dns\Config\Config as DnsConfig;
+use React\Dns\Resolver\Factory as DnsFactory;
+use React\Dns\Resolver\ResolverInterface;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise;
+use React\Promise\CancellablePromiseInterface;
+use \Exception;
+
+class Factory
+{
+ protected $loop;
+ protected $resolver;
+
+ /**
+ *
+ * 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 ?ResolverInterface $resolver Resolver instance to use. Will otherwise
+ * try to load the system default DNS config or fall back to using
+ * Google's public DNS 8.8.8.8
+ */
+ public function __construct(LoopInterface $loop = null, ResolverInterface $resolver = null)
+ {
+ $loop = $loop ?: Loop::get();
+ if ($resolver === null) {
+ // try to load nameservers from system config or default to Google's public DNS
+ $config = DnsConfig::loadSystemConfigBlocking();
+ if (!$config->nameservers) {
+ $config->nameservers[] = '8.8.8.8'; // @codeCoverageIgnore
+ }
+
+ $factory = new DnsFactory();
+ $resolver = $factory->create($config, $loop);
+ }
+
+ $this->loop = $loop;
+ $this->resolver = $resolver;
+ }
+
+ public function createClient($address)
+ {
+ $loop = $this->loop;
+
+ return $this->resolveAddress($address)->then(function ($address) use ($loop) {
+ $socket = @\stream_socket_client($address, $errno, $errstr);
+ if (!$socket) {
+ throw new Exception('Unable to create client socket: ' . $errstr, $errno);
+ }
+
+ return new Socket($loop, $socket);
+ });
+ }
+
+ public function createServer($address)
+ {
+ $loop = $this->loop;
+
+ return $this->resolveAddress($address)->then(function ($address) use ($loop) {
+ $socket = @\stream_socket_server($address, $errno, $errstr, \STREAM_SERVER_BIND);
+ if (!$socket) {
+ throw new Exception('Unable to create server socket: ' . $errstr, $errno);
+ }
+
+ return new Socket($loop, $socket);
+ });
+ }
+
+ protected function resolveAddress($address)
+ {
+ if (\strpos($address, '://') === false) {
+ $address = 'udp://' . $address;
+ }
+
+ // parse_url() does not accept null ports (random port assignment) => manually remove
+ $nullport = false;
+ if (\substr($address, -2) === ':0') {
+ $address = \substr($address, 0, -2);
+ $nullport = true;
+ }
+
+ $parts = \parse_url($address);
+
+ if (!$parts || !isset($parts['host'])) {
+ return Promise\resolve($address);
+ }
+
+ if ($nullport) {
+ $parts['port'] = 0;
+ }
+
+ // remove square brackets for IPv6 addresses
+ $host = \trim($parts['host'], '[]');
+
+ return $this->resolveHost($host)->then(function ($host) use ($parts) {
+ $address = $parts['scheme'] . '://';
+
+ if (isset($parts['port']) && \strpos($host, ':') !== false) {
+ // enclose IPv6 address in square brackets if a port will be appended
+ $host = '[' . $host . ']';
+ }
+
+ $address .= $host;
+
+ if (isset($parts['port'])) {
+ $address .= ':' . $parts['port'];
+ }
+
+ return $address;
+ });
+ }
+
+ protected function resolveHost($host)
+ {
+ // there's no need to resolve if the host is already given as an IP address
+ if (false !== \filter_var($host, \FILTER_VALIDATE_IP)) {
+ return Promise\resolve($host);
+ }
+
+ $promise = $this->resolver->resolve($host);
+
+ // wrap DNS lookup in order to control cancellation behavior
+ return new Promise\Promise(
+ function ($resolve, $reject) use ($promise) {
+ // forward promise resolution
+ $promise->then($resolve, $reject);
+ },
+ function ($_, $reject) use ($promise) {
+ // reject with custom message once cancelled
+ $reject(new \RuntimeException('Cancelled creating socket during DNS lookup'));
+
+ // (try to) cancel pending DNS lookup, otherwise ignoring its results
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ }
+ );
+ }
+}
diff --git a/vendor/react/datagram/src/Socket.php b/vendor/react/datagram/src/Socket.php
new file mode 100644
index 0000000..e06723c
--- /dev/null
+++ b/vendor/react/datagram/src/Socket.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace React\Datagram;
+
+use React\EventLoop\LoopInterface;
+use Evenement\EventEmitter;
+use Exception;
+
+class Socket extends EventEmitter implements SocketInterface
+{
+ protected $loop;
+ protected $socket;
+
+ protected $buffer;
+
+ public $bufferSize = 65536;
+
+ public function __construct(LoopInterface $loop, $socket, Buffer $buffer = null)
+ {
+ $this->loop = $loop;
+ $this->socket = $socket;
+
+ if ($buffer === null) {
+ $buffer = new Buffer($loop, $socket);
+ }
+ $this->buffer = $buffer;
+
+ $that = $this;
+ $this->buffer->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error, $that));
+ });
+ $this->buffer->on('close', array($this, 'close'));
+
+ $this->resume();
+ }
+
+ public function getLocalAddress()
+ {
+ if ($this->socket === false) {
+ return null;
+ }
+
+ return $this->sanitizeAddress(@\stream_socket_get_name($this->socket, false));
+ }
+
+ public function getRemoteAddress()
+ {
+ if ($this->socket === false) {
+ return null;
+ }
+
+ return $this->sanitizeAddress(@\stream_socket_get_name($this->socket, true));
+ }
+
+ public function send($data, $remoteAddress = null)
+ {
+ $this->buffer->send($data, $remoteAddress);
+ }
+
+ public function pause()
+ {
+ $this->loop->removeReadStream($this->socket);
+ }
+
+ public function resume()
+ {
+ if ($this->socket !== false) {
+ $this->loop->addReadStream($this->socket, array($this, 'onReceive'));
+ }
+ }
+
+ public function onReceive()
+ {
+ try {
+ $data = $this->handleReceive($peer);
+ }
+ catch (Exception $e) {
+ // emit error message and local socket
+ $this->emit('error', array($e, $this));
+ return;
+ }
+
+ $this->emit('message', array($data, $peer, $this));
+ }
+
+ public function close()
+ {
+ if ($this->socket === false) {
+ return;
+ }
+
+ $this->emit('close', array($this));
+ $this->pause();
+
+ $this->handleClose();
+ $this->socket = false;
+ $this->buffer->close();
+
+ $this->removeAllListeners();
+ }
+
+ public function end()
+ {
+ $this->buffer->end();
+ }
+
+ private function sanitizeAddress($address)
+ {
+ if ($address === false) {
+ return null;
+ }
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets (PHP < 7.3)
+ $pos = \strrpos($address, ':');
+ if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
+ $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
+ }
+ return $address;
+ }
+
+ protected function handleReceive(&$peerAddress)
+ {
+ $data = \stream_socket_recvfrom($this->socket, $this->bufferSize, 0, $peerAddress);
+
+ if ($data === false) {
+ // receiving data failed => remote side rejected one of our packets
+ // due to the nature of UDP, there's no way to tell which one exactly
+ // $peer is not filled either
+
+ throw new Exception('Invalid message');
+ }
+
+ $peerAddress = $this->sanitizeAddress($peerAddress);
+
+ return $data;
+ }
+
+ protected function handleClose()
+ {
+ \fclose($this->socket);
+ }
+}
diff --git a/vendor/react/datagram/src/SocketInterface.php b/vendor/react/datagram/src/SocketInterface.php
new file mode 100644
index 0000000..3292c64
--- /dev/null
+++ b/vendor/react/datagram/src/SocketInterface.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace React\Datagram;
+
+use Evenement\EventEmitterInterface;
+
+/**
+ * interface very similar to React\Stream\Stream
+ *
+ * @event message($data, $remoteAddress, $thisSocket)
+ * @event error($exception, $thisSocket)
+ * @event close($thisSocket)
+ */
+interface SocketInterface extends EventEmitterInterface
+{
+ public function send($data, $remoteAddress = null);
+
+ public function close();
+
+ public function end();
+
+ public function resume();
+
+ public function pause();
+
+ public function getLocalAddress();
+
+ public function getRemoteAddress();
+}
diff --git a/vendor/react/dns/LICENSE b/vendor/react/dns/LICENSE
new file mode 100644
index 0000000..d6f8901
--- /dev/null
+++ b/vendor/react/dns/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/dns/composer.json b/vendor/react/dns/composer.json
new file mode 100644
index 0000000..0126343
--- /dev/null
+++ b/vendor/react/dns/composer.json
@@ -0,0 +1,45 @@
+{
+ "name": "react/dns",
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "^1.0 || ^0.6 || ^0.5",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.0 || ^2.7 || ^1.2.1",
+ "react/promise-timer": "^1.8"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^9.3 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": { "React\\Dns\\": "src" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Dns\\": "tests" }
+ }
+}
diff --git a/vendor/react/dns/src/BadServerException.php b/vendor/react/dns/src/BadServerException.php
new file mode 100644
index 0000000..3d95213
--- /dev/null
+++ b/vendor/react/dns/src/BadServerException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns;
+
+final class BadServerException extends \Exception
+{
+}
diff --git a/vendor/react/dns/src/Config/Config.php b/vendor/react/dns/src/Config/Config.php
new file mode 100644
index 0000000..9677ee5
--- /dev/null
+++ b/vendor/react/dns/src/Config/Config.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace React\Dns\Config;
+
+use RuntimeException;
+
+final class Config
+{
+ /**
+ * Loads the system DNS configuration
+ *
+ * Note that this method may block while loading its internal files and/or
+ * commands and should thus be used with care! While this should be
+ * relatively fast for most systems, it remains unknown if this may block
+ * under certain circumstances. In particular, this method should only be
+ * executed before the loop starts, not while it is running.
+ *
+ * Note that this method will try to access its files and/or commands and
+ * try to parse its output. Currently, this will only parse valid nameserver
+ * entries from its output and will ignore all other output without
+ * complaining.
+ *
+ * Note that the previous section implies that this may return an empty
+ * `Config` object if no valid nameserver entries can be found.
+ *
+ * @return self
+ * @codeCoverageIgnore
+ */
+ public static function loadSystemConfigBlocking()
+ {
+ // Use WMIC output on Windows
+ if (DIRECTORY_SEPARATOR === '\\') {
+ return self::loadWmicBlocking();
+ }
+
+ // otherwise (try to) load from resolv.conf
+ try {
+ return self::loadResolvConfBlocking();
+ } catch (RuntimeException $ignored) {
+ // return empty config if parsing fails (file not found)
+ return new self();
+ }
+ }
+
+ /**
+ * Loads a resolv.conf file (from the given path or default location)
+ *
+ * Note that this method blocks while loading the given path and should
+ * thus be used with care! While this should be relatively fast for normal
+ * resolv.conf files, this may be an issue if this file is located on a slow
+ * device or contains an excessive number of entries. In particular, this
+ * method should only be executed before the loop starts, not while it is
+ * running.
+ *
+ * Note that this method will throw if the given file can not be loaded,
+ * such as if it is not readable or does not exist. In particular, this file
+ * is not available on Windows.
+ *
+ * Currently, this will only parse valid "nameserver X" lines from the
+ * given file contents. Lines can be commented out with "#" and ";" and
+ * invalid lines will be ignored without complaining. See also
+ * `man resolv.conf` for more details.
+ *
+ * Note that the previous section implies that this may return an empty
+ * `Config` object if no valid "nameserver X" lines can be found. See also
+ * `man resolv.conf` which suggests that the DNS server on the localhost
+ * should be used in this case. This is left up to higher level consumers
+ * of this API.
+ *
+ * @param ?string $path (optional) path to resolv.conf file or null=load default location
+ * @return self
+ * @throws RuntimeException if the path can not be loaded (does not exist)
+ */
+ public static function loadResolvConfBlocking($path = null)
+ {
+ if ($path === null) {
+ $path = '/etc/resolv.conf';
+ }
+
+ $contents = @file_get_contents($path);
+ if ($contents === false) {
+ throw new RuntimeException('Unable to load resolv.conf file "' . $path . '"');
+ }
+
+ $matches = array();
+ preg_match_all('/^nameserver\s+(\S+)\s*$/m', $contents, $matches);
+
+ $config = new self();
+ foreach ($matches[1] as $ip) {
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
+ $ip = substr($ip, 0, $pos);
+ }
+
+ if (@inet_pton($ip) !== false) {
+ $config->nameservers[] = $ip;
+ }
+ }
+
+ return $config;
+ }
+
+ /**
+ * Loads the DNS configurations from Windows's WMIC (from the given command or default command)
+ *
+ * Note that this method blocks while loading the given command and should
+ * thus be used with care! While this should be relatively fast for normal
+ * WMIC commands, it remains unknown if this may block under certain
+ * circumstances. In particular, this method should only be executed before
+ * the loop starts, not while it is running.
+ *
+ * Note that this method will only try to execute the given command try to
+ * parse its output, irrespective of whether this command exists. In
+ * particular, this command is only available on Windows. Currently, this
+ * will only parse valid nameserver entries from the command output and will
+ * ignore all other output without complaining.
+ *
+ * Note that the previous section implies that this may return an empty
+ * `Config` object if no valid nameserver entries can be found.
+ *
+ * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
+ * @return self
+ * @link https://ss64.com/nt/wmic.html
+ */
+ public static function loadWmicBlocking($command = null)
+ {
+ $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
+ preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
+
+ $config = new self();
+ $config->nameservers = $matches[1];
+
+ return $config;
+ }
+
+ public $nameservers = array();
+}
diff --git a/vendor/react/dns/src/Config/HostsFile.php b/vendor/react/dns/src/Config/HostsFile.php
new file mode 100644
index 0000000..1060231
--- /dev/null
+++ b/vendor/react/dns/src/Config/HostsFile.php
@@ -0,0 +1,153 @@
+<?php
+
+namespace React\Dns\Config;
+
+use RuntimeException;
+
+/**
+ * Represents a static hosts file which maps hostnames to IPs
+ *
+ * Hosts files are used on most systems to avoid actually hitting the DNS for
+ * certain common hostnames.
+ *
+ * Most notably, this file usually contains an entry to map "localhost" to the
+ * local IP. Windows is a notable exception here, as Windows does not actually
+ * include "localhost" in this file by default. To compensate for this, this
+ * class may explicitly be wrapped in another HostsFile instance which
+ * hard-codes these entries for Windows (see also Factory).
+ *
+ * This class mostly exists to abstract the parsing/extraction process so this
+ * can be replaced with a faster alternative in the future.
+ */
+class HostsFile
+{
+ /**
+ * Returns the default path for the hosts file on this system
+ *
+ * @return string
+ * @codeCoverageIgnore
+ */
+ public static function getDefaultPath()
+ {
+ // use static path for all Unix-based systems
+ if (DIRECTORY_SEPARATOR !== '\\') {
+ return '/etc/hosts';
+ }
+
+ // Windows actually stores the path in the registry under
+ // \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DataBasePath
+ $path = '%SystemRoot%\\system32\drivers\etc\hosts';
+
+ $base = getenv('SystemRoot');
+ if ($base === false) {
+ $base = 'C:\\Windows';
+ }
+
+ return str_replace('%SystemRoot%', $base, $path);
+ }
+
+ /**
+ * Loads a hosts file (from the given path or default location)
+ *
+ * Note that this method blocks while loading the given path and should
+ * thus be used with care! While this should be relatively fast for normal
+ * hosts file, this may be an issue if this file is located on a slow device
+ * or contains an excessive number of entries. In particular, this method
+ * should only be executed before the loop starts, not while it is running.
+ *
+ * @param ?string $path (optional) path to hosts file or null=load default location
+ * @return self
+ * @throws RuntimeException if the path can not be loaded (does not exist)
+ */
+ public static function loadFromPathBlocking($path = null)
+ {
+ if ($path === null) {
+ $path = self::getDefaultPath();
+ }
+
+ $contents = @file_get_contents($path);
+ if ($contents === false) {
+ throw new RuntimeException('Unable to load hosts file "' . $path . '"');
+ }
+
+ return new self($contents);
+ }
+
+ private $contents;
+
+ /**
+ * Instantiate new hosts file with the given hosts file contents
+ *
+ * @param string $contents
+ */
+ public function __construct($contents)
+ {
+ // remove all comments from the contents
+ $contents = preg_replace('/[ \t]*#.*/', '', strtolower($contents));
+
+ $this->contents = $contents;
+ }
+
+ /**
+ * Returns all IPs for the given hostname
+ *
+ * @param string $name
+ * @return string[]
+ */
+ public function getIpsForHost($name)
+ {
+ $name = strtolower($name);
+
+ $ips = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line);
+ $ip = array_shift($parts);
+ if ($parts && array_search($name, $parts) !== false) {
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
+ $ip = substr($ip, 0, $pos);
+ }
+
+ if (@inet_pton($ip) !== false) {
+ $ips[] = $ip;
+ }
+ }
+ }
+
+ return $ips;
+ }
+
+ /**
+ * Returns all hostnames for the given IPv4 or IPv6 address
+ *
+ * @param string $ip
+ * @return string[]
+ */
+ public function getHostsForIp($ip)
+ {
+ // check binary representation of IP to avoid string case and short notation
+ $ip = @inet_pton($ip);
+ if ($ip === false) {
+ return array();
+ }
+
+ $names = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
+ $addr = (string) array_shift($parts);
+
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
+ $addr = substr($addr, 0, $pos);
+ }
+
+ if (@inet_pton($addr) === $ip) {
+ foreach ($parts as $part) {
+ $names[] = $part;
+ }
+ }
+ }
+
+ return $names;
+ }
+}
diff --git a/vendor/react/dns/src/Model/Message.php b/vendor/react/dns/src/Model/Message.php
new file mode 100644
index 0000000..bac2b10
--- /dev/null
+++ b/vendor/react/dns/src/Model/Message.php
@@ -0,0 +1,230 @@
+<?php
+
+namespace React\Dns\Model;
+
+use React\Dns\Query\Query;
+
+/**
+ * This class represents an outgoing query message or an incoming response message
+ *
+ * @link https://tools.ietf.org/html/rfc1035#section-4.1.1
+ */
+final class Message
+{
+ const TYPE_A = 1;
+ const TYPE_NS = 2;
+ const TYPE_CNAME = 5;
+ const TYPE_SOA = 6;
+ const TYPE_PTR = 12;
+ const TYPE_MX = 15;
+ const TYPE_TXT = 16;
+ const TYPE_AAAA = 28;
+ const TYPE_SRV = 33;
+ const TYPE_SSHFP = 44;
+
+ /**
+ * pseudo-type for EDNS0
+ *
+ * These are included in the additional section and usually not in answer section.
+ * Defined in [RFC 6891](https://tools.ietf.org/html/rfc6891) (or older
+ * [RFC 2671](https://tools.ietf.org/html/rfc2671)).
+ *
+ * The OPT record uses the "class" field to store the maximum size.
+ *
+ * The OPT record uses the "ttl" field to store additional flags.
+ */
+ const TYPE_OPT = 41;
+
+ /**
+ * Sender Policy Framework (SPF) had a dedicated SPF type which has been
+ * deprecated in favor of reusing the existing TXT type.
+ *
+ * @deprecated https://datatracker.ietf.org/doc/html/rfc7208#section-3.1
+ * @see self::TYPE_TXT
+ */
+ const TYPE_SPF = 99;
+
+ const TYPE_ANY = 255;
+ const TYPE_CAA = 257;
+
+ const CLASS_IN = 1;
+
+ const OPCODE_QUERY = 0;
+ const OPCODE_IQUERY = 1; // inverse query
+ const OPCODE_STATUS = 2;
+
+ const RCODE_OK = 0;
+ const RCODE_FORMAT_ERROR = 1;
+ const RCODE_SERVER_FAILURE = 2;
+ const RCODE_NAME_ERROR = 3;
+ const RCODE_NOT_IMPLEMENTED = 4;
+ const RCODE_REFUSED = 5;
+
+ /**
+ * The edns-tcp-keepalive EDNS0 Option
+ *
+ * Option value contains a `?float` with timeout in seconds (in 0.1s steps)
+ * for DNS response or `null` for DNS query.
+ *
+ * @link https://tools.ietf.org/html/rfc7828
+ */
+ const OPT_TCP_KEEPALIVE = 11;
+
+ /**
+ * The EDNS(0) Padding Option
+ *
+ * Option value contains a `string` with binary data (usually variable
+ * number of null bytes)
+ *
+ * @link https://tools.ietf.org/html/rfc7830
+ */
+ const OPT_PADDING = 12;
+
+ /**
+ * Creates a new request message for the given query
+ *
+ * @param Query $query
+ * @return self
+ */
+ public static function createRequestForQuery(Query $query)
+ {
+ $request = new Message();
+ $request->id = self::generateId();
+ $request->rd = true;
+ $request->questions[] = $query;
+
+ return $request;
+ }
+
+ /**
+ * Creates a new response message for the given query with the given answer records
+ *
+ * @param Query $query
+ * @param Record[] $answers
+ * @return self
+ */
+ public static function createResponseWithAnswersForQuery(Query $query, array $answers)
+ {
+ $response = new Message();
+ $response->id = self::generateId();
+ $response->qr = true;
+ $response->rd = true;
+
+ $response->questions[] = $query;
+
+ foreach ($answers as $record) {
+ $response->answers[] = $record;
+ }
+
+ return $response;
+ }
+
+ /**
+ * generates a random 16 bit message ID
+ *
+ * This uses a CSPRNG so that an outside attacker that is sending spoofed
+ * DNS response messages can not guess the message ID to avoid possible
+ * cache poisoning attacks.
+ *
+ * The `random_int()` function is only available on PHP 7+ or when
+ * https://github.com/paragonie/random_compat is installed. As such, using
+ * the latest supported PHP version is highly recommended. This currently
+ * falls back to a less secure random number generator on older PHP versions
+ * in the hope that this system is properly protected against outside
+ * attackers, for example by using one of the common local DNS proxy stubs.
+ *
+ * @return int
+ * @see self::getId()
+ * @codeCoverageIgnore
+ */
+ private static function generateId()
+ {
+ if (function_exists('random_int')) {
+ return random_int(0, 0xffff);
+ }
+ return mt_rand(0, 0xffff);
+ }
+
+ /**
+ * The 16 bit message ID
+ *
+ * The response message ID has to match the request message ID. This allows
+ * the receiver to verify this is the correct response message. An outside
+ * attacker may try to inject fake responses by "guessing" the message ID,
+ * so this should use a proper CSPRNG to avoid possible cache poisoning.
+ *
+ * @var int 16 bit message ID
+ * @see self::generateId()
+ */
+ public $id = 0;
+
+ /**
+ * @var bool Query/Response flag, query=false or response=true
+ */
+ public $qr = false;
+
+ /**
+ * @var int specifies the kind of query (4 bit), see self::OPCODE_* constants
+ * @see self::OPCODE_QUERY
+ */
+ public $opcode = self::OPCODE_QUERY;
+
+ /**
+ *
+ * @var bool Authoritative Answer
+ */
+ public $aa = false;
+
+ /**
+ * @var bool TrunCation
+ */
+ public $tc = false;
+
+ /**
+ * @var bool Recursion Desired
+ */
+ public $rd = false;
+
+ /**
+ * @var bool Recursion Available
+ */
+ public $ra = false;
+
+ /**
+ * @var int response code (4 bit), see self::RCODE_* constants
+ * @see self::RCODE_OK
+ */
+ public $rcode = Message::RCODE_OK;
+
+ /**
+ * An array of Query objects
+ *
+ * ```php
+ * $questions = array(
+ * new Query(
+ * 'reactphp.org',
+ * Message::TYPE_A,
+ * Message::CLASS_IN
+ * )
+ * );
+ * ```
+ *
+ * @var Query[]
+ */
+ public $questions = array();
+
+ /**
+ * @var Record[]
+ */
+ public $answers = array();
+
+ /**
+ * @var Record[]
+ */
+ public $authority = array();
+
+ /**
+ * @var Record[]
+ */
+ public $additional = array();
+}
diff --git a/vendor/react/dns/src/Model/Record.php b/vendor/react/dns/src/Model/Record.php
new file mode 100644
index 0000000..c20403f
--- /dev/null
+++ b/vendor/react/dns/src/Model/Record.php
@@ -0,0 +1,153 @@
+<?php
+
+namespace React\Dns\Model;
+
+/**
+ * This class represents a single resulting record in a response message
+ *
+ * It uses a structure similar to `\React\Dns\Query\Query`, but does include
+ * fields for resulting TTL and resulting record data (IPs etc.).
+ *
+ * @link https://tools.ietf.org/html/rfc1035#section-4.1.3
+ * @see \React\Dns\Query\Query
+ */
+final class Record
+{
+ /**
+ * @var string hostname without trailing dot, for example "reactphp.org"
+ */
+ public $name;
+
+ /**
+ * @var int see Message::TYPE_* constants (UINT16)
+ */
+ public $type;
+
+ /**
+ * Defines the network class, usually `Message::CLASS_IN`.
+ *
+ * For `OPT` records (EDNS0), this defines the maximum message size instead.
+ *
+ * @var int see Message::CLASS_IN constant (UINT16)
+ * @see Message::CLASS_IN
+ */
+ public $class;
+
+ /**
+ * Defines the maximum time-to-live (TTL) in seconds
+ *
+ * For `OPT` records (EDNS0), this defines additional flags instead.
+ *
+ * @var int maximum TTL in seconds (UINT32, most significant bit always unset)
+ * @link https://tools.ietf.org/html/rfc2181#section-8
+ * @link https://tools.ietf.org/html/rfc6891#section-6.1.3 for `OPT` records (EDNS0)
+ */
+ public $ttl;
+
+ /**
+ * The payload data for this record
+ *
+ * The payload data format depends on the record type. As a rule of thumb,
+ * this library will try to express this in a way that can be consumed
+ * easily without having to worry about DNS internals and its binary transport:
+ *
+ * - A:
+ * IPv4 address string, for example "192.168.1.1".
+ *
+ * - AAAA:
+ * IPv6 address string, for example "::1".
+ *
+ * - CNAME / PTR / NS:
+ * The hostname without trailing dot, for example "reactphp.org".
+ *
+ * - TXT:
+ * List of string values, for example `["v=spf1 include:example.com"]`.
+ * This is commonly a list with only a single string value, but this
+ * technically allows multiple strings (0-255 bytes each) in a single
+ * record. This is rarely used and depending on application you may want
+ * to join these together or handle them separately. Each string can
+ * transport any binary data, its character encoding is not defined (often
+ * ASCII/UTF-8 in practice). [RFC 1464](https://tools.ietf.org/html/rfc1464)
+ * suggests using key-value pairs such as `["name=test","version=1"]`, but
+ * interpretation of this is not enforced and left up to consumers of this
+ * library (used for DNS-SD/Zeroconf and others).
+ *
+ * - MX:
+ * Mail server priority (UINT16) and target hostname without trailing dot,
+ * for example `{"priority":10,"target":"mx.example.com"}`.
+ * The payload data uses an associative array with fixed keys "priority"
+ * (also commonly referred to as weight or preference) and "target" (also
+ * referred to as exchange). If a response message contains multiple
+ * records of this type, targets should be sorted by priority (lowest
+ * first) - this is left up to consumers of this library (used for SMTP).
+ *
+ * - SRV:
+ * Service priority (UINT16), service weight (UINT16), service port (UINT16)
+ * and target hostname without trailing dot, for example
+ * `{"priority":10,"weight":50,"port":8080,"target":"example.com"}`.
+ * The payload data uses an associative array with fixed keys "priority",
+ * "weight", "port" and "target" (also referred to as name).
+ * The target may be an empty host name string if the service is decidedly
+ * not available. If a response message contains multiple records of this
+ * type, targets should be sorted by priority (lowest first) and selected
+ * randomly according to their weight - this is left up to consumers of
+ * this library, see also [RFC 2782](https://tools.ietf.org/html/rfc2782)
+ * for more details.
+ *
+ * - SSHFP:
+ * Includes algorithm (UNIT8), fingerprint type (UNIT8) and fingerprint
+ * value as lower case hex string, for example:
+ * `{"algorithm":1,"type":1,"fingerprint":"0123456789abcdef..."}`
+ * See also https://www.iana.org/assignments/dns-sshfp-rr-parameters/dns-sshfp-rr-parameters.xhtml
+ * for algorithm and fingerprint type assignments.
+ *
+ * - SOA:
+ * Includes master hostname without trailing dot, responsible person email
+ * as hostname without trailing dot and serial, refresh, retry, expire and
+ * minimum times in seconds (UINT32 each), for example:
+ * `{"mname":"ns.example.com","rname":"hostmaster.example.com","serial":
+ * 2018082601,"refresh":3600,"retry":1800,"expire":60000,"minimum":3600}`.
+ *
+ * - CAA:
+ * Includes flag (UNIT8), tag string and value string, for example:
+ * `{"flag":128,"tag":"issue","value":"letsencrypt.org"}`
+ *
+ * - OPT:
+ * Special pseudo-type for EDNS0. Includes an array of additional opt codes
+ * with a value according to the respective OPT code. See `Message::OPT_*`
+ * for list of supported OPT codes. Any other OPT code not currently
+ * supported will be an opaque binary string containing the raw data
+ * as transported in the DNS record. For forwards compatibility, you should
+ * not rely on this format for unknown types. Future versions may add
+ * support for new types and this may then parse the payload data
+ * appropriately - this will not be considered a BC break. See also
+ * [RFC 6891](https://tools.ietf.org/html/rfc6891) for more details.
+ *
+ * - Any other unknown type:
+ * An opaque binary string containing the RDATA as transported in the DNS
+ * record. For forwards compatibility, you should not rely on this format
+ * for unknown types. Future versions may add support for new types and
+ * this may then parse the payload data appropriately - this will not be
+ * considered a BC break. See the format definition of known types above
+ * for more details.
+ *
+ * @var string|string[]|array
+ */
+ public $data;
+
+ /**
+ * @param string $name
+ * @param int $type
+ * @param int $class
+ * @param int $ttl
+ * @param string|string[]|array $data
+ */
+ public function __construct($name, $type, $class, $ttl, $data)
+ {
+ $this->name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->ttl = $ttl;
+ $this->data = $data;
+ }
+}
diff --git a/vendor/react/dns/src/Protocol/BinaryDumper.php b/vendor/react/dns/src/Protocol/BinaryDumper.php
new file mode 100644
index 0000000..6f4030f
--- /dev/null
+++ b/vendor/react/dns/src/Protocol/BinaryDumper.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace React\Dns\Protocol;
+
+use React\Dns\Model\Message;
+use React\Dns\Model\Record;
+use React\Dns\Query\Query;
+
+final class BinaryDumper
+{
+ /**
+ * @param Message $message
+ * @return string
+ */
+ public function toBinary(Message $message)
+ {
+ $data = '';
+
+ $data .= $this->headerToBinary($message);
+ $data .= $this->questionToBinary($message->questions);
+ $data .= $this->recordsToBinary($message->answers);
+ $data .= $this->recordsToBinary($message->authority);
+ $data .= $this->recordsToBinary($message->additional);
+
+ return $data;
+ }
+
+ /**
+ * @param Message $message
+ * @return string
+ */
+ private function headerToBinary(Message $message)
+ {
+ $data = '';
+
+ $data .= pack('n', $message->id);
+
+ $flags = 0x00;
+ $flags = ($flags << 1) | ($message->qr ? 1 : 0);
+ $flags = ($flags << 4) | $message->opcode;
+ $flags = ($flags << 1) | ($message->aa ? 1 : 0);
+ $flags = ($flags << 1) | ($message->tc ? 1 : 0);
+ $flags = ($flags << 1) | ($message->rd ? 1 : 0);
+ $flags = ($flags << 1) | ($message->ra ? 1 : 0);
+ $flags = ($flags << 3) | 0; // skip unused zero bit
+ $flags = ($flags << 4) | $message->rcode;
+
+ $data .= pack('n', $flags);
+
+ $data .= pack('n', count($message->questions));
+ $data .= pack('n', count($message->answers));
+ $data .= pack('n', count($message->authority));
+ $data .= pack('n', count($message->additional));
+
+ return $data;
+ }
+
+ /**
+ * @param Query[] $questions
+ * @return string
+ */
+ private function questionToBinary(array $questions)
+ {
+ $data = '';
+
+ foreach ($questions as $question) {
+ $data .= $this->domainNameToBinary($question->name);
+ $data .= pack('n*', $question->type, $question->class);
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param Record[] $records
+ * @return string
+ */
+ private function recordsToBinary(array $records)
+ {
+ $data = '';
+
+ foreach ($records as $record) {
+ /* @var $record Record */
+ switch ($record->type) {
+ case Message::TYPE_A:
+ case Message::TYPE_AAAA:
+ $binary = \inet_pton($record->data);
+ break;
+ case Message::TYPE_CNAME:
+ case Message::TYPE_NS:
+ case Message::TYPE_PTR:
+ $binary = $this->domainNameToBinary($record->data);
+ break;
+ case Message::TYPE_TXT:
+ case Message::TYPE_SPF:
+ $binary = $this->textsToBinary($record->data);
+ break;
+ case Message::TYPE_MX:
+ $binary = \pack(
+ 'n',
+ $record->data['priority']
+ );
+ $binary .= $this->domainNameToBinary($record->data['target']);
+ break;
+ case Message::TYPE_SRV:
+ $binary = \pack(
+ 'n*',
+ $record->data['priority'],
+ $record->data['weight'],
+ $record->data['port']
+ );
+ $binary .= $this->domainNameToBinary($record->data['target']);
+ break;
+ case Message::TYPE_SOA:
+ $binary = $this->domainNameToBinary($record->data['mname']);
+ $binary .= $this->domainNameToBinary($record->data['rname']);
+ $binary .= \pack(
+ 'N*',
+ $record->data['serial'],
+ $record->data['refresh'],
+ $record->data['retry'],
+ $record->data['expire'],
+ $record->data['minimum']
+ );
+ break;
+ case Message::TYPE_CAA:
+ $binary = \pack(
+ 'C*',
+ $record->data['flag'],
+ \strlen($record->data['tag'])
+ );
+ $binary .= $record->data['tag'];
+ $binary .= $record->data['value'];
+ break;
+ case Message::TYPE_SSHFP:
+ $binary = \pack(
+ 'CCH*',
+ $record->data['algorithm'],
+ $record->data['type'],
+ $record->data['fingerprint']
+ );
+ break;
+ case Message::TYPE_OPT:
+ $binary = '';
+ foreach ($record->data as $opt => $value) {
+ if ($opt === Message::OPT_TCP_KEEPALIVE && $value !== null) {
+ $value = \pack('n', round($value * 10));
+ }
+ $binary .= \pack('n*', $opt, \strlen((string) $value)) . $value;
+ }
+ break;
+ default:
+ // RDATA is already stored as binary value for unknown record types
+ $binary = $record->data;
+ }
+
+ $data .= $this->domainNameToBinary($record->name);
+ $data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary));
+ $data .= $binary;
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param string[] $texts
+ * @return string
+ */
+ private function textsToBinary(array $texts)
+ {
+ $data = '';
+ foreach ($texts as $text) {
+ $data .= \chr(\strlen($text)) . $text;
+ }
+ return $data;
+ }
+
+ /**
+ * @param string $host
+ * @return string
+ */
+ private function domainNameToBinary($host)
+ {
+ if ($host === '') {
+ return "\0";
+ }
+
+ // break up domain name at each dot that is not preceeded by a backslash (escaped notation)
+ return $this->textsToBinary(
+ \array_map(
+ 'stripcslashes',
+ \preg_split(
+ '/(?<!\\\\)\./',
+ $host . '.'
+ )
+ )
+ );
+ }
+}
diff --git a/vendor/react/dns/src/Protocol/Parser.php b/vendor/react/dns/src/Protocol/Parser.php
new file mode 100644
index 0000000..011a6e7
--- /dev/null
+++ b/vendor/react/dns/src/Protocol/Parser.php
@@ -0,0 +1,356 @@
+<?php
+
+namespace React\Dns\Protocol;
+
+use React\Dns\Model\Message;
+use React\Dns\Model\Record;
+use React\Dns\Query\Query;
+use InvalidArgumentException;
+
+/**
+ * DNS protocol parser
+ *
+ * Obsolete and uncommon types and classes are not implemented.
+ */
+final class Parser
+{
+ /**
+ * Parses the given raw binary message into a Message object
+ *
+ * @param string $data
+ * @throws InvalidArgumentException
+ * @return Message
+ */
+ public function parseMessage($data)
+ {
+ $message = $this->parse($data, 0);
+ if ($message === null) {
+ throw new InvalidArgumentException('Unable to parse binary message');
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param string $data
+ * @param int $consumed
+ * @return ?Message
+ */
+ private function parse($data, $consumed)
+ {
+ if (!isset($data[12 - 1])) {
+ return null;
+ }
+
+ list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', substr($data, 0, 12)));
+
+ $message = new Message();
+ $message->id = $id;
+ $message->rcode = $fields & 0xf;
+ $message->ra = (($fields >> 7) & 1) === 1;
+ $message->rd = (($fields >> 8) & 1) === 1;
+ $message->tc = (($fields >> 9) & 1) === 1;
+ $message->aa = (($fields >> 10) & 1) === 1;
+ $message->opcode = ($fields >> 11) & 0xf;
+ $message->qr = (($fields >> 15) & 1) === 1;
+ $consumed += 12;
+
+ // parse all questions
+ for ($i = $qdCount; $i > 0; --$i) {
+ list($question, $consumed) = $this->parseQuestion($data, $consumed);
+ if ($question === null) {
+ return null;
+ } else {
+ $message->questions[] = $question;
+ }
+ }
+
+ // parse all answer records
+ for ($i = $anCount; $i > 0; --$i) {
+ list($record, $consumed) = $this->parseRecord($data, $consumed);
+ if ($record === null) {
+ return null;
+ } else {
+ $message->answers[] = $record;
+ }
+ }
+
+ // parse all authority records
+ for ($i = $nsCount; $i > 0; --$i) {
+ list($record, $consumed) = $this->parseRecord($data, $consumed);
+ if ($record === null) {
+ return null;
+ } else {
+ $message->authority[] = $record;
+ }
+ }
+
+ // parse all additional records
+ for ($i = $arCount; $i > 0; --$i) {
+ list($record, $consumed) = $this->parseRecord($data, $consumed);
+ if ($record === null) {
+ return null;
+ } else {
+ $message->additional[] = $record;
+ }
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param string $data
+ * @param int $consumed
+ * @return array
+ */
+ private function parseQuestion($data, $consumed)
+ {
+ list($labels, $consumed) = $this->readLabels($data, $consumed);
+
+ if ($labels === null || !isset($data[$consumed + 4 - 1])) {
+ return array(null, null);
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
+ $consumed += 4;
+
+ return array(
+ new Query(
+ implode('.', $labels),
+ $type,
+ $class
+ ),
+ $consumed
+ );
+ }
+
+ /**
+ * @param string $data
+ * @param int $consumed
+ * @return array An array with a parsed Record on success or array with null if data is invalid/incomplete
+ */
+ private function parseRecord($data, $consumed)
+ {
+ list($name, $consumed) = $this->readDomain($data, $consumed);
+
+ if ($name === null || !isset($data[$consumed + 10 - 1])) {
+ return array(null, null);
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
+ $consumed += 4;
+
+ list($ttl) = array_values(unpack('N', substr($data, $consumed, 4)));
+ $consumed += 4;
+
+ // TTL is a UINT32 that must not have most significant bit set for BC reasons
+ if ($ttl < 0 || $ttl >= 1 << 31) {
+ $ttl = 0;
+ }
+
+ list($rdLength) = array_values(unpack('n', substr($data, $consumed, 2)));
+ $consumed += 2;
+
+ if (!isset($data[$consumed + $rdLength - 1])) {
+ return array(null, null);
+ }
+
+ $rdata = null;
+ $expected = $consumed + $rdLength;
+
+ if (Message::TYPE_A === $type) {
+ if ($rdLength === 4) {
+ $rdata = inet_ntop(substr($data, $consumed, $rdLength));
+ $consumed += $rdLength;
+ }
+ } elseif (Message::TYPE_AAAA === $type) {
+ if ($rdLength === 16) {
+ $rdata = inet_ntop(substr($data, $consumed, $rdLength));
+ $consumed += $rdLength;
+ }
+ } elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
+ list($rdata, $consumed) = $this->readDomain($data, $consumed);
+ } elseif (Message::TYPE_TXT === $type || Message::TYPE_SPF === $type) {
+ $rdata = array();
+ while ($consumed < $expected) {
+ $len = ord($data[$consumed]);
+ $rdata[] = (string)substr($data, $consumed + 1, $len);
+ $consumed += $len + 1;
+ }
+ } elseif (Message::TYPE_MX === $type) {
+ if ($rdLength > 2) {
+ list($priority) = array_values(unpack('n', substr($data, $consumed, 2)));
+ list($target, $consumed) = $this->readDomain($data, $consumed + 2);
+
+ $rdata = array(
+ 'priority' => $priority,
+ 'target' => $target
+ );
+ }
+ } elseif (Message::TYPE_SRV === $type) {
+ if ($rdLength > 6) {
+ list($priority, $weight, $port) = array_values(unpack('n*', substr($data, $consumed, 6)));
+ list($target, $consumed) = $this->readDomain($data, $consumed + 6);
+
+ $rdata = array(
+ 'priority' => $priority,
+ 'weight' => $weight,
+ 'port' => $port,
+ 'target' => $target
+ );
+ }
+ } elseif (Message::TYPE_SSHFP === $type) {
+ if ($rdLength > 2) {
+ list($algorithm, $hash) = \array_values(\unpack('C*', \substr($data, $consumed, 2)));
+ $fingerprint = \bin2hex(\substr($data, $consumed + 2, $rdLength - 2));
+ $consumed += $rdLength;
+
+ $rdata = array(
+ 'algorithm' => $algorithm,
+ 'type' => $hash,
+ 'fingerprint' => $fingerprint
+ );
+ }
+ } elseif (Message::TYPE_SOA === $type) {
+ list($mname, $consumed) = $this->readDomain($data, $consumed);
+ list($rname, $consumed) = $this->readDomain($data, $consumed);
+
+ if ($mname !== null && $rname !== null && isset($data[$consumed + 20 - 1])) {
+ list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($data, $consumed, 20)));
+ $consumed += 20;
+
+ $rdata = array(
+ 'mname' => $mname,
+ 'rname' => $rname,
+ 'serial' => $serial,
+ 'refresh' => $refresh,
+ 'retry' => $retry,
+ 'expire' => $expire,
+ 'minimum' => $minimum
+ );
+ }
+ } elseif (Message::TYPE_OPT === $type) {
+ $rdata = array();
+ while (isset($data[$consumed + 4 - 1])) {
+ list($code, $length) = array_values(unpack('n*', substr($data, $consumed, 4)));
+ $value = (string) substr($data, $consumed + 4, $length);
+ if ($code === Message::OPT_TCP_KEEPALIVE && $value === '') {
+ $value = null;
+ } elseif ($code === Message::OPT_TCP_KEEPALIVE && $length === 2) {
+ list($value) = array_values(unpack('n', $value));
+ $value = round($value * 0.1, 1);
+ } elseif ($code === Message::OPT_TCP_KEEPALIVE) {
+ break;
+ }
+ $rdata[$code] = $value;
+ $consumed += 4 + $length;
+ }
+ } elseif (Message::TYPE_CAA === $type) {
+ if ($rdLength > 3) {
+ list($flag, $tagLength) = array_values(unpack('C*', substr($data, $consumed, 2)));
+
+ if ($tagLength > 0 && $rdLength - 2 - $tagLength > 0) {
+ $tag = substr($data, $consumed + 2, $tagLength);
+ $value = substr($data, $consumed + 2 + $tagLength, $rdLength - 2 - $tagLength);
+ $consumed += $rdLength;
+
+ $rdata = array(
+ 'flag' => $flag,
+ 'tag' => $tag,
+ 'value' => $value
+ );
+ }
+ }
+ } else {
+ // unknown types simply parse rdata as an opaque binary string
+ $rdata = substr($data, $consumed, $rdLength);
+ $consumed += $rdLength;
+ }
+
+ // ensure parsing record data consumes expact number of bytes indicated in record length
+ if ($consumed !== $expected || $rdata === null) {
+ return array(null, null);
+ }
+
+ return array(
+ new Record($name, $type, $class, $ttl, $rdata),
+ $consumed
+ );
+ }
+
+ private function readDomain($data, $consumed)
+ {
+ list ($labels, $consumed) = $this->readLabels($data, $consumed);
+
+ if ($labels === null) {
+ return array(null, null);
+ }
+
+ // use escaped notation for each label part, then join using dots
+ return array(
+ \implode(
+ '.',
+ \array_map(
+ function ($label) {
+ return \addcslashes($label, "\0..\40.\177");
+ },
+ $labels
+ )
+ ),
+ $consumed
+ );
+ }
+
+ /**
+ * @param string $data
+ * @param int $consumed
+ * @param int $compressionDepth maximum depth for compressed labels to avoid unreasonable recursion
+ * @return array
+ */
+ private function readLabels($data, $consumed, $compressionDepth = 127)
+ {
+ $labels = array();
+
+ while (true) {
+ if (!isset($data[$consumed])) {
+ return array(null, null);
+ }
+
+ $length = \ord($data[$consumed]);
+
+ // end of labels reached
+ if ($length === 0) {
+ $consumed += 1;
+ break;
+ }
+
+ // first two bits set? this is a compressed label (14 bit pointer offset)
+ if (($length & 0xc0) === 0xc0 && isset($data[$consumed + 1]) && $compressionDepth) {
+ $offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
+ if ($offset >= $consumed) {
+ return array(null, null);
+ }
+
+ $consumed += 2;
+ list($newLabels) = $this->readLabels($data, $offset, $compressionDepth - 1);
+
+ if ($newLabels === null) {
+ return array(null, null);
+ }
+
+ $labels = array_merge($labels, $newLabels);
+ break;
+ }
+
+ // length MUST be 0-63 (6 bits only) and data has to be large enough
+ if ($length & 0xc0 || !isset($data[$consumed + $length - 1])) {
+ return array(null, null);
+ }
+
+ $labels[] = substr($data, $consumed + 1, $length);
+ $consumed += $length + 1;
+ }
+
+ return array($labels, $consumed);
+ }
+}
diff --git a/vendor/react/dns/src/Query/CachingExecutor.php b/vendor/react/dns/src/Query/CachingExecutor.php
new file mode 100644
index 0000000..e530b24
--- /dev/null
+++ b/vendor/react/dns/src/Query/CachingExecutor.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Cache\CacheInterface;
+use React\Dns\Model\Message;
+use React\Promise\Promise;
+
+final class CachingExecutor implements ExecutorInterface
+{
+ /**
+ * Default TTL for negative responses (NXDOMAIN etc.).
+ *
+ * @internal
+ */
+ const TTL = 60;
+
+ private $executor;
+ private $cache;
+
+ public function __construct(ExecutorInterface $executor, CacheInterface $cache)
+ {
+ $this->executor = $executor;
+ $this->cache = $cache;
+ }
+
+ public function query(Query $query)
+ {
+ $id = $query->name . ':' . $query->type . ':' . $query->class;
+ $cache = $this->cache;
+ $that = $this;
+ $executor = $this->executor;
+
+ $pending = $cache->get($id);
+ return new Promise(function ($resolve, $reject) use ($query, $id, $cache, $executor, &$pending, $that) {
+ $pending->then(
+ function ($message) use ($query, $id, $cache, $executor, &$pending, $that) {
+ // return cached response message on cache hit
+ if ($message !== null) {
+ return $message;
+ }
+
+ // perform DNS lookup if not already cached
+ return $pending = $executor->query($query)->then(
+ function (Message $message) use ($cache, $id, $that) {
+ // DNS response message received => store in cache when not truncated and return
+ if (!$message->tc) {
+ $cache->set($id, $message, $that->ttl($message));
+ }
+
+ return $message;
+ }
+ );
+ }
+ )->then($resolve, function ($e) use ($reject, &$pending) {
+ $reject($e);
+ $pending = null;
+ });
+ }, function ($_, $reject) use (&$pending, $query) {
+ $reject(new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled'));
+ $pending->cancel();
+ $pending = null;
+ });
+ }
+
+ /**
+ * @param Message $message
+ * @return int
+ * @internal
+ */
+ public function ttl(Message $message)
+ {
+ // select TTL from answers (should all be the same), use smallest value if available
+ // @link https://tools.ietf.org/html/rfc2181#section-5.2
+ $ttl = null;
+ foreach ($message->answers as $answer) {
+ if ($ttl === null || $answer->ttl < $ttl) {
+ $ttl = $answer->ttl;
+ }
+ }
+
+ if ($ttl === null) {
+ $ttl = self::TTL;
+ }
+
+ return $ttl;
+ }
+}
diff --git a/vendor/react/dns/src/Query/CancellationException.php b/vendor/react/dns/src/Query/CancellationException.php
new file mode 100644
index 0000000..5432b36
--- /dev/null
+++ b/vendor/react/dns/src/Query/CancellationException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns\Query;
+
+final class CancellationException extends \RuntimeException
+{
+}
diff --git a/vendor/react/dns/src/Query/CoopExecutor.php b/vendor/react/dns/src/Query/CoopExecutor.php
new file mode 100644
index 0000000..e3f913b
--- /dev/null
+++ b/vendor/react/dns/src/Query/CoopExecutor.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Promise\Promise;
+
+/**
+ * Cooperatively resolves hosts via the given base executor to ensure same query is not run concurrently
+ *
+ * Wraps an existing `ExecutorInterface` to keep tracking of pending queries
+ * and only starts a new query when the same query is not already pending. Once
+ * the underlying query is fulfilled/rejected, it will forward its value to all
+ * promises awaiting the same query.
+ *
+ * This means it will not limit concurrency for queries that differ, for example
+ * when sending many queries for different host names or types.
+ *
+ * This is useful because all executors are entirely async and as such allow you
+ * to execute any number of queries concurrently. You should probably limit the
+ * number of concurrent queries in your application or you're very likely going
+ * to face rate limitations and bans on the resolver end. For many common
+ * applications, you may want to avoid sending the same query multiple times
+ * when the first one is still pending, so you will likely want to use this in
+ * combination with some other executor like this:
+ *
+ * ```php
+ * $executor = new CoopExecutor(
+ * new RetryExecutor(
+ * new TimeoutExecutor(
+ * new UdpTransportExecutor($nameserver),
+ * 3.0
+ * )
+ * )
+ * );
+ * ```
+ */
+final class CoopExecutor implements ExecutorInterface
+{
+ private $executor;
+ private $pending = array();
+ private $counts = array();
+
+ public function __construct(ExecutorInterface $base)
+ {
+ $this->executor = $base;
+ }
+
+ public function query(Query $query)
+ {
+ $key = $this->serializeQueryToIdentity($query);
+ if (isset($this->pending[$key])) {
+ // same query is already pending, so use shared reference to pending query
+ $promise = $this->pending[$key];
+ ++$this->counts[$key];
+ } else {
+ // no such query pending, so start new query and keep reference until it's fulfilled or rejected
+ $promise = $this->executor->query($query);
+ $this->pending[$key] = $promise;
+ $this->counts[$key] = 1;
+
+ $pending =& $this->pending;
+ $counts =& $this->counts;
+ $promise->then(function () use ($key, &$pending, &$counts) {
+ unset($pending[$key], $counts[$key]);
+ }, function () use ($key, &$pending, &$counts) {
+ unset($pending[$key], $counts[$key]);
+ });
+ }
+
+ // Return a child promise awaiting the pending query.
+ // Cancelling this child promise should only cancel the pending query
+ // when no other child promise is awaiting the same query.
+ $pending =& $this->pending;
+ $counts =& $this->counts;
+ return new Promise(function ($resolve, $reject) use ($promise) {
+ $promise->then($resolve, $reject);
+ }, function () use (&$promise, $key, $query, &$pending, &$counts) {
+ if (--$counts[$key] < 1) {
+ unset($pending[$key], $counts[$key]);
+ $promise->cancel();
+ $promise = null;
+ }
+ throw new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled');
+ });
+ }
+
+ private function serializeQueryToIdentity(Query $query)
+ {
+ return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
+ }
+}
diff --git a/vendor/react/dns/src/Query/ExecutorInterface.php b/vendor/react/dns/src/Query/ExecutorInterface.php
new file mode 100644
index 0000000..b356dc6
--- /dev/null
+++ b/vendor/react/dns/src/Query/ExecutorInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace React\Dns\Query;
+
+interface ExecutorInterface
+{
+ /**
+ * Executes a query and will return a response message
+ *
+ * It returns a Promise which either fulfills with a response
+ * `React\Dns\Model\Message` on success or rejects with an `Exception` if
+ * the query is not successful. A response message may indicate an error
+ * condition in its `rcode`, but this is considered a valid response message.
+ *
+ * ```php
+ * $executor->query($query)->then(
+ * function (React\Dns\Model\Message $response) {
+ * // response message successfully received
+ * var_dump($response->rcode, $response->answers);
+ * },
+ * function (Exception $error) {
+ * // failed to query due to $error
+ * }
+ * );
+ * ```
+ *
+ * The returned Promise MUST be implemented in such a way that it can be
+ * cancelled when it is still pending. Cancelling a pending promise MUST
+ * reject its value with an Exception. It SHOULD clean up any underlying
+ * resources and references as applicable.
+ *
+ * ```php
+ * $promise = $executor->query($query);
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param Query $query
+ * @return \React\Promise\PromiseInterface<\React\Dns\Model\Message,\Exception>
+ * resolves with response message on success or rejects with an Exception on error
+ */
+ public function query(Query $query);
+}
diff --git a/vendor/react/dns/src/Query/FallbackExecutor.php b/vendor/react/dns/src/Query/FallbackExecutor.php
new file mode 100644
index 0000000..83bd360
--- /dev/null
+++ b/vendor/react/dns/src/Query/FallbackExecutor.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Promise\Promise;
+
+final class FallbackExecutor implements ExecutorInterface
+{
+ private $executor;
+ private $fallback;
+
+ public function __construct(ExecutorInterface $executor, ExecutorInterface $fallback)
+ {
+ $this->executor = $executor;
+ $this->fallback = $fallback;
+ }
+
+ public function query(Query $query)
+ {
+ $cancelled = false;
+ $fallback = $this->fallback;
+ $promise = $this->executor->query($query);
+
+ return new Promise(function ($resolve, $reject) use (&$promise, $fallback, $query, &$cancelled) {
+ $promise->then($resolve, function (\Exception $e1) use ($fallback, $query, $resolve, $reject, &$cancelled, &$promise) {
+ // reject if primary resolution rejected due to cancellation
+ if ($cancelled) {
+ $reject($e1);
+ return;
+ }
+
+ // start fallback query if primary query rejected
+ $promise = $fallback->query($query)->then($resolve, function (\Exception $e2) use ($e1, $reject) {
+ $append = $e2->getMessage();
+ if (($pos = strpos($append, ':')) !== false) {
+ $append = substr($append, $pos + 2);
+ }
+
+ // reject with combined error message if both queries fail
+ $reject(new \RuntimeException($e1->getMessage() . '. ' . $append));
+ });
+ });
+ }, function () use (&$promise, &$cancelled) {
+ // cancel pending query (primary or fallback)
+ $cancelled = true;
+ $promise->cancel();
+ });
+ }
+}
diff --git a/vendor/react/dns/src/Query/HostsFileExecutor.php b/vendor/react/dns/src/Query/HostsFileExecutor.php
new file mode 100644
index 0000000..d6e2d93
--- /dev/null
+++ b/vendor/react/dns/src/Query/HostsFileExecutor.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Config\HostsFile;
+use React\Dns\Model\Message;
+use React\Dns\Model\Record;
+use React\Promise;
+
+/**
+ * Resolves hosts from the given HostsFile or falls back to another executor
+ *
+ * If the host is found in the hosts file, it will not be passed to the actual
+ * DNS executor. If the host is not found in the hosts file, it will be passed
+ * to the DNS executor as a fallback.
+ */
+final class HostsFileExecutor implements ExecutorInterface
+{
+ private $hosts;
+ private $fallback;
+
+ public function __construct(HostsFile $hosts, ExecutorInterface $fallback)
+ {
+ $this->hosts = $hosts;
+ $this->fallback = $fallback;
+ }
+
+ public function query(Query $query)
+ {
+ if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
+ // forward lookup for type A or AAAA
+ $records = array();
+ $expectsColon = $query->type === Message::TYPE_AAAA;
+ foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
+ // ensure this is an IPv4/IPV6 address according to query type
+ if ((strpos($ip, ':') !== false) === $expectsColon) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $ip);
+ }
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ } elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
+ // reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
+ $ip = $this->getIpFromHost($query->name);
+
+ if ($ip !== null) {
+ $records = array();
+ foreach ($this->hosts->getHostsForIp($ip) as $host) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $host);
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ }
+ }
+
+ return $this->fallback->query($query);
+ }
+
+ private function getIpFromHost($host)
+ {
+ if (substr($host, -13) === '.in-addr.arpa') {
+ // IPv4: read as IP and reverse bytes
+ $ip = @inet_pton(substr($host, 0, -13));
+ if ($ip === false || isset($ip[4])) {
+ return null;
+ }
+
+ return inet_ntop(strrev($ip));
+ } elseif (substr($host, -9) === '.ip6.arpa') {
+ // IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
+ $ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
+ if ($ip === false) {
+ return null;
+ }
+
+ return $ip;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/vendor/react/dns/src/Query/Query.php b/vendor/react/dns/src/Query/Query.php
new file mode 100644
index 0000000..a3dcfb5
--- /dev/null
+++ b/vendor/react/dns/src/Query/Query.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Model\Message;
+
+/**
+ * This class represents a single question in a query/response message
+ *
+ * It uses a structure similar to `\React\Dns\Message\Record`, but does not
+ * contain fields for resulting TTL and resulting record data (IPs etc.).
+ *
+ * @link https://tools.ietf.org/html/rfc1035#section-4.1.2
+ * @see \React\Dns\Message\Record
+ */
+final class Query
+{
+ /**
+ * @var string query name, i.e. hostname to look up
+ */
+ public $name;
+
+ /**
+ * @var int query type (aka QTYPE), see Message::TYPE_* constants
+ */
+ public $type;
+
+ /**
+ * @var int query class (aka QCLASS), see Message::CLASS_IN constant
+ */
+ public $class;
+
+ /**
+ * @param string $name query name, i.e. hostname to look up
+ * @param int $type query type, see Message::TYPE_* constants
+ * @param int $class query class, see Message::CLASS_IN constant
+ */
+ public function __construct($name, $type, $class)
+ {
+ $this->name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ }
+
+ /**
+ * Describes the hostname and query type/class for this query
+ *
+ * The output format is supposed to be human readable and is subject to change.
+ * The format is inspired by RFC 3597 when handling unkown types/classes.
+ *
+ * @return string "example.com (A)" or "example.com (CLASS0 TYPE1234)"
+ * @link https://tools.ietf.org/html/rfc3597
+ */
+ public function describe()
+ {
+ $class = $this->class !== Message::CLASS_IN ? 'CLASS' . $this->class . ' ' : '';
+
+ $type = 'TYPE' . $this->type;
+ $ref = new \ReflectionClass('React\Dns\Model\Message');
+ foreach ($ref->getConstants() as $name => $value) {
+ if ($value === $this->type && \strpos($name, 'TYPE_') === 0) {
+ $type = \substr($name, 5);
+ break;
+ }
+ }
+
+ return $this->name . ' (' . $class . $type . ')';
+ }
+}
diff --git a/vendor/react/dns/src/Query/RetryExecutor.php b/vendor/react/dns/src/Query/RetryExecutor.php
new file mode 100644
index 0000000..7efcacc
--- /dev/null
+++ b/vendor/react/dns/src/Query/RetryExecutor.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Promise\CancellablePromiseInterface;
+use React\Promise\Deferred;
+use React\Promise\PromiseInterface;
+
+final class RetryExecutor implements ExecutorInterface
+{
+ private $executor;
+ private $retries;
+
+ public function __construct(ExecutorInterface $executor, $retries = 2)
+ {
+ $this->executor = $executor;
+ $this->retries = $retries;
+ }
+
+ public function query(Query $query)
+ {
+ return $this->tryQuery($query, $this->retries);
+ }
+
+ public function tryQuery(Query $query, $retries)
+ {
+ $deferred = new Deferred(function () use (&$promise) {
+ if ($promise instanceof CancellablePromiseInterface || (!\interface_exists('React\Promise\CancellablePromiseInterface') && \method_exists($promise, 'cancel'))) {
+ $promise->cancel();
+ }
+ });
+
+ $success = function ($value) use ($deferred, &$errorback) {
+ $errorback = null;
+ $deferred->resolve($value);
+ };
+
+ $executor = $this->executor;
+ $errorback = function ($e) use ($deferred, &$promise, $query, $success, &$errorback, &$retries, $executor) {
+ if (!$e instanceof TimeoutException) {
+ $errorback = null;
+ $deferred->reject($e);
+ } elseif ($retries <= 0) {
+ $errorback = null;
+ $deferred->reject($e = new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: too many retries',
+ 0,
+ $e
+ ));
+
+ // avoid garbage references by replacing all closures in call stack.
+ // what a lovely piece of code!
+ $r = new \ReflectionProperty('Exception', 'trace');
+ $r->setAccessible(true);
+ $trace = $r->getValue($e);
+
+ // Exception trace arguments are not available on some PHP 7.4 installs
+ // @codeCoverageIgnoreStart
+ foreach ($trace as &$one) {
+ if (isset($one['args'])) {
+ foreach ($one['args'] as &$arg) {
+ if ($arg instanceof \Closure) {
+ $arg = 'Object(' . \get_class($arg) . ')';
+ }
+ }
+ }
+ }
+ // @codeCoverageIgnoreEnd
+ $r->setValue($e, $trace);
+ } else {
+ --$retries;
+ $promise = $executor->query($query)->then(
+ $success,
+ $errorback
+ );
+ }
+ };
+
+ $promise = $this->executor->query($query)->then(
+ $success,
+ $errorback
+ );
+
+ return $deferred->promise();
+ }
+}
diff --git a/vendor/react/dns/src/Query/SelectiveTransportExecutor.php b/vendor/react/dns/src/Query/SelectiveTransportExecutor.php
new file mode 100644
index 0000000..0f0ca5d
--- /dev/null
+++ b/vendor/react/dns/src/Query/SelectiveTransportExecutor.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Promise\Promise;
+
+/**
+ * Send DNS queries over a UDP or TCP/IP stream transport.
+ *
+ * This class will automatically choose the correct transport protocol to send
+ * a DNS query to your DNS server. It will always try to send it over the more
+ * efficient UDP transport first. If this query yields a size related issue
+ * (truncated messages), it will retry over a streaming TCP/IP transport.
+ *
+ * For more advanced usages one can utilize this class directly.
+ * The following example looks up the `IPv6` address for `reactphp.org`.
+ *
+ * ```php
+ * $executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);
+ *
+ * $executor->query(
+ * new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
+ * )->then(function (Message $message) {
+ * foreach ($message->answers as $answer) {
+ * echo 'IPv6: ' . $answer->data . PHP_EOL;
+ * }
+ * }, 'printf');
+ * ```
+ *
+ * Note that this executor only implements the logic to select the correct
+ * transport for the given DNS query. Implementing the correct transport logic,
+ * implementing timeouts and any retry logic is left up to the given executors,
+ * see also [`UdpTransportExecutor`](#udptransportexecutor) and
+ * [`TcpTransportExecutor`](#tcptransportexecutor) for more details.
+ *
+ * Note that this executor is entirely async and as such allows you to execute
+ * any number of queries concurrently. You should probably limit the number of
+ * concurrent queries in your application or you're very likely going to face
+ * rate limitations and bans on the resolver end. For many common applications,
+ * you may want to avoid sending the same query multiple times when the first
+ * one is still pending, so you will likely want to use this in combination with
+ * a `CoopExecutor` like this:
+ *
+ * ```php
+ * $executor = new CoopExecutor(
+ * new SelectiveTransportExecutor(
+ * $datagramExecutor,
+ * $streamExecutor
+ * )
+ * );
+ * ```
+ */
+class SelectiveTransportExecutor implements ExecutorInterface
+{
+ private $datagramExecutor;
+ private $streamExecutor;
+
+ public function __construct(ExecutorInterface $datagramExecutor, ExecutorInterface $streamExecutor)
+ {
+ $this->datagramExecutor = $datagramExecutor;
+ $this->streamExecutor = $streamExecutor;
+ }
+
+ public function query(Query $query)
+ {
+ $stream = $this->streamExecutor;
+ $pending = $this->datagramExecutor->query($query);
+
+ return new Promise(function ($resolve, $reject) use (&$pending, $stream, $query) {
+ $pending->then(
+ $resolve,
+ function ($e) use (&$pending, $stream, $query, $resolve, $reject) {
+ if ($e->getCode() === (\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90)) {
+ $pending = $stream->query($query)->then($resolve, $reject);
+ } else {
+ $reject($e);
+ }
+ }
+ );
+ }, function () use (&$pending) {
+ $pending->cancel();
+ $pending = null;
+ });
+ }
+}
diff --git a/vendor/react/dns/src/Query/TcpTransportExecutor.php b/vendor/react/dns/src/Query/TcpTransportExecutor.php
new file mode 100644
index 0000000..6644e16
--- /dev/null
+++ b/vendor/react/dns/src/Query/TcpTransportExecutor.php
@@ -0,0 +1,367 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Model\Message;
+use React\Dns\Protocol\BinaryDumper;
+use React\Dns\Protocol\Parser;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+
+/**
+ * Send DNS queries over a TCP/IP stream transport.
+ *
+ * This is one of the main classes that send a DNS query to your DNS server.
+ *
+ * For more advanced usages one can utilize this class directly.
+ * The following example looks up the `IPv6` address for `reactphp.org`.
+ *
+ * ```php
+ * $executor = new TcpTransportExecutor('8.8.8.8:53');
+ *
+ * $executor->query(
+ * new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
+ * )->then(function (Message $message) {
+ * foreach ($message->answers as $answer) {
+ * echo 'IPv6: ' . $answer->data . PHP_EOL;
+ * }
+ * }, 'printf');
+ * ```
+ *
+ * See also [example #92](examples).
+ *
+ * Note that this executor does not implement a timeout, so you will very likely
+ * want to use this in combination with a `TimeoutExecutor` like this:
+ *
+ * ```php
+ * $executor = new TimeoutExecutor(
+ * new TcpTransportExecutor($nameserver),
+ * 3.0
+ * );
+ * ```
+ *
+ * Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP
+ * transport, so you do not necessarily have to implement any retry logic.
+ *
+ * Note that this executor is entirely async and as such allows you to execute
+ * queries concurrently. The first query will establish a TCP/IP socket
+ * connection to the DNS server which will be kept open for a short period.
+ * Additional queries will automatically reuse this existing socket connection
+ * to the DNS server, will pipeline multiple requests over this single
+ * connection and will keep an idle connection open for a short period. The
+ * initial TCP/IP connection overhead may incur a slight delay if you only send
+ * occasional queries – when sending a larger number of concurrent queries over
+ * an existing connection, it becomes increasingly more efficient and avoids
+ * creating many concurrent sockets like the UDP-based executor. You may still
+ * want to limit the number of (concurrent) queries in your application or you
+ * may be facing rate limitations and bans on the resolver end. For many common
+ * applications, you may want to avoid sending the same query multiple times
+ * when the first one is still pending, so you will likely want to use this in
+ * combination with a `CoopExecutor` like this:
+ *
+ * ```php
+ * $executor = new CoopExecutor(
+ * new TimeoutExecutor(
+ * new TcpTransportExecutor($nameserver),
+ * 3.0
+ * )
+ * );
+ * ```
+ *
+ * > Internally, this class uses PHP's TCP/IP sockets and does not take advantage
+ * of [react/socket](https://github.com/reactphp/socket) purely for
+ * organizational reasons to avoid a cyclic dependency between the two
+ * packages. Higher-level components should take advantage of the Socket
+ * component instead of reimplementing this socket logic from scratch.
+ */
+class TcpTransportExecutor implements ExecutorInterface
+{
+ private $nameserver;
+ private $loop;
+ private $parser;
+ private $dumper;
+
+ /**
+ * @var ?resource
+ */
+ private $socket;
+
+ /**
+ * @var Deferred[]
+ */
+ private $pending = array();
+
+ /**
+ * @var string[]
+ */
+ private $names = array();
+
+ /**
+ * Maximum idle time when socket is current unused (i.e. no pending queries outstanding)
+ *
+ * If a new query is to be sent during the idle period, we can reuse the
+ * existing socket without having to wait for a new socket connection.
+ * This uses a rather small, hard-coded value to not keep any unneeded
+ * sockets open and to not keep the loop busy longer than needed.
+ *
+ * A future implementation may take advantage of `edns-tcp-keepalive` to keep
+ * the socket open for longer periods. This will likely require explicit
+ * configuration because this may consume additional resources and also keep
+ * the loop busy for longer than expected in some applications.
+ *
+ * @var float
+ * @link https://tools.ietf.org/html/rfc7766#section-6.2.1
+ * @link https://tools.ietf.org/html/rfc7828
+ */
+ private $idlePeriod = 0.001;
+
+ /**
+ * @var ?\React\EventLoop\TimerInterface
+ */
+ private $idleTimer;
+
+ private $writeBuffer = '';
+ private $writePending = false;
+
+ private $readBuffer = '';
+ private $readPending = false;
+
+ /** @var string */
+ private $readChunk = 0xffff;
+
+ /**
+ * @param string $nameserver
+ * @param ?LoopInterface $loop
+ */
+ public function __construct($nameserver, LoopInterface $loop = null)
+ {
+ if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
+ // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
+ $nameserver = '[' . $nameserver . ']';
+ }
+
+ $parts = \parse_url((\strpos($nameserver, '://') === false ? 'tcp://' : '') . $nameserver);
+ if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'tcp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
+ throw new \InvalidArgumentException('Invalid nameserver address given');
+ }
+
+ $this->nameserver = 'tcp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
+ $this->loop = $loop ?: Loop::get();
+ $this->parser = new Parser();
+ $this->dumper = new BinaryDumper();
+ }
+
+ public function query(Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ // keep shuffing message ID to avoid using the same message ID for two pending queries at the same time
+ while (isset($this->pending[$request->id])) {
+ $request->id = \mt_rand(0, 0xffff); // @codeCoverageIgnore
+ }
+
+ $queryData = $this->dumper->toBinary($request);
+ $length = \strlen($queryData);
+ if ($length > 0xffff) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: Query too large for TCP transport'
+ ));
+ }
+
+ $queryData = \pack('n', $length) . $queryData;
+
+ if ($this->socket === null) {
+ // create async TCP/IP connection (may take a while)
+ $socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT);
+ if ($socket === false) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
+ $errno
+ ));
+ }
+
+ // set socket to non-blocking and wait for it to become writable (connection success/rejected)
+ \stream_set_blocking($socket, false);
+ if (\function_exists('stream_set_chunk_size')) {
+ \stream_set_chunk_size($socket, $this->readChunk); // @codeCoverageIgnore
+ }
+ $this->socket = $socket;
+ }
+
+ if ($this->idleTimer !== null) {
+ $this->loop->cancelTimer($this->idleTimer);
+ $this->idleTimer = null;
+ }
+
+ // wait for socket to become writable to actually write out data
+ $this->writeBuffer .= $queryData;
+ if (!$this->writePending) {
+ $this->writePending = true;
+ $this->loop->addWriteStream($this->socket, array($this, 'handleWritable'));
+ }
+
+ $names =& $this->names;
+ $that = $this;
+ $deferred = new Deferred(function () use ($that, &$names, $request) {
+ // remove from list of pending names, but remember pending query
+ $name = $names[$request->id];
+ unset($names[$request->id]);
+ $that->checkIdle();
+
+ throw new CancellationException('DNS query for ' . $name . ' has been cancelled');
+ });
+
+ $this->pending[$request->id] = $deferred;
+ $this->names[$request->id] = $query->describe();
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @internal
+ */
+ public function handleWritable()
+ {
+ if ($this->readPending === false) {
+ $name = @\stream_socket_get_name($this->socket, true);
+ if ($name === false) {
+ // Connection failed? Check socket error if available for underlying errno/errstr.
+ // @codeCoverageIgnoreStart
+ if (\function_exists('socket_import_stream')) {
+ $socket = \socket_import_stream($this->socket);
+ $errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
+ $errstr = \socket_strerror($errno);
+ } else {
+ $errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;
+ $errstr = 'Connection refused';
+ }
+ // @codeCoverageIgnoreEnd
+
+ $this->closeError('Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')', $errno);
+ return;
+ }
+
+ $this->readPending = true;
+ $this->loop->addReadStream($this->socket, array($this, 'handleRead'));
+ }
+
+ $written = @\fwrite($this->socket, $this->writeBuffer);
+ if ($written === false || $written === 0) {
+ $error = \error_get_last();
+ \preg_match('/errno=(\d+) (.+)/', $error['message'], $m);
+ $this->closeError(
+ 'Unable to send query to DNS server ' . $this->nameserver . ' (' . (isset($m[2]) ? $m[2] : $error['message']) . ')',
+ isset($m[1]) ? (int) $m[1] : 0
+ );
+ return;
+ }
+
+ if (isset($this->writeBuffer[$written])) {
+ $this->writeBuffer = \substr($this->writeBuffer, $written);
+ } else {
+ $this->loop->removeWriteStream($this->socket);
+ $this->writePending = false;
+ $this->writeBuffer = '';
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public function handleRead()
+ {
+ // read one chunk of data from the DNS server
+ // any error is fatal, this is a stream of TCP/IP data
+ $chunk = @\fread($this->socket, $this->readChunk);
+ if ($chunk === false || $chunk === '') {
+ $this->closeError('Connection to DNS server ' . $this->nameserver . ' lost');
+ return;
+ }
+
+ // reassemble complete message by concatenating all chunks.
+ $this->readBuffer .= $chunk;
+
+ // response message header contains at least 12 bytes
+ while (isset($this->readBuffer[11])) {
+ // read response message length from first 2 bytes and ensure we have length + data in buffer
+ list(, $length) = \unpack('n', $this->readBuffer);
+ if (!isset($this->readBuffer[$length + 1])) {
+ return;
+ }
+
+ $data = \substr($this->readBuffer, 2, $length);
+ $this->readBuffer = (string)substr($this->readBuffer, $length + 2);
+
+ try {
+ $response = $this->parser->parseMessage($data);
+ } catch (\Exception $e) {
+ // reject all pending queries if we received an invalid message from remote server
+ $this->closeError('Invalid message received from DNS server ' . $this->nameserver);
+ return;
+ }
+
+ // reject all pending queries if we received an unexpected response ID or truncated response
+ if (!isset($this->pending[$response->id]) || $response->tc) {
+ $this->closeError('Invalid response message received from DNS server ' . $this->nameserver);
+ return;
+ }
+
+ $deferred = $this->pending[$response->id];
+ unset($this->pending[$response->id], $this->names[$response->id]);
+
+ $deferred->resolve($response);
+
+ $this->checkIdle();
+ }
+ }
+
+ /**
+ * @internal
+ * @param string $reason
+ * @param int $code
+ */
+ public function closeError($reason, $code = 0)
+ {
+ $this->readBuffer = '';
+ if ($this->readPending) {
+ $this->loop->removeReadStream($this->socket);
+ $this->readPending = false;
+ }
+
+ $this->writeBuffer = '';
+ if ($this->writePending) {
+ $this->loop->removeWriteStream($this->socket);
+ $this->writePending = false;
+ }
+
+ if ($this->idleTimer !== null) {
+ $this->loop->cancelTimer($this->idleTimer);
+ $this->idleTimer = null;
+ }
+
+ @\fclose($this->socket);
+ $this->socket = null;
+
+ foreach ($this->names as $id => $name) {
+ $this->pending[$id]->reject(new \RuntimeException(
+ 'DNS query for ' . $name . ' failed: ' . $reason,
+ $code
+ ));
+ }
+ $this->pending = $this->names = array();
+ }
+
+ /**
+ * @internal
+ */
+ public function checkIdle()
+ {
+ if ($this->idleTimer === null && !$this->names) {
+ $that = $this;
+ $this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () use ($that) {
+ $that->closeError('Idle timeout');
+ });
+ }
+ }
+}
diff --git a/vendor/react/dns/src/Query/TimeoutException.php b/vendor/react/dns/src/Query/TimeoutException.php
new file mode 100644
index 0000000..109b0a9
--- /dev/null
+++ b/vendor/react/dns/src/Query/TimeoutException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns\Query;
+
+final class TimeoutException extends \Exception
+{
+}
diff --git a/vendor/react/dns/src/Query/TimeoutExecutor.php b/vendor/react/dns/src/Query/TimeoutExecutor.php
new file mode 100644
index 0000000..15c8c22
--- /dev/null
+++ b/vendor/react/dns/src/Query/TimeoutExecutor.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise\Timer;
+
+final class TimeoutExecutor implements ExecutorInterface
+{
+ private $executor;
+ private $loop;
+ private $timeout;
+
+ public function __construct(ExecutorInterface $executor, $timeout, LoopInterface $loop = null)
+ {
+ $this->executor = $executor;
+ $this->loop = $loop ?: Loop::get();
+ $this->timeout = $timeout;
+ }
+
+ public function query(Query $query)
+ {
+ return Timer\timeout($this->executor->query($query), $this->timeout, $this->loop)->then(null, function ($e) use ($query) {
+ if ($e instanceof Timer\TimeoutException) {
+ $e = new TimeoutException(sprintf("DNS query for %s timed out", $query->describe()), 0, $e);
+ }
+ throw $e;
+ });
+ }
+}
diff --git a/vendor/react/dns/src/Query/UdpTransportExecutor.php b/vendor/react/dns/src/Query/UdpTransportExecutor.php
new file mode 100644
index 0000000..4c995a8
--- /dev/null
+++ b/vendor/react/dns/src/Query/UdpTransportExecutor.php
@@ -0,0 +1,208 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Model\Message;
+use React\Dns\Protocol\BinaryDumper;
+use React\Dns\Protocol\Parser;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+
+/**
+ * Send DNS queries over a UDP transport.
+ *
+ * This is the main class that sends a DNS query to your DNS server and is used
+ * internally by the `Resolver` for the actual message transport.
+ *
+ * For more advanced usages one can utilize this class directly.
+ * The following example looks up the `IPv6` address for `igor.io`.
+ *
+ * ```php
+ * $executor = new UdpTransportExecutor('8.8.8.8:53');
+ *
+ * $executor->query(
+ * new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
+ * )->then(function (Message $message) {
+ * foreach ($message->answers as $answer) {
+ * echo 'IPv6: ' . $answer->data . PHP_EOL;
+ * }
+ * }, 'printf');
+ * ```
+ *
+ * See also the [fourth example](examples).
+ *
+ * Note that this executor does not implement a timeout, so you will very likely
+ * want to use this in combination with a `TimeoutExecutor` like this:
+ *
+ * ```php
+ * $executor = new TimeoutExecutor(
+ * new UdpTransportExecutor($nameserver),
+ * 3.0
+ * );
+ * ```
+ *
+ * Also note that this executor uses an unreliable UDP transport and that it
+ * does not implement any retry logic, so you will likely want to use this in
+ * combination with a `RetryExecutor` like this:
+ *
+ * ```php
+ * $executor = new RetryExecutor(
+ * new TimeoutExecutor(
+ * new UdpTransportExecutor($nameserver),
+ * 3.0
+ * )
+ * );
+ * ```
+ *
+ * Note that this executor is entirely async and as such allows you to execute
+ * any number of queries concurrently. You should probably limit the number of
+ * concurrent queries in your application or you're very likely going to face
+ * rate limitations and bans on the resolver end. For many common applications,
+ * you may want to avoid sending the same query multiple times when the first
+ * one is still pending, so you will likely want to use this in combination with
+ * a `CoopExecutor` like this:
+ *
+ * ```php
+ * $executor = new CoopExecutor(
+ * new RetryExecutor(
+ * new TimeoutExecutor(
+ * new UdpTransportExecutor($nameserver),
+ * 3.0
+ * )
+ * )
+ * );
+ * ```
+ *
+ * > Internally, this class uses PHP's UDP sockets and does not take advantage
+ * of [react/datagram](https://github.com/reactphp/datagram) purely for
+ * organizational reasons to avoid a cyclic dependency between the two
+ * packages. Higher-level components should take advantage of the Datagram
+ * component instead of reimplementing this socket logic from scratch.
+ */
+final class UdpTransportExecutor implements ExecutorInterface
+{
+ private $nameserver;
+ private $loop;
+ private $parser;
+ private $dumper;
+
+ /**
+ * maximum UDP packet size to send and receive
+ *
+ * @var int
+ */
+ private $maxPacketSize = 512;
+
+ /**
+ * @param string $nameserver
+ * @param ?LoopInterface $loop
+ */
+ public function __construct($nameserver, LoopInterface $loop = null)
+ {
+ if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
+ // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
+ $nameserver = '[' . $nameserver . ']';
+ }
+
+ $parts = \parse_url((\strpos($nameserver, '://') === false ? 'udp://' : '') . $nameserver);
+ if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'udp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
+ throw new \InvalidArgumentException('Invalid nameserver address given');
+ }
+
+ $this->nameserver = 'udp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
+ $this->loop = $loop ?: Loop::get();
+ $this->parser = new Parser();
+ $this->dumper = new BinaryDumper();
+ }
+
+ public function query(Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ $queryData = $this->dumper->toBinary($request);
+ if (isset($queryData[$this->maxPacketSize])) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: Query too large for UDP transport',
+ \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
+ ));
+ }
+
+ // UDP connections are instant, so try connection without a loop or timeout
+ $socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0);
+ if ($socket === false) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
+ $errno
+ ));
+ }
+
+ // set socket to non-blocking and immediately try to send (fill write buffer)
+ \stream_set_blocking($socket, false);
+ $written = @\fwrite($socket, $queryData);
+
+ if ($written !== \strlen($queryData)) {
+ // Write may potentially fail, but most common errors are already caught by connection check above.
+ // Among others, macOS is known to report here when trying to send to broadcast address.
+ // This can also be reproduced by writing data exceeding `stream_set_chunk_size()` to a server refusing UDP data.
+ // fwrite(): send of 8192 bytes failed with errno=111 Connection refused
+ $error = \error_get_last();
+ \preg_match('/errno=(\d+) (.+)/', $error['message'], $m);
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: Unable to send query to DNS server ' . $this->nameserver . ' (' . (isset($m[2]) ? $m[2] : $error['message']) . ')',
+ isset($m[1]) ? (int) $m[1] : 0
+ ));
+ }
+
+ $loop = $this->loop;
+ $deferred = new Deferred(function () use ($loop, $socket, $query) {
+ // cancellation should remove socket from loop and close socket
+ $loop->removeReadStream($socket);
+ \fclose($socket);
+
+ throw new CancellationException('DNS query for ' . $query->describe() . ' has been cancelled');
+ });
+
+ $max = $this->maxPacketSize;
+ $parser = $this->parser;
+ $nameserver = $this->nameserver;
+ $loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request, $max, $nameserver) {
+ // try to read a single data packet from the DNS server
+ // ignoring any errors, this is uses UDP packets and not a stream of data
+ $data = @\fread($socket, $max);
+ if ($data === false) {
+ return;
+ }
+
+ try {
+ $response = $parser->parseMessage($data);
+ } catch (\Exception $e) {
+ // ignore and await next if we received an invalid message from remote server
+ // this may as well be a fake response from an attacker (possible DOS)
+ return;
+ }
+
+ // ignore and await next if we received an unexpected response ID
+ // this may as well be a fake response from an attacker (possible cache poisoning)
+ if ($response->id !== $request->id) {
+ return;
+ }
+
+ // we only react to the first valid message, so remove socket from loop and close
+ $loop->removeReadStream($socket);
+ \fclose($socket);
+
+ if ($response->tc) {
+ $deferred->reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: The DNS server ' . $nameserver . ' returned a truncated result for a UDP query',
+ \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
+ ));
+ return;
+ }
+
+ $deferred->resolve($response);
+ });
+
+ return $deferred->promise();
+ }
+}
diff --git a/vendor/react/dns/src/RecordNotFoundException.php b/vendor/react/dns/src/RecordNotFoundException.php
new file mode 100644
index 0000000..3b70274
--- /dev/null
+++ b/vendor/react/dns/src/RecordNotFoundException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns;
+
+final class RecordNotFoundException extends \Exception
+{
+}
diff --git a/vendor/react/dns/src/Resolver/Factory.php b/vendor/react/dns/src/Resolver/Factory.php
new file mode 100644
index 0000000..5fe608c
--- /dev/null
+++ b/vendor/react/dns/src/Resolver/Factory.php
@@ -0,0 +1,214 @@
+<?php
+
+namespace React\Dns\Resolver;
+
+use React\Cache\ArrayCache;
+use React\Cache\CacheInterface;
+use React\Dns\Config\Config;
+use React\Dns\Config\HostsFile;
+use React\Dns\Query\CachingExecutor;
+use React\Dns\Query\CoopExecutor;
+use React\Dns\Query\ExecutorInterface;
+use React\Dns\Query\FallbackExecutor;
+use React\Dns\Query\HostsFileExecutor;
+use React\Dns\Query\RetryExecutor;
+use React\Dns\Query\SelectiveTransportExecutor;
+use React\Dns\Query\TcpTransportExecutor;
+use React\Dns\Query\TimeoutExecutor;
+use React\Dns\Query\UdpTransportExecutor;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+
+final class Factory
+{
+ /**
+ * Creates a DNS resolver instance for the given DNS config
+ *
+ * As of v1.7.0 it's recommended to pass a `Config` object instead of a
+ * single nameserver address. If the given config contains more than one DNS
+ * nameserver, all DNS nameservers will be used in order. The primary DNS
+ * server will always be used first before falling back to the secondary or
+ * tertiary DNS server.
+ *
+ * @param Config|string $config DNS Config object (recommended) or single nameserver address
+ * @param ?LoopInterface $loop
+ * @return \React\Dns\Resolver\ResolverInterface
+ * @throws \InvalidArgumentException for invalid DNS server address
+ * @throws \UnderflowException when given DNS Config object has an empty list of nameservers
+ */
+ public function create($config, LoopInterface $loop = null)
+ {
+ $executor = $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get()));
+
+ return new Resolver($executor);
+ }
+
+ /**
+ * Creates a cached DNS resolver instance for the given DNS config and cache
+ *
+ * As of v1.7.0 it's recommended to pass a `Config` object instead of a
+ * single nameserver address. If the given config contains more than one DNS
+ * nameserver, all DNS nameservers will be used in order. The primary DNS
+ * server will always be used first before falling back to the secondary or
+ * tertiary DNS server.
+ *
+ * @param Config|string $config DNS Config object (recommended) or single nameserver address
+ * @param ?LoopInterface $loop
+ * @param ?CacheInterface $cache
+ * @return \React\Dns\Resolver\ResolverInterface
+ * @throws \InvalidArgumentException for invalid DNS server address
+ * @throws \UnderflowException when given DNS Config object has an empty list of nameservers
+ */
+ public function createCached($config, LoopInterface $loop = null, CacheInterface $cache = null)
+ {
+ // default to keeping maximum of 256 responses in cache unless explicitly given
+ if (!($cache instanceof CacheInterface)) {
+ $cache = new ArrayCache(256);
+ }
+
+ $executor = $this->createExecutor($config, $loop ?: Loop::get());
+ $executor = new CachingExecutor($executor, $cache);
+ $executor = $this->decorateHostsFileExecutor($executor);
+
+ return new Resolver($executor);
+ }
+
+ /**
+ * Tries to load the hosts file and decorates the given executor on success
+ *
+ * @param ExecutorInterface $executor
+ * @return ExecutorInterface
+ * @codeCoverageIgnore
+ */
+ private function decorateHostsFileExecutor(ExecutorInterface $executor)
+ {
+ try {
+ $executor = new HostsFileExecutor(
+ HostsFile::loadFromPathBlocking(),
+ $executor
+ );
+ } catch (\RuntimeException $e) {
+ // ignore this file if it can not be loaded
+ }
+
+ // Windows does not store localhost in hosts file by default but handles this internally
+ // To compensate for this, we explicitly use hard-coded defaults for localhost
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $executor = new HostsFileExecutor(
+ new HostsFile("127.0.0.1 localhost\n::1 localhost"),
+ $executor
+ );
+ }
+
+ return $executor;
+ }
+
+ /**
+ * @param Config|string $nameserver
+ * @param LoopInterface $loop
+ * @return CoopExecutor
+ * @throws \InvalidArgumentException for invalid DNS server address
+ * @throws \UnderflowException when given DNS Config object has an empty list of nameservers
+ */
+ private function createExecutor($nameserver, LoopInterface $loop)
+ {
+ if ($nameserver instanceof Config) {
+ if (!$nameserver->nameservers) {
+ throw new \UnderflowException('Empty config with no DNS servers');
+ }
+
+ // Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config).
+ // Note to future self: Recursion isn't too hard, but how deep do we really want to go?
+ $primary = reset($nameserver->nameservers);
+ $secondary = next($nameserver->nameservers);
+ $tertiary = next($nameserver->nameservers);
+
+ if ($tertiary !== false) {
+ // 3 DNS servers given => nest first with fallback for second and third
+ return new CoopExecutor(
+ new RetryExecutor(
+ new FallbackExecutor(
+ $this->createSingleExecutor($primary, $loop),
+ new FallbackExecutor(
+ $this->createSingleExecutor($secondary, $loop),
+ $this->createSingleExecutor($tertiary, $loop)
+ )
+ )
+ )
+ );
+ } elseif ($secondary !== false) {
+ // 2 DNS servers given => fallback from first to second
+ return new CoopExecutor(
+ new RetryExecutor(
+ new FallbackExecutor(
+ $this->createSingleExecutor($primary, $loop),
+ $this->createSingleExecutor($secondary, $loop)
+ )
+ )
+ );
+ } else {
+ // 1 DNS server given => use single executor
+ $nameserver = $primary;
+ }
+ }
+
+ return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop)));
+ }
+
+ /**
+ * @param string $nameserver
+ * @param LoopInterface $loop
+ * @return ExecutorInterface
+ * @throws \InvalidArgumentException for invalid DNS server address
+ */
+ private function createSingleExecutor($nameserver, LoopInterface $loop)
+ {
+ $parts = \parse_url($nameserver);
+
+ if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') {
+ $executor = $this->createTcpExecutor($nameserver, $loop);
+ } elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') {
+ $executor = $this->createUdpExecutor($nameserver, $loop);
+ } else {
+ $executor = new SelectiveTransportExecutor(
+ $this->createUdpExecutor($nameserver, $loop),
+ $this->createTcpExecutor($nameserver, $loop)
+ );
+ }
+
+ return $executor;
+ }
+
+ /**
+ * @param string $nameserver
+ * @param LoopInterface $loop
+ * @return TimeoutExecutor
+ * @throws \InvalidArgumentException for invalid DNS server address
+ */
+ private function createTcpExecutor($nameserver, LoopInterface $loop)
+ {
+ return new TimeoutExecutor(
+ new TcpTransportExecutor($nameserver, $loop),
+ 5.0,
+ $loop
+ );
+ }
+
+ /**
+ * @param string $nameserver
+ * @param LoopInterface $loop
+ * @return TimeoutExecutor
+ * @throws \InvalidArgumentException for invalid DNS server address
+ */
+ private function createUdpExecutor($nameserver, LoopInterface $loop)
+ {
+ return new TimeoutExecutor(
+ new UdpTransportExecutor(
+ $nameserver,
+ $loop
+ ),
+ 5.0,
+ $loop
+ );
+ }
+}
diff --git a/vendor/react/dns/src/Resolver/Resolver.php b/vendor/react/dns/src/Resolver/Resolver.php
new file mode 100644
index 0000000..92926f3
--- /dev/null
+++ b/vendor/react/dns/src/Resolver/Resolver.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace React\Dns\Resolver;
+
+use React\Dns\Model\Message;
+use React\Dns\Query\ExecutorInterface;
+use React\Dns\Query\Query;
+use React\Dns\RecordNotFoundException;
+
+/**
+ * @see ResolverInterface for the base interface
+ */
+final class Resolver implements ResolverInterface
+{
+ private $executor;
+
+ public function __construct(ExecutorInterface $executor)
+ {
+ $this->executor = $executor;
+ }
+
+ public function resolve($domain)
+ {
+ return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
+ return $ips[array_rand($ips)];
+ });
+ }
+
+ public function resolveAll($domain, $type)
+ {
+ $query = new Query($domain, $type, Message::CLASS_IN);
+ $that = $this;
+
+ return $this->executor->query(
+ $query
+ )->then(function (Message $response) use ($query, $that) {
+ return $that->extractValues($query, $response);
+ });
+ }
+
+ /**
+ * [Internal] extract all resource record values from response for this query
+ *
+ * @param Query $query
+ * @param Message $response
+ * @return array
+ * @throws RecordNotFoundException when response indicates an error or contains no data
+ * @internal
+ */
+ public function extractValues(Query $query, Message $response)
+ {
+ // reject if response code indicates this is an error response message
+ $code = $response->rcode;
+ if ($code !== Message::RCODE_OK) {
+ switch ($code) {
+ case Message::RCODE_FORMAT_ERROR:
+ $message = 'Format Error';
+ break;
+ case Message::RCODE_SERVER_FAILURE:
+ $message = 'Server Failure';
+ break;
+ case Message::RCODE_NAME_ERROR:
+ $message = 'Non-Existent Domain / NXDOMAIN';
+ break;
+ case Message::RCODE_NOT_IMPLEMENTED:
+ $message = 'Not Implemented';
+ break;
+ case Message::RCODE_REFUSED:
+ $message = 'Refused';
+ break;
+ default:
+ $message = 'Unknown error response code ' . $code;
+ }
+ throw new RecordNotFoundException(
+ 'DNS query for ' . $query->describe() . ' returned an error response (' . $message . ')',
+ $code
+ );
+ }
+
+ $answers = $response->answers;
+ $addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);
+
+ // reject if we did not receive a valid answer (domain is valid, but no record for this type could be found)
+ if (0 === count($addresses)) {
+ throw new RecordNotFoundException(
+ 'DNS query for ' . $query->describe() . ' did not return a valid answer (NOERROR / NODATA)'
+ );
+ }
+
+ return array_values($addresses);
+ }
+
+ /**
+ * @param \React\Dns\Model\Record[] $answers
+ * @param string $name
+ * @param int $type
+ * @return array
+ */
+ private function valuesByNameAndType(array $answers, $name, $type)
+ {
+ // return all record values for this name and type (if any)
+ $named = $this->filterByName($answers, $name);
+ $records = $this->filterByType($named, $type);
+ if ($records) {
+ return $this->mapRecordData($records);
+ }
+
+ // no matching records found? check if there are any matching CNAMEs instead
+ $cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
+ if ($cnameRecords) {
+ $cnames = $this->mapRecordData($cnameRecords);
+ foreach ($cnames as $cname) {
+ $records = array_merge(
+ $records,
+ $this->valuesByNameAndType($answers, $cname, $type)
+ );
+ }
+ }
+
+ return $records;
+ }
+
+ private function filterByName(array $answers, $name)
+ {
+ return $this->filterByField($answers, 'name', $name);
+ }
+
+ private function filterByType(array $answers, $type)
+ {
+ return $this->filterByField($answers, 'type', $type);
+ }
+
+ private function filterByField(array $answers, $field, $value)
+ {
+ $value = strtolower($value);
+ return array_filter($answers, function ($answer) use ($field, $value) {
+ return $value === strtolower($answer->$field);
+ });
+ }
+
+ private function mapRecordData(array $records)
+ {
+ return array_map(function ($record) {
+ return $record->data;
+ }, $records);
+ }
+}
diff --git a/vendor/react/dns/src/Resolver/ResolverInterface.php b/vendor/react/dns/src/Resolver/ResolverInterface.php
new file mode 100644
index 0000000..fe937dc
--- /dev/null
+++ b/vendor/react/dns/src/Resolver/ResolverInterface.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace React\Dns\Resolver;
+
+interface ResolverInterface
+{
+ /**
+ * Resolves the given $domain name to a single IPv4 address (type `A` query).
+ *
+ * ```php
+ * $resolver->resolve('reactphp.org')->then(function ($ip) {
+ * echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
+ * });
+ * ```
+ *
+ * This is one of the main methods in this package. It sends a DNS query
+ * for the given $domain name to your DNS server and returns a single IP
+ * address on success.
+ *
+ * If the DNS server sends a DNS response message that contains more than
+ * one IP address for this query, it will randomly pick one of the IP
+ * addresses from the response. If you want the full list of IP addresses
+ * or want to send a different type of query, you should use the
+ * [`resolveAll()`](#resolveall) method instead.
+ *
+ * If the DNS server sends a DNS response message that indicates an error
+ * code, this method will reject with a `RecordNotFoundException`. Its
+ * message and code can be used to check for the response code.
+ *
+ * If the DNS communication fails and the server does not respond with a
+ * valid response message, this message will reject with an `Exception`.
+ *
+ * Pending DNS queries can be cancelled by cancelling its pending promise like so:
+ *
+ * ```php
+ * $promise = $resolver->resolve('reactphp.org');
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $domain
+ * @return \React\Promise\PromiseInterface<string,\Exception>
+ * resolves with a single IP address on success or rejects with an Exception on error.
+ */
+ public function resolve($domain);
+
+ /**
+ * Resolves all record values for the given $domain name and query $type.
+ *
+ * ```php
+ * $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
+ * echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+ * });
+ *
+ * $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
+ * echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+ * });
+ * ```
+ *
+ * This is one of the main methods in this package. It sends a DNS query
+ * for the given $domain name to your DNS server and returns a list with all
+ * record values on success.
+ *
+ * If the DNS server sends a DNS response message that contains one or more
+ * records for this query, it will return a list with all record values
+ * from the response. You can use the `Message::TYPE_*` constants to control
+ * which type of query will be sent. Note that this method always returns a
+ * list of record values, but each record value type depends on the query
+ * type. For example, it returns the IPv4 addresses for type `A` queries,
+ * the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
+ * `CNAME` and `PTR` queries and structured data for other queries. See also
+ * the `Record` documentation for more details.
+ *
+ * If the DNS server sends a DNS response message that indicates an error
+ * code, this method will reject with a `RecordNotFoundException`. Its
+ * message and code can be used to check for the response code.
+ *
+ * If the DNS communication fails and the server does not respond with a
+ * valid response message, this message will reject with an `Exception`.
+ *
+ * Pending DNS queries can be cancelled by cancelling its pending promise like so:
+ *
+ * ```php
+ * $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $domain
+ * @return \React\Promise\PromiseInterface<array,\Exception>
+ * Resolves with all record values on success or rejects with an Exception on error.
+ */
+ public function resolveAll($domain, $type);
+}
diff --git a/vendor/react/event-loop/LICENSE b/vendor/react/event-loop/LICENSE
new file mode 100644
index 0000000..d6f8901
--- /dev/null
+++ b/vendor/react/event-loop/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/event-loop/composer.json b/vendor/react/event-loop/composer.json
new file mode 100644
index 0000000..d9b032e
--- /dev/null
+++ b/vendor/react/event-loop/composer.json
@@ -0,0 +1,49 @@
+{
+ "name": "react/event-loop",
+ "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
+ "keywords": ["event-loop", "asynchronous"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
+ },
+ "suggest": {
+ "ext-event": "~1.0 for ExtEventLoop",
+ "ext-pcntl": "For signal handling support when using the StreamSelectLoop",
+ "ext-uv": "* for ExtUvLoop"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\EventLoop\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\EventLoop\\": "tests"
+ }
+ }
+}
diff --git a/vendor/react/event-loop/src/ExtEvLoop.php b/vendor/react/event-loop/src/ExtEvLoop.php
new file mode 100644
index 0000000..a3fcec6
--- /dev/null
+++ b/vendor/react/event-loop/src/ExtEvLoop.php
@@ -0,0 +1,253 @@
+<?php
+
+namespace React\EventLoop;
+
+use Ev;
+use EvIo;
+use EvLoop;
+use React\EventLoop\Tick\FutureTickQueue;
+use React\EventLoop\Timer\Timer;
+use SplObjectStorage;
+
+/**
+ * An `ext-ev` based event loop.
+ *
+ * This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev),
+ * that provides an interface to `libev` library.
+ * `libev` itself supports a number of system-specific backends (epoll, kqueue).
+ *
+ * This loop is known to work with PHP 5.4 through PHP 8+.
+ *
+ * @see http://php.net/manual/en/book.ev.php
+ * @see https://bitbucket.org/osmanov/pecl-ev/overview
+ */
+class ExtEvLoop implements LoopInterface
+{
+ /**
+ * @var EvLoop
+ */
+ private $loop;
+
+ /**
+ * @var FutureTickQueue
+ */
+ private $futureTickQueue;
+
+ /**
+ * @var SplObjectStorage
+ */
+ private $timers;
+
+ /**
+ * @var EvIo[]
+ */
+ private $readStreams = array();
+
+ /**
+ * @var EvIo[]
+ */
+ private $writeStreams = array();
+
+ /**
+ * @var bool
+ */
+ private $running;
+
+ /**
+ * @var SignalsHandler
+ */
+ private $signals;
+
+ /**
+ * @var \EvSignal[]
+ */
+ private $signalEvents = array();
+
+ public function __construct()
+ {
+ $this->loop = new EvLoop();
+ $this->futureTickQueue = new FutureTickQueue();
+ $this->timers = new SplObjectStorage();
+ $this->signals = new SignalsHandler();
+ }
+
+ public function addReadStream($stream, $listener)
+ {
+ $key = (int)$stream;
+
+ if (isset($this->readStreams[$key])) {
+ return;
+ }
+
+ $callback = $this->getStreamListenerClosure($stream, $listener);
+ $event = $this->loop->io($stream, Ev::READ, $callback);
+ $this->readStreams[$key] = $event;
+ }
+
+ /**
+ * @param resource $stream
+ * @param callable $listener
+ *
+ * @return \Closure
+ */
+ private function getStreamListenerClosure($stream, $listener)
+ {
+ return function () use ($stream, $listener) {
+ \call_user_func($listener, $stream);
+ };
+ }
+
+ public function addWriteStream($stream, $listener)
+ {
+ $key = (int)$stream;
+
+ if (isset($this->writeStreams[$key])) {
+ return;
+ }
+
+ $callback = $this->getStreamListenerClosure($stream, $listener);
+ $event = $this->loop->io($stream, Ev::WRITE, $callback);
+ $this->writeStreams[$key] = $event;
+ }
+
+ public function removeReadStream($stream)
+ {
+ $key = (int)$stream;
+
+ if (!isset($this->readStreams[$key])) {
+ return;
+ }
+
+ $this->readStreams[$key]->stop();
+ unset($this->readStreams[$key]);
+ }
+
+ public function removeWriteStream($stream)
+ {
+ $key = (int)$stream;
+
+ if (!isset($this->writeStreams[$key])) {
+ return;
+ }
+
+ $this->writeStreams[$key]->stop();
+ unset($this->writeStreams[$key]);
+ }
+
+ public function addTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, false);
+
+ $that = $this;
+ $timers = $this->timers;
+ $callback = function () use ($timer, $timers, $that) {
+ \call_user_func($timer->getCallback(), $timer);
+
+ if ($timers->contains($timer)) {
+ $that->cancelTimer($timer);
+ }
+ };
+
+ $event = $this->loop->timer($timer->getInterval(), 0.0, $callback);
+ $this->timers->attach($timer, $event);
+
+ return $timer;
+ }
+
+ public function addPeriodicTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, true);
+
+ $callback = function () use ($timer) {
+ \call_user_func($timer->getCallback(), $timer);
+ };
+
+ $event = $this->loop->timer($timer->getInterval(), $timer->getInterval(), $callback);
+ $this->timers->attach($timer, $event);
+
+ return $timer;
+ }
+
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if (!isset($this->timers[$timer])) {
+ return;
+ }
+
+ $event = $this->timers[$timer];
+ $event->stop();
+ $this->timers->detach($timer);
+ }
+
+ public function futureTick($listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->futureTickQueue->tick();
+
+ $hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
+ $wasJustStopped = !$this->running;
+ $nothingLeftToDo = !$this->readStreams
+ && !$this->writeStreams
+ && !$this->timers->count()
+ && $this->signals->isEmpty();
+
+ $flags = Ev::RUN_ONCE;
+ if ($wasJustStopped || $hasPendingCallbacks) {
+ $flags |= Ev::RUN_NOWAIT;
+ } elseif ($nothingLeftToDo) {
+ break;
+ }
+
+ $this->loop->run($flags);
+ }
+ }
+
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ public function __destruct()
+ {
+ /** @var TimerInterface $timer */
+ foreach ($this->timers as $timer) {
+ $this->cancelTimer($timer);
+ }
+
+ foreach ($this->readStreams as $key => $stream) {
+ $this->removeReadStream($key);
+ }
+
+ foreach ($this->writeStreams as $key => $stream) {
+ $this->removeWriteStream($key);
+ }
+ }
+
+ public function addSignal($signal, $listener)
+ {
+ $this->signals->add($signal, $listener);
+
+ if (!isset($this->signalEvents[$signal])) {
+ $this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) {
+ $this->signals->call($signal);
+ });
+ }
+ }
+
+ public function removeSignal($signal, $listener)
+ {
+ $this->signals->remove($signal, $listener);
+
+ if (isset($this->signalEvents[$signal])) {
+ $this->signalEvents[$signal]->stop();
+ unset($this->signalEvents[$signal]);
+ }
+ }
+}
diff --git a/vendor/react/event-loop/src/ExtEventLoop.php b/vendor/react/event-loop/src/ExtEventLoop.php
new file mode 100644
index 0000000..b162a40
--- /dev/null
+++ b/vendor/react/event-loop/src/ExtEventLoop.php
@@ -0,0 +1,275 @@
+<?php
+
+namespace React\EventLoop;
+
+use BadMethodCallException;
+use Event;
+use EventBase;
+use React\EventLoop\Tick\FutureTickQueue;
+use React\EventLoop\Timer\Timer;
+use SplObjectStorage;
+
+/**
+ * An `ext-event` based event loop.
+ *
+ * This uses the [`event` PECL extension](https://pecl.php.net/package/event),
+ * that provides an interface to `libevent` library.
+ * `libevent` itself supports a number of system-specific backends (epoll, kqueue).
+ *
+ * This loop is known to work with PHP 5.4 through PHP 8+.
+ *
+ * @link https://pecl.php.net/package/event
+ */
+final class ExtEventLoop implements LoopInterface
+{
+ private $eventBase;
+ private $futureTickQueue;
+ private $timerCallback;
+ private $timerEvents;
+ private $streamCallback;
+ private $readEvents = array();
+ private $writeEvents = array();
+ private $readListeners = array();
+ private $writeListeners = array();
+ private $readRefs = array();
+ private $writeRefs = array();
+ private $running;
+ private $signals;
+ private $signalEvents = array();
+
+ public function __construct()
+ {
+ if (!\class_exists('EventBase', false)) {
+ throw new BadMethodCallException('Cannot create ExtEventLoop, ext-event extension missing');
+ }
+
+ // support arbitrary file descriptors and not just sockets
+ // Windows only has limited file descriptor support, so do not require this (will fail otherwise)
+ // @link http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html#_setting_up_a_complicated_event_base
+ $config = new \EventConfig();
+ if (\DIRECTORY_SEPARATOR !== '\\') {
+ $config->requireFeatures(\EventConfig::FEATURE_FDS);
+ }
+
+ $this->eventBase = new EventBase($config);
+ $this->futureTickQueue = new FutureTickQueue();
+ $this->timerEvents = new SplObjectStorage();
+ $this->signals = new SignalsHandler();
+
+ $this->createTimerCallback();
+ $this->createStreamCallback();
+ }
+
+ public function __destruct()
+ {
+ // explicitly clear all references to Event objects to prevent SEGFAULTs on Windows
+ foreach ($this->timerEvents as $timer) {
+ $this->timerEvents->detach($timer);
+ }
+
+ $this->readEvents = array();
+ $this->writeEvents = array();
+ }
+
+ public function addReadStream($stream, $listener)
+ {
+ $key = (int) $stream;
+ if (isset($this->readListeners[$key])) {
+ return;
+ }
+
+ $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::READ, $this->streamCallback);
+ $event->add();
+ $this->readEvents[$key] = $event;
+ $this->readListeners[$key] = $listener;
+
+ // ext-event does not increase refcount on stream resources for PHP 7+
+ // manually keep track of stream resource to prevent premature garbage collection
+ if (\PHP_VERSION_ID >= 70000) {
+ $this->readRefs[$key] = $stream;
+ }
+ }
+
+ public function addWriteStream($stream, $listener)
+ {
+ $key = (int) $stream;
+ if (isset($this->writeListeners[$key])) {
+ return;
+ }
+
+ $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::WRITE, $this->streamCallback);
+ $event->add();
+ $this->writeEvents[$key] = $event;
+ $this->writeListeners[$key] = $listener;
+
+ // ext-event does not increase refcount on stream resources for PHP 7+
+ // manually keep track of stream resource to prevent premature garbage collection
+ if (\PHP_VERSION_ID >= 70000) {
+ $this->writeRefs[$key] = $stream;
+ }
+ }
+
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readEvents[$key])) {
+ $this->readEvents[$key]->free();
+ unset(
+ $this->readEvents[$key],
+ $this->readListeners[$key],
+ $this->readRefs[$key]
+ );
+ }
+ }
+
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeEvents[$key])) {
+ $this->writeEvents[$key]->free();
+ unset(
+ $this->writeEvents[$key],
+ $this->writeListeners[$key],
+ $this->writeRefs[$key]
+ );
+ }
+ }
+
+ public function addTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, false);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ public function addPeriodicTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, true);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if ($this->timerEvents->contains($timer)) {
+ $this->timerEvents[$timer]->free();
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ public function futureTick($listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ public function addSignal($signal, $listener)
+ {
+ $this->signals->add($signal, $listener);
+
+ if (!isset($this->signalEvents[$signal])) {
+ $this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, array($this->signals, 'call'));
+ $this->signalEvents[$signal]->add();
+ }
+ }
+
+ public function removeSignal($signal, $listener)
+ {
+ $this->signals->remove($signal, $listener);
+
+ if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
+ $this->signalEvents[$signal]->free();
+ unset($this->signalEvents[$signal]);
+ }
+ }
+
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->futureTickQueue->tick();
+
+ $flags = EventBase::LOOP_ONCE;
+ if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EventBase::LOOP_NONBLOCK;
+ } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
+ break;
+ }
+
+ $this->eventBase->loop($flags);
+ }
+ }
+
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Schedule a timer for execution.
+ *
+ * @param TimerInterface $timer
+ */
+ private function scheduleTimer(TimerInterface $timer)
+ {
+ $flags = Event::TIMEOUT;
+
+ if ($timer->isPeriodic()) {
+ $flags |= Event::PERSIST;
+ }
+
+ $event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer);
+ $this->timerEvents[$timer] = $event;
+
+ $event->add($timer->getInterval());
+ }
+
+ /**
+ * Create a callback used as the target of timer events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createTimerCallback()
+ {
+ $timers = $this->timerEvents;
+ $this->timerCallback = function ($_, $__, $timer) use ($timers) {
+ \call_user_func($timer->getCallback(), $timer);
+
+ if (!$timer->isPeriodic() && $timers->contains($timer)) {
+ $this->cancelTimer($timer);
+ }
+ };
+ }
+
+ /**
+ * Create a callback used as the target of stream events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createStreamCallback()
+ {
+ $read =& $this->readListeners;
+ $write =& $this->writeListeners;
+ $this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
+ $key = (int) $stream;
+
+ if (Event::READ === (Event::READ & $flags) && isset($read[$key])) {
+ \call_user_func($read[$key], $stream);
+ }
+
+ if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) {
+ \call_user_func($write[$key], $stream);
+ }
+ };
+ }
+}
diff --git a/vendor/react/event-loop/src/ExtLibevLoop.php b/vendor/react/event-loop/src/ExtLibevLoop.php
new file mode 100644
index 0000000..c303fdd
--- /dev/null
+++ b/vendor/react/event-loop/src/ExtLibevLoop.php
@@ -0,0 +1,201 @@
+<?php
+
+namespace React\EventLoop;
+
+use BadMethodCallException;
+use libev\EventLoop;
+use libev\IOEvent;
+use libev\SignalEvent;
+use libev\TimerEvent;
+use React\EventLoop\Tick\FutureTickQueue;
+use React\EventLoop\Timer\Timer;
+use SplObjectStorage;
+
+/**
+ * [Deprecated] An `ext-libev` based event loop.
+ *
+ * This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev),
+ * that provides an interface to `libev` library.
+ * `libev` itself supports a number of system-specific backends (epoll, kqueue).
+ *
+ * This loop does only work with PHP 5.
+ * An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8)
+ * to happen any time soon.
+ *
+ * @see https://github.com/m4rw3r/php-libev
+ * @see https://gist.github.com/1688204
+ * @deprecated 1.2.0, use [`ExtEvLoop`](#extevloop) instead.
+ */
+final class ExtLibevLoop implements LoopInterface
+{
+ private $loop;
+ private $futureTickQueue;
+ private $timerEvents;
+ private $readEvents = array();
+ private $writeEvents = array();
+ private $running;
+ private $signals;
+ private $signalEvents = array();
+
+ public function __construct()
+ {
+ if (!\class_exists('libev\EventLoop', false)) {
+ throw new BadMethodCallException('Cannot create ExtLibevLoop, ext-libev extension missing');
+ }
+
+ $this->loop = new EventLoop();
+ $this->futureTickQueue = new FutureTickQueue();
+ $this->timerEvents = new SplObjectStorage();
+ $this->signals = new SignalsHandler();
+ }
+
+ public function addReadStream($stream, $listener)
+ {
+ if (isset($this->readEvents[(int) $stream])) {
+ return;
+ }
+
+ $callback = function () use ($stream, $listener) {
+ \call_user_func($listener, $stream);
+ };
+
+ $event = new IOEvent($callback, $stream, IOEvent::READ);
+ $this->loop->add($event);
+
+ $this->readEvents[(int) $stream] = $event;
+ }
+
+ public function addWriteStream($stream, $listener)
+ {
+ if (isset($this->writeEvents[(int) $stream])) {
+ return;
+ }
+
+ $callback = function () use ($stream, $listener) {
+ \call_user_func($listener, $stream);
+ };
+
+ $event = new IOEvent($callback, $stream, IOEvent::WRITE);
+ $this->loop->add($event);
+
+ $this->writeEvents[(int) $stream] = $event;
+ }
+
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readEvents[$key])) {
+ $this->readEvents[$key]->stop();
+ $this->loop->remove($this->readEvents[$key]);
+ unset($this->readEvents[$key]);
+ }
+ }
+
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeEvents[$key])) {
+ $this->writeEvents[$key]->stop();
+ $this->loop->remove($this->writeEvents[$key]);
+ unset($this->writeEvents[$key]);
+ }
+ }
+
+ public function addTimer($interval, $callback)
+ {
+ $timer = new Timer( $interval, $callback, false);
+
+ $that = $this;
+ $timers = $this->timerEvents;
+ $callback = function () use ($timer, $timers, $that) {
+ \call_user_func($timer->getCallback(), $timer);
+
+ if ($timers->contains($timer)) {
+ $that->cancelTimer($timer);
+ }
+ };
+
+ $event = new TimerEvent($callback, $timer->getInterval());
+ $this->timerEvents->attach($timer, $event);
+ $this->loop->add($event);
+
+ return $timer;
+ }
+
+ public function addPeriodicTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, true);
+
+ $callback = function () use ($timer) {
+ \call_user_func($timer->getCallback(), $timer);
+ };
+
+ $event = new TimerEvent($callback, $timer->getInterval(), $timer->getInterval());
+ $this->timerEvents->attach($timer, $event);
+ $this->loop->add($event);
+
+ return $timer;
+ }
+
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if (isset($this->timerEvents[$timer])) {
+ $this->loop->remove($this->timerEvents[$timer]);
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ public function futureTick($listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ public function addSignal($signal, $listener)
+ {
+ $this->signals->add($signal, $listener);
+
+ if (!isset($this->signalEvents[$signal])) {
+ $signals = $this->signals;
+ $this->signalEvents[$signal] = new SignalEvent(function () use ($signals, $signal) {
+ $signals->call($signal);
+ }, $signal);
+ $this->loop->add($this->signalEvents[$signal]);
+ }
+ }
+
+ public function removeSignal($signal, $listener)
+ {
+ $this->signals->remove($signal, $listener);
+
+ if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
+ $this->signalEvents[$signal]->stop();
+ $this->loop->remove($this->signalEvents[$signal]);
+ unset($this->signalEvents[$signal]);
+ }
+ }
+
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->futureTickQueue->tick();
+
+ $flags = EventLoop::RUN_ONCE;
+ if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EventLoop::RUN_NOWAIT;
+ } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
+ break;
+ }
+
+ $this->loop->run($flags);
+ }
+ }
+
+ public function stop()
+ {
+ $this->running = false;
+ }
+}
diff --git a/vendor/react/event-loop/src/ExtLibeventLoop.php b/vendor/react/event-loop/src/ExtLibeventLoop.php
new file mode 100644
index 0000000..099293a
--- /dev/null
+++ b/vendor/react/event-loop/src/ExtLibeventLoop.php
@@ -0,0 +1,285 @@
+<?php
+
+namespace React\EventLoop;
+
+use BadMethodCallException;
+use Event;
+use EventBase;
+use React\EventLoop\Tick\FutureTickQueue;
+use React\EventLoop\Timer\Timer;
+use SplObjectStorage;
+
+/**
+ * [Deprecated] An `ext-libevent` based event loop.
+ *
+ * This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent),
+ * that provides an interface to `libevent` library.
+ * `libevent` itself supports a number of system-specific backends (epoll, kqueue).
+ *
+ * This event loop does only work with PHP 5.
+ * An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for
+ * PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s.
+ * To reiterate: Using this event loop on PHP 7 is not recommended.
+ * Accordingly, neither the [`Loop` class](#loop) nor the deprecated
+ * [`Factory` class](#factory) will try to use this event loop on PHP 7.
+ *
+ * This event loop is known to trigger a readable listener only if
+ * the stream *becomes* readable (edge-triggered) and may not trigger if the
+ * stream has already been readable from the beginning.
+ * This also implies that a stream may not be recognized as readable when data
+ * is still left in PHP's internal stream buffers.
+ * As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
+ * to disable PHP's internal read buffer in this case.
+ * See also [`addReadStream()`](#addreadstream) for more details.
+ *
+ * @link https://pecl.php.net/package/libevent
+ * @deprecated 1.2.0, use [`ExtEventLoop`](#exteventloop) instead.
+ */
+final class ExtLibeventLoop implements LoopInterface
+{
+ /** @internal */
+ const MICROSECONDS_PER_SECOND = 1000000;
+
+ private $eventBase;
+ private $futureTickQueue;
+ private $timerCallback;
+ private $timerEvents;
+ private $streamCallback;
+ private $readEvents = array();
+ private $writeEvents = array();
+ private $readListeners = array();
+ private $writeListeners = array();
+ private $running;
+ private $signals;
+ private $signalEvents = array();
+
+ public function __construct()
+ {
+ if (!\function_exists('event_base_new')) {
+ throw new BadMethodCallException('Cannot create ExtLibeventLoop, ext-libevent extension missing');
+ }
+
+ $this->eventBase = \event_base_new();
+ $this->futureTickQueue = new FutureTickQueue();
+ $this->timerEvents = new SplObjectStorage();
+ $this->signals = new SignalsHandler();
+
+ $this->createTimerCallback();
+ $this->createStreamCallback();
+ }
+
+ public function addReadStream($stream, $listener)
+ {
+ $key = (int) $stream;
+ if (isset($this->readListeners[$key])) {
+ return;
+ }
+
+ $event = \event_new();
+ \event_set($event, $stream, \EV_PERSIST | \EV_READ, $this->streamCallback);
+ \event_base_set($event, $this->eventBase);
+ \event_add($event);
+
+ $this->readEvents[$key] = $event;
+ $this->readListeners[$key] = $listener;
+ }
+
+ public function addWriteStream($stream, $listener)
+ {
+ $key = (int) $stream;
+ if (isset($this->writeListeners[$key])) {
+ return;
+ }
+
+ $event = \event_new();
+ \event_set($event, $stream, \EV_PERSIST | \EV_WRITE, $this->streamCallback);
+ \event_base_set($event, $this->eventBase);
+ \event_add($event);
+
+ $this->writeEvents[$key] = $event;
+ $this->writeListeners[$key] = $listener;
+ }
+
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ $event = $this->readEvents[$key];
+ \event_del($event);
+ \event_free($event);
+
+ unset(
+ $this->readEvents[$key],
+ $this->readListeners[$key]
+ );
+ }
+ }
+
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ $event = $this->writeEvents[$key];
+ \event_del($event);
+ \event_free($event);
+
+ unset(
+ $this->writeEvents[$key],
+ $this->writeListeners[$key]
+ );
+ }
+ }
+
+ public function addTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, false);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ public function addPeriodicTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, true);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if ($this->timerEvents->contains($timer)) {
+ $event = $this->timerEvents[$timer];
+ \event_del($event);
+ \event_free($event);
+
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ public function futureTick($listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ public function addSignal($signal, $listener)
+ {
+ $this->signals->add($signal, $listener);
+
+ if (!isset($this->signalEvents[$signal])) {
+ $this->signalEvents[$signal] = \event_new();
+ \event_set($this->signalEvents[$signal], $signal, \EV_PERSIST | \EV_SIGNAL, array($this->signals, 'call'));
+ \event_base_set($this->signalEvents[$signal], $this->eventBase);
+ \event_add($this->signalEvents[$signal]);
+ }
+ }
+
+ public function removeSignal($signal, $listener)
+ {
+ $this->signals->remove($signal, $listener);
+
+ if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
+ \event_del($this->signalEvents[$signal]);
+ \event_free($this->signalEvents[$signal]);
+ unset($this->signalEvents[$signal]);
+ }
+ }
+
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->futureTickQueue->tick();
+
+ $flags = \EVLOOP_ONCE;
+ if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+ $flags |= \EVLOOP_NONBLOCK;
+ } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
+ break;
+ }
+
+ \event_base_loop($this->eventBase, $flags);
+ }
+ }
+
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Schedule a timer for execution.
+ *
+ * @param TimerInterface $timer
+ */
+ private function scheduleTimer(TimerInterface $timer)
+ {
+ $this->timerEvents[$timer] = $event = \event_timer_new();
+
+ \event_timer_set($event, $this->timerCallback, $timer);
+ \event_base_set($event, $this->eventBase);
+ \event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND);
+ }
+
+ /**
+ * Create a callback used as the target of timer events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createTimerCallback()
+ {
+ $that = $this;
+ $timers = $this->timerEvents;
+ $this->timerCallback = function ($_, $__, $timer) use ($timers, $that) {
+ \call_user_func($timer->getCallback(), $timer);
+
+ // Timer already cancelled ...
+ if (!$timers->contains($timer)) {
+ return;
+ }
+
+ // Reschedule periodic timers ...
+ if ($timer->isPeriodic()) {
+ \event_add(
+ $timers[$timer],
+ $timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND
+ );
+
+ // Clean-up one shot timers ...
+ } else {
+ $that->cancelTimer($timer);
+ }
+ };
+ }
+
+ /**
+ * Create a callback used as the target of stream events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createStreamCallback()
+ {
+ $read =& $this->readListeners;
+ $write =& $this->writeListeners;
+ $this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
+ $key = (int) $stream;
+
+ if (\EV_READ === (\EV_READ & $flags) && isset($read[$key])) {
+ \call_user_func($read[$key], $stream);
+ }
+
+ if (\EV_WRITE === (\EV_WRITE & $flags) && isset($write[$key])) {
+ \call_user_func($write[$key], $stream);
+ }
+ };
+ }
+}
diff --git a/vendor/react/event-loop/src/ExtUvLoop.php b/vendor/react/event-loop/src/ExtUvLoop.php
new file mode 100644
index 0000000..631a459
--- /dev/null
+++ b/vendor/react/event-loop/src/ExtUvLoop.php
@@ -0,0 +1,342 @@
+<?php
+
+namespace React\EventLoop;
+
+use React\EventLoop\Tick\FutureTickQueue;
+use React\EventLoop\Timer\Timer;
+use SplObjectStorage;
+
+/**
+ * An `ext-uv` based event loop.
+ *
+ * This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv),
+ * that provides an interface to `libuv` library.
+ * `libuv` itself supports a number of system-specific backends (epoll, kqueue).
+ *
+ * This loop is known to work with PHP 7.x.
+ *
+ * @see https://github.com/bwoebi/php-uv
+ */
+final class ExtUvLoop implements LoopInterface
+{
+ private $uv;
+ private $futureTickQueue;
+ private $timers;
+ private $streamEvents = array();
+ private $readStreams = array();
+ private $writeStreams = array();
+ private $running;
+ private $signals;
+ private $signalEvents = array();
+ private $streamListener;
+
+ public function __construct()
+ {
+ if (!\function_exists('uv_loop_new')) {
+ throw new \BadMethodCallException('Cannot create LibUvLoop, ext-uv extension missing');
+ }
+
+ $this->uv = \uv_loop_new();
+ $this->futureTickQueue = new FutureTickQueue();
+ $this->timers = new SplObjectStorage();
+ $this->streamListener = $this->createStreamListener();
+ $this->signals = new SignalsHandler();
+ }
+
+ /**
+ * Returns the underlying ext-uv event loop. (Internal ReactPHP use only.)
+ *
+ * @internal
+ *
+ * @return resource
+ */
+ public function getUvLoop()
+ {
+ return $this->uv;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, $listener)
+ {
+ if (isset($this->readStreams[(int) $stream])) {
+ return;
+ }
+
+ $this->readStreams[(int) $stream] = $listener;
+ $this->addStream($stream);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, $listener)
+ {
+ if (isset($this->writeStreams[(int) $stream])) {
+ return;
+ }
+
+ $this->writeStreams[(int) $stream] = $listener;
+ $this->addStream($stream);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ if (!isset($this->streamEvents[(int) $stream])) {
+ return;
+ }
+
+ unset($this->readStreams[(int) $stream]);
+ $this->removeStream($stream);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ if (!isset($this->streamEvents[(int) $stream])) {
+ return;
+ }
+
+ unset($this->writeStreams[(int) $stream]);
+ $this->removeStream($stream);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, false);
+
+ $that = $this;
+ $timers = $this->timers;
+ $callback = function () use ($timer, $timers, $that) {
+ \call_user_func($timer->getCallback(), $timer);
+
+ if ($timers->contains($timer)) {
+ $that->cancelTimer($timer);
+ }
+ };
+
+ $event = \uv_timer_init($this->uv);
+ $this->timers->attach($timer, $event);
+ \uv_timer_start(
+ $event,
+ $this->convertFloatSecondsToMilliseconds($interval),
+ 0,
+ $callback
+ );
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, true);
+
+ $callback = function () use ($timer) {
+ \call_user_func($timer->getCallback(), $timer);
+ };
+
+ $interval = $this->convertFloatSecondsToMilliseconds($interval);
+ $event = \uv_timer_init($this->uv);
+ $this->timers->attach($timer, $event);
+ \uv_timer_start(
+ $event,
+ $interval,
+ (int) $interval === 0 ? 1 : $interval,
+ $callback
+ );
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if (isset($this->timers[$timer])) {
+ @\uv_timer_stop($this->timers[$timer]);
+ $this->timers->detach($timer);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick($listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ public function addSignal($signal, $listener)
+ {
+ $this->signals->add($signal, $listener);
+
+ if (!isset($this->signalEvents[$signal])) {
+ $signals = $this->signals;
+ $this->signalEvents[$signal] = \uv_signal_init($this->uv);
+ \uv_signal_start($this->signalEvents[$signal], function () use ($signals, $signal) {
+ $signals->call($signal);
+ }, $signal);
+ }
+ }
+
+ public function removeSignal($signal, $listener)
+ {
+ $this->signals->remove($signal, $listener);
+
+ if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
+ \uv_signal_stop($this->signalEvents[$signal]);
+ unset($this->signalEvents[$signal]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->futureTickQueue->tick();
+
+ $hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
+ $wasJustStopped = !$this->running;
+ $nothingLeftToDo = !$this->readStreams
+ && !$this->writeStreams
+ && !$this->timers->count()
+ && $this->signals->isEmpty();
+
+ // Use UV::RUN_ONCE when there are only I/O events active in the loop and block until one of those triggers,
+ // otherwise use UV::RUN_NOWAIT.
+ // @link http://docs.libuv.org/en/v1.x/loop.html#c.uv_run
+ $flags = \UV::RUN_ONCE;
+ if ($wasJustStopped || $hasPendingCallbacks) {
+ $flags = \UV::RUN_NOWAIT;
+ } elseif ($nothingLeftToDo) {
+ break;
+ }
+
+ \uv_run($this->uv, $flags);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ private function addStream($stream)
+ {
+ if (!isset($this->streamEvents[(int) $stream])) {
+ $this->streamEvents[(int)$stream] = \uv_poll_init_socket($this->uv, $stream);
+ }
+
+ if ($this->streamEvents[(int) $stream] !== false) {
+ $this->pollStream($stream);
+ }
+ }
+
+ private function removeStream($stream)
+ {
+ if (!isset($this->streamEvents[(int) $stream])) {
+ return;
+ }
+
+ if (!isset($this->readStreams[(int) $stream])
+ && !isset($this->writeStreams[(int) $stream])) {
+ \uv_poll_stop($this->streamEvents[(int) $stream]);
+ \uv_close($this->streamEvents[(int) $stream]);
+ unset($this->streamEvents[(int) $stream]);
+ return;
+ }
+
+ $this->pollStream($stream);
+ }
+
+ private function pollStream($stream)
+ {
+ if (!isset($this->streamEvents[(int) $stream])) {
+ return;
+ }
+
+ $flags = 0;
+ if (isset($this->readStreams[(int) $stream])) {
+ $flags |= \UV::READABLE;
+ }
+
+ if (isset($this->writeStreams[(int) $stream])) {
+ $flags |= \UV::WRITABLE;
+ }
+
+ \uv_poll_start($this->streamEvents[(int) $stream], $flags, $this->streamListener);
+ }
+
+ /**
+ * Create a stream listener
+ *
+ * @return callable Returns a callback
+ */
+ private function createStreamListener()
+ {
+ $callback = function ($event, $status, $events, $stream) {
+ // libuv automatically stops polling on error, re-enable polling to match other loop implementations
+ if ($status !== 0) {
+ $this->pollStream($stream);
+
+ // libuv may report no events on error, but this should still invoke stream listeners to report closed connections
+ // re-enable both readable and writable, correct listeners will be checked below anyway
+ if ($events === 0) {
+ $events = \UV::READABLE | \UV::WRITABLE;
+ }
+ }
+
+ if (isset($this->readStreams[(int) $stream]) && ($events & \UV::READABLE)) {
+ \call_user_func($this->readStreams[(int) $stream], $stream);
+ }
+
+ if (isset($this->writeStreams[(int) $stream]) && ($events & \UV::WRITABLE)) {
+ \call_user_func($this->writeStreams[(int) $stream], $stream);
+ }
+ };
+
+ return $callback;
+ }
+
+ /**
+ * @param float $interval
+ * @return int
+ */
+ private function convertFloatSecondsToMilliseconds($interval)
+ {
+ if ($interval < 0) {
+ return 0;
+ }
+
+ $maxValue = (int) (\PHP_INT_MAX / 1000);
+ $intInterval = (int) $interval;
+
+ if (($intInterval <= 0 && $interval > 1) || $intInterval >= $maxValue) {
+ throw new \InvalidArgumentException(
+ "Interval overflow, value must be lower than '{$maxValue}', but '{$interval}' passed."
+ );
+ }
+
+ return (int) \floor($interval * 1000);
+ }
+}
diff --git a/vendor/react/event-loop/src/Factory.php b/vendor/react/event-loop/src/Factory.php
new file mode 100644
index 0000000..30bbfd7
--- /dev/null
+++ b/vendor/react/event-loop/src/Factory.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace React\EventLoop;
+
+/**
+ * [Deprecated] The `Factory` class exists as a convenient way to pick the best available event loop implementation.
+ *
+ * @deprecated 1.2.0 See Loop instead.
+ * @see Loop
+ */
+final class Factory
+{
+ /**
+ * [Deprecated] Creates a new event loop instance
+ *
+ * ```php
+ * // deprecated
+ * $loop = React\EventLoop\Factory::create();
+ *
+ * // new
+ * $loop = React\EventLoop\Loop::get();
+ * ```
+ *
+ * This method always returns an instance implementing `LoopInterface`,
+ * the actual event loop implementation is an implementation detail.
+ *
+ * This method should usually only be called once at the beginning of the program.
+ *
+ * @deprecated 1.2.0 See Loop::get() instead.
+ * @see Loop::get()
+ *
+ * @return LoopInterface
+ */
+ public static function create()
+ {
+ $loop = self::construct();
+
+ Loop::set($loop);
+
+ return $loop;
+ }
+
+ /**
+ * @internal
+ * @return LoopInterface
+ */
+ private static function construct()
+ {
+ // @codeCoverageIgnoreStart
+ if (\function_exists('uv_loop_new')) {
+ // only use ext-uv on PHP 7
+ return new ExtUvLoop();
+ }
+
+ if (\class_exists('libev\EventLoop', false)) {
+ return new ExtLibevLoop();
+ }
+
+ if (\class_exists('EvLoop', false)) {
+ return new ExtEvLoop();
+ }
+
+ if (\class_exists('EventBase', false)) {
+ return new ExtEventLoop();
+ }
+
+ if (\function_exists('event_base_new') && \PHP_MAJOR_VERSION === 5) {
+ // only use ext-libevent on PHP 5 for now
+ return new ExtLibeventLoop();
+ }
+
+ return new StreamSelectLoop();
+ // @codeCoverageIgnoreEnd
+ }
+}
diff --git a/vendor/react/event-loop/src/Loop.php b/vendor/react/event-loop/src/Loop.php
new file mode 100644
index 0000000..fd5d81c
--- /dev/null
+++ b/vendor/react/event-loop/src/Loop.php
@@ -0,0 +1,225 @@
+<?php
+
+namespace React\EventLoop;
+
+/**
+ * The `Loop` class exists as a convenient way to get the currently relevant loop
+ */
+final class Loop
+{
+ /**
+ * @var LoopInterface
+ */
+ private static $instance;
+
+ /** @var bool */
+ private static $stopped = false;
+
+ /**
+ * Returns the event loop.
+ * When no loop is set, it will call the factory to create one.
+ *
+ * This method always returns an instance implementing `LoopInterface`,
+ * the actual event loop implementation is an implementation detail.
+ *
+ * This method is the preferred way to get the event loop and using
+ * Factory::create has been deprecated.
+ *
+ * @return LoopInterface
+ */
+ public static function get()
+ {
+ if (self::$instance instanceof LoopInterface) {
+ return self::$instance;
+ }
+
+ self::$instance = $loop = Factory::create();
+
+ // Automatically run loop at end of program, unless already started or stopped explicitly.
+ // This is tested using child processes, so coverage is actually 100%, see BinTest.
+ // @codeCoverageIgnoreStart
+ $hasRun = false;
+ $loop->futureTick(function () use (&$hasRun) {
+ $hasRun = true;
+ });
+
+ $stopped =& self::$stopped;
+ register_shutdown_function(function () use ($loop, &$hasRun, &$stopped) {
+ // Don't run if we're coming from a fatal error (uncaught exception).
+ $error = error_get_last();
+ if ((isset($error['type']) ? $error['type'] : 0) & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) {
+ return;
+ }
+
+ if (!$hasRun && !$stopped) {
+ $loop->run();
+ }
+ });
+ // @codeCoverageIgnoreEnd
+
+ return self::$instance;
+ }
+
+ /**
+ * Internal undocumented method, behavior might change or throw in the
+ * future. Use with caution and at your own risk.
+ *
+ * @internal
+ * @return void
+ */
+ public static function set(LoopInterface $loop)
+ {
+ self::$instance = $loop;
+ }
+
+ /**
+ * [Advanced] Register a listener to be notified when a stream is ready to read.
+ *
+ * @param resource $stream
+ * @param callable $listener
+ * @return void
+ * @throws \Exception
+ * @see LoopInterface::addReadStream()
+ */
+ public static function addReadStream($stream, $listener)
+ {
+ self::get()->addReadStream($stream, $listener);
+ }
+
+ /**
+ * [Advanced] Register a listener to be notified when a stream is ready to write.
+ *
+ * @param resource $stream
+ * @param callable $listener
+ * @return void
+ * @throws \Exception
+ * @see LoopInterface::addWriteStream()
+ */
+ public static function addWriteStream($stream, $listener)
+ {
+ self::get()->addWriteStream($stream, $listener);
+ }
+
+ /**
+ * Remove the read event listener for the given stream.
+ *
+ * @param resource $stream
+ * @return void
+ * @see LoopInterface::removeReadStream()
+ */
+ public static function removeReadStream($stream)
+ {
+ self::get()->removeReadStream($stream);
+ }
+
+ /**
+ * Remove the write event listener for the given stream.
+ *
+ * @param resource $stream
+ * @return void
+ * @see LoopInterface::removeWriteStream()
+ */
+ public static function removeWriteStream($stream)
+ {
+ self::get()->removeWriteStream($stream);
+ }
+
+ /**
+ * Enqueue a callback to be invoked once after the given interval.
+ *
+ * @param float $interval
+ * @param callable $callback
+ * @return TimerInterface
+ * @see LoopInterface::addTimer()
+ */
+ public static function addTimer($interval, $callback)
+ {
+ return self::get()->addTimer($interval, $callback);
+ }
+
+ /**
+ * Enqueue a callback to be invoked repeatedly after the given interval.
+ *
+ * @param float $interval
+ * @param callable $callback
+ * @return TimerInterface
+ * @see LoopInterface::addPeriodicTimer()
+ */
+ public static function addPeriodicTimer($interval, $callback)
+ {
+ return self::get()->addPeriodicTimer($interval, $callback);
+ }
+
+ /**
+ * Cancel a pending timer.
+ *
+ * @param TimerInterface $timer
+ * @return void
+ * @see LoopInterface::cancelTimer()
+ */
+ public static function cancelTimer(TimerInterface $timer)
+ {
+ return self::get()->cancelTimer($timer);
+ }
+
+ /**
+ * Schedule a callback to be invoked on a future tick of the event loop.
+ *
+ * @param callable $listener
+ * @return void
+ * @see LoopInterface::futureTick()
+ */
+ public static function futureTick($listener)
+ {
+ self::get()->futureTick($listener);
+ }
+
+ /**
+ * Register a listener to be notified when a signal has been caught by this process.
+ *
+ * @param int $signal
+ * @param callable $listener
+ * @return void
+ * @see LoopInterface::addSignal()
+ */
+ public static function addSignal($signal, $listener)
+ {
+ self::get()->addSignal($signal, $listener);
+ }
+
+ /**
+ * Removes a previously added signal listener.
+ *
+ * @param int $signal
+ * @param callable $listener
+ * @return void
+ * @see LoopInterface::removeSignal()
+ */
+ public static function removeSignal($signal, $listener)
+ {
+ self::get()->removeSignal($signal, $listener);
+ }
+
+ /**
+ * Run the event loop until there are no more tasks to perform.
+ *
+ * @return void
+ * @see LoopInterface::run()
+ */
+ public static function run()
+ {
+ self::get()->run();
+ }
+
+ /**
+ * Instruct a running event loop to stop.
+ *
+ * @return void
+ * @see LoopInterface::stop()
+ */
+ public static function stop()
+ {
+ self::$stopped = true;
+ self::get()->stop();
+ }
+}
diff --git a/vendor/react/event-loop/src/LoopInterface.php b/vendor/react/event-loop/src/LoopInterface.php
new file mode 100644
index 0000000..9266f71
--- /dev/null
+++ b/vendor/react/event-loop/src/LoopInterface.php
@@ -0,0 +1,472 @@
+<?php
+
+namespace React\EventLoop;
+
+interface LoopInterface
+{
+ /**
+ * [Advanced] Register a listener to be notified when a stream is ready to read.
+ *
+ * Note that this low-level API is considered advanced usage.
+ * Most use cases should probably use the higher-level
+ * [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface)
+ * instead.
+ *
+ * The first parameter MUST be a valid stream resource that supports
+ * checking whether it is ready to read by this loop implementation.
+ * A single stream resource MUST NOT be added more than once.
+ * Instead, either call [`removeReadStream()`](#removereadstream) first or
+ * react to this event with a single listener and then dispatch from this
+ * listener. This method MAY throw an `Exception` if the given resource type
+ * is not supported by this loop implementation.
+ *
+ * The second parameter MUST be a listener callback function that accepts
+ * the stream resource as its only parameter.
+ * If you don't use the stream resource inside your listener callback function
+ * you MAY use a function which has no parameters at all.
+ *
+ * The listener callback function MUST NOT throw an `Exception`.
+ * The return value of the listener callback function will be ignored and has
+ * no effect, so for performance reasons you're recommended to not return
+ * any excessive data structures.
+ *
+ * If you want to access any variables within your callback function, you
+ * can bind arbitrary data to a callback closure like this:
+ *
+ * ```php
+ * $loop->addReadStream($stream, function ($stream) use ($name) {
+ * echo $name . ' said: ' . fread($stream);
+ * });
+ * ```
+ *
+ * See also [example #11](examples).
+ *
+ * You can invoke [`removeReadStream()`](#removereadstream) to remove the
+ * read event listener for this stream.
+ *
+ * The execution order of listeners when multiple streams become ready at
+ * the same time is not guaranteed.
+ *
+ * @param resource $stream The PHP stream resource to check.
+ * @param callable $listener Invoked when the stream is ready.
+ * @throws \Exception if the given resource type is not supported by this loop implementation
+ * @see self::removeReadStream()
+ */
+ public function addReadStream($stream, $listener);
+
+ /**
+ * [Advanced] Register a listener to be notified when a stream is ready to write.
+ *
+ * Note that this low-level API is considered advanced usage.
+ * Most use cases should probably use the higher-level
+ * [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface)
+ * instead.
+ *
+ * The first parameter MUST be a valid stream resource that supports
+ * checking whether it is ready to write by this loop implementation.
+ * A single stream resource MUST NOT be added more than once.
+ * Instead, either call [`removeWriteStream()`](#removewritestream) first or
+ * react to this event with a single listener and then dispatch from this
+ * listener. This method MAY throw an `Exception` if the given resource type
+ * is not supported by this loop implementation.
+ *
+ * The second parameter MUST be a listener callback function that accepts
+ * the stream resource as its only parameter.
+ * If you don't use the stream resource inside your listener callback function
+ * you MAY use a function which has no parameters at all.
+ *
+ * The listener callback function MUST NOT throw an `Exception`.
+ * The return value of the listener callback function will be ignored and has
+ * no effect, so for performance reasons you're recommended to not return
+ * any excessive data structures.
+ *
+ * If you want to access any variables within your callback function, you
+ * can bind arbitrary data to a callback closure like this:
+ *
+ * ```php
+ * $loop->addWriteStream($stream, function ($stream) use ($name) {
+ * fwrite($stream, 'Hello ' . $name);
+ * });
+ * ```
+ *
+ * See also [example #12](examples).
+ *
+ * You can invoke [`removeWriteStream()`](#removewritestream) to remove the
+ * write event listener for this stream.
+ *
+ * The execution order of listeners when multiple streams become ready at
+ * the same time is not guaranteed.
+ *
+ * Some event loop implementations are known to only trigger the listener if
+ * the stream *becomes* readable (edge-triggered) and may not trigger if the
+ * stream has already been readable from the beginning.
+ * This also implies that a stream may not be recognized as readable when data
+ * is still left in PHP's internal stream buffers.
+ * As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
+ * to disable PHP's internal read buffer in this case.
+ *
+ * @param resource $stream The PHP stream resource to check.
+ * @param callable $listener Invoked when the stream is ready.
+ * @throws \Exception if the given resource type is not supported by this loop implementation
+ * @see self::removeWriteStream()
+ */
+ public function addWriteStream($stream, $listener);
+
+ /**
+ * Remove the read event listener for the given stream.
+ *
+ * Removing a stream from the loop that has already been removed or trying
+ * to remove a stream that was never added or is invalid has no effect.
+ *
+ * @param resource $stream The PHP stream resource.
+ */
+ public function removeReadStream($stream);
+
+ /**
+ * Remove the write event listener for the given stream.
+ *
+ * Removing a stream from the loop that has already been removed or trying
+ * to remove a stream that was never added or is invalid has no effect.
+ *
+ * @param resource $stream The PHP stream resource.
+ */
+ public function removeWriteStream($stream);
+
+ /**
+ * Enqueue a callback to be invoked once after the given interval.
+ *
+ * The second parameter MUST be a timer callback function that accepts
+ * the timer instance as its only parameter.
+ * If you don't use the timer instance inside your timer callback function
+ * you MAY use a function which has no parameters at all.
+ *
+ * The timer callback function MUST NOT throw an `Exception`.
+ * The return value of the timer callback function will be ignored and has
+ * no effect, so for performance reasons you're recommended to not return
+ * any excessive data structures.
+ *
+ * This method returns a timer instance. The same timer instance will also be
+ * passed into the timer callback function as described above.
+ * You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer.
+ * Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure
+ * the callback will be invoked only once after the given interval.
+ *
+ * ```php
+ * $loop->addTimer(0.8, function () {
+ * echo 'world!' . PHP_EOL;
+ * });
+ *
+ * $loop->addTimer(0.3, function () {
+ * echo 'hello ';
+ * });
+ * ```
+ *
+ * See also [example #1](examples).
+ *
+ * If you want to access any variables within your callback function, you
+ * can bind arbitrary data to a callback closure like this:
+ *
+ * ```php
+ * function hello($name, LoopInterface $loop)
+ * {
+ * $loop->addTimer(1.0, function () use ($name) {
+ * echo "hello $name\n";
+ * });
+ * }
+ *
+ * hello('Tester', $loop);
+ * ```
+ *
+ * This interface does not enforce any particular timer resolution, so
+ * special care may have to be taken if you rely on very high precision with
+ * millisecond accuracy or below. Event loop implementations SHOULD work on
+ * a best effort basis and SHOULD provide at least millisecond accuracy
+ * unless otherwise noted. Many existing event loop implementations are
+ * known to provide microsecond accuracy, but it's generally not recommended
+ * to rely on this high precision.
+ *
+ * Similarly, the execution order of timers scheduled to execute at the
+ * same time (within its possible accuracy) is not guaranteed.
+ *
+ * This interface suggests that event loop implementations SHOULD use a
+ * monotonic time source if available. Given that a monotonic time source is
+ * only available as of PHP 7.3 by default, event loop implementations MAY
+ * fall back to using wall-clock time.
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you schedule a timer to trigger in 30s and then adjust
+ * your system time forward by 20s, the timer SHOULD still trigger in 30s.
+ * See also [event loop implementations](#loop-implementations) for more details.
+ *
+ * @param int|float $interval The number of seconds to wait before execution.
+ * @param callable $callback The callback to invoke.
+ *
+ * @return TimerInterface
+ */
+ public function addTimer($interval, $callback);
+
+ /**
+ * Enqueue a callback to be invoked repeatedly after the given interval.
+ *
+ * The second parameter MUST be a timer callback function that accepts
+ * the timer instance as its only parameter.
+ * If you don't use the timer instance inside your timer callback function
+ * you MAY use a function which has no parameters at all.
+ *
+ * The timer callback function MUST NOT throw an `Exception`.
+ * The return value of the timer callback function will be ignored and has
+ * no effect, so for performance reasons you're recommended to not return
+ * any excessive data structures.
+ *
+ * This method returns a timer instance. The same timer instance will also be
+ * passed into the timer callback function as described above.
+ * Unlike [`addTimer()`](#addtimer), this method will ensure the callback
+ * will be invoked infinitely after the given interval or until you invoke
+ * [`cancelTimer`](#canceltimer).
+ *
+ * ```php
+ * $timer = $loop->addPeriodicTimer(0.1, function () {
+ * echo 'tick!' . PHP_EOL;
+ * });
+ *
+ * $loop->addTimer(1.0, function () use ($loop, $timer) {
+ * $loop->cancelTimer($timer);
+ * echo 'Done' . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also [example #2](examples).
+ *
+ * If you want to limit the number of executions, you can bind
+ * arbitrary data to a callback closure like this:
+ *
+ * ```php
+ * function hello($name, LoopInterface $loop)
+ * {
+ * $n = 3;
+ * $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) {
+ * if ($n > 0) {
+ * --$n;
+ * echo "hello $name\n";
+ * } else {
+ * $loop->cancelTimer($timer);
+ * }
+ * });
+ * }
+ *
+ * hello('Tester', $loop);
+ * ```
+ *
+ * This interface does not enforce any particular timer resolution, so
+ * special care may have to be taken if you rely on very high precision with
+ * millisecond accuracy or below. Event loop implementations SHOULD work on
+ * a best effort basis and SHOULD provide at least millisecond accuracy
+ * unless otherwise noted. Many existing event loop implementations are
+ * known to provide microsecond accuracy, but it's generally not recommended
+ * to rely on this high precision.
+ *
+ * Similarly, the execution order of timers scheduled to execute at the
+ * same time (within its possible accuracy) is not guaranteed.
+ *
+ * This interface suggests that event loop implementations SHOULD use a
+ * monotonic time source if available. Given that a monotonic time source is
+ * only available as of PHP 7.3 by default, event loop implementations MAY
+ * fall back to using wall-clock time.
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you schedule a timer to trigger in 30s and then adjust
+ * your system time forward by 20s, the timer SHOULD still trigger in 30s.
+ * See also [event loop implementations](#loop-implementations) for more details.
+ *
+ * Additionally, periodic timers may be subject to timer drift due to
+ * re-scheduling after each invocation. As such, it's generally not
+ * recommended to rely on this for high precision intervals with millisecond
+ * accuracy or below.
+ *
+ * @param int|float $interval The number of seconds to wait before execution.
+ * @param callable $callback The callback to invoke.
+ *
+ * @return TimerInterface
+ */
+ public function addPeriodicTimer($interval, $callback);
+
+ /**
+ * Cancel a pending timer.
+ *
+ * See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples).
+ *
+ * Calling this method on a timer instance that has not been added to this
+ * loop instance or on a timer that has already been cancelled has no effect.
+ *
+ * @param TimerInterface $timer The timer to cancel.
+ *
+ * @return void
+ */
+ public function cancelTimer(TimerInterface $timer);
+
+ /**
+ * Schedule a callback to be invoked on a future tick of the event loop.
+ *
+ * This works very much similar to timers with an interval of zero seconds,
+ * but does not require the overhead of scheduling a timer queue.
+ *
+ * The tick callback function MUST be able to accept zero parameters.
+ *
+ * The tick callback function MUST NOT throw an `Exception`.
+ * The return value of the tick callback function will be ignored and has
+ * no effect, so for performance reasons you're recommended to not return
+ * any excessive data structures.
+ *
+ * If you want to access any variables within your callback function, you
+ * can bind arbitrary data to a callback closure like this:
+ *
+ * ```php
+ * function hello($name, LoopInterface $loop)
+ * {
+ * $loop->futureTick(function () use ($name) {
+ * echo "hello $name\n";
+ * });
+ * }
+ *
+ * hello('Tester', $loop);
+ * ```
+ *
+ * Unlike timers, tick callbacks are guaranteed to be executed in the order
+ * they are enqueued.
+ * Also, once a callback is enqueued, there's no way to cancel this operation.
+ *
+ * This is often used to break down bigger tasks into smaller steps (a form
+ * of cooperative multitasking).
+ *
+ * ```php
+ * $loop->futureTick(function () {
+ * echo 'b';
+ * });
+ * $loop->futureTick(function () {
+ * echo 'c';
+ * });
+ * echo 'a';
+ * ```
+ *
+ * See also [example #3](examples).
+ *
+ * @param callable $listener The callback to invoke.
+ *
+ * @return void
+ */
+ public function futureTick($listener);
+
+ /**
+ * Register a listener to be notified when a signal has been caught by this process.
+ *
+ * This is useful to catch user interrupt signals or shutdown signals from
+ * tools like `supervisor` or `systemd`.
+ *
+ * The second parameter MUST be a listener callback function that accepts
+ * the signal as its only parameter.
+ * If you don't use the signal inside your listener callback function
+ * you MAY use a function which has no parameters at all.
+ *
+ * The listener callback function MUST NOT throw an `Exception`.
+ * The return value of the listener callback function will be ignored and has
+ * no effect, so for performance reasons you're recommended to not return
+ * any excessive data structures.
+ *
+ * ```php
+ * $loop->addSignal(SIGINT, function (int $signal) {
+ * echo 'Caught user interrupt signal' . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also [example #4](examples).
+ *
+ * Signaling is only available on Unix-like platforms, Windows isn't
+ * supported due to operating system limitations.
+ * This method may throw a `BadMethodCallException` if signals aren't
+ * supported on this platform, for example when required extensions are
+ * missing.
+ *
+ * **Note: A listener can only be added once to the same signal, any
+ * attempts to add it more than once will be ignored.**
+ *
+ * @param int $signal
+ * @param callable $listener
+ *
+ * @throws \BadMethodCallException when signals aren't supported on this
+ * platform, for example when required extensions are missing.
+ *
+ * @return void
+ */
+ public function addSignal($signal, $listener);
+
+ /**
+ * Removes a previously added signal listener.
+ *
+ * ```php
+ * $loop->removeSignal(SIGINT, $listener);
+ * ```
+ *
+ * Any attempts to remove listeners that aren't registered will be ignored.
+ *
+ * @param int $signal
+ * @param callable $listener
+ *
+ * @return void
+ */
+ public function removeSignal($signal, $listener);
+
+ /**
+ * Run the event loop until there are no more tasks to perform.
+ *
+ * For many applications, this method is the only directly visible
+ * invocation on the event loop.
+ * As a rule of thumb, it is usually recommended to attach everything to the
+ * same loop instance and then run the loop once at the bottom end of the
+ * application.
+ *
+ * ```php
+ * $loop->run();
+ * ```
+ *
+ * This method will keep the loop running until there are no more tasks
+ * to perform. In other words: This method will block until the last
+ * timer, stream and/or signal has been removed.
+ *
+ * Likewise, it is imperative to ensure the application actually invokes
+ * this method once. Adding listeners to the loop and missing to actually
+ * run it will result in the application exiting without actually waiting
+ * for any of the attached listeners.
+ *
+ * This method MUST NOT be called while the loop is already running.
+ * This method MAY be called more than once after it has explicitly been
+ * [`stop()`ped](#stop) or after it automatically stopped because it
+ * previously did no longer have anything to do.
+ *
+ * @return void
+ */
+ public function run();
+
+ /**
+ * Instruct a running event loop to stop.
+ *
+ * This method is considered advanced usage and should be used with care.
+ * As a rule of thumb, it is usually recommended to let the loop stop
+ * only automatically when it no longer has anything to do.
+ *
+ * This method can be used to explicitly instruct the event loop to stop:
+ *
+ * ```php
+ * $loop->addTimer(3.0, function () use ($loop) {
+ * $loop->stop();
+ * });
+ * ```
+ *
+ * Calling this method on a loop instance that is not currently running or
+ * on a loop instance that has already been stopped has no effect.
+ *
+ * @return void
+ */
+ public function stop();
+}
diff --git a/vendor/react/event-loop/src/SignalsHandler.php b/vendor/react/event-loop/src/SignalsHandler.php
new file mode 100644
index 0000000..10d125d
--- /dev/null
+++ b/vendor/react/event-loop/src/SignalsHandler.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace React\EventLoop;
+
+/**
+ * @internal
+ */
+final class SignalsHandler
+{
+ private $signals = array();
+
+ public function add($signal, $listener)
+ {
+ if (!isset($this->signals[$signal])) {
+ $this->signals[$signal] = array();
+ }
+
+ if (\in_array($listener, $this->signals[$signal])) {
+ return;
+ }
+
+ $this->signals[$signal][] = $listener;
+ }
+
+ public function remove($signal, $listener)
+ {
+ if (!isset($this->signals[$signal])) {
+ return;
+ }
+
+ $index = \array_search($listener, $this->signals[$signal], true);
+ unset($this->signals[$signal][$index]);
+
+ if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) {
+ unset($this->signals[$signal]);
+ }
+ }
+
+ public function call($signal)
+ {
+ if (!isset($this->signals[$signal])) {
+ return;
+ }
+
+ foreach ($this->signals[$signal] as $listener) {
+ \call_user_func($listener, $signal);
+ }
+ }
+
+ public function count($signal)
+ {
+ if (!isset($this->signals[$signal])) {
+ return 0;
+ }
+
+ return \count($this->signals[$signal]);
+ }
+
+ public function isEmpty()
+ {
+ return !$this->signals;
+ }
+}
diff --git a/vendor/react/event-loop/src/StreamSelectLoop.php b/vendor/react/event-loop/src/StreamSelectLoop.php
new file mode 100644
index 0000000..71983da
--- /dev/null
+++ b/vendor/react/event-loop/src/StreamSelectLoop.php
@@ -0,0 +1,329 @@
+<?php
+
+namespace React\EventLoop;
+
+use React\EventLoop\Tick\FutureTickQueue;
+use React\EventLoop\Timer\Timer;
+use React\EventLoop\Timer\Timers;
+
+/**
+ * A `stream_select()` based event loop.
+ *
+ * This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
+ * function and is the only implementation that works out of the box with PHP.
+ *
+ * This event loop works out of the box on PHP 5.4 through PHP 8+ and HHVM.
+ * This means that no installation is required and this library works on all
+ * platforms and supported PHP versions.
+ * Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory)
+ * will use this event loop by default if you do not install any of the event loop
+ * extensions listed below.
+ *
+ * Under the hood, it does a simple `select` system call.
+ * This system call is limited to the maximum file descriptor number of
+ * `FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)`
+ * (`m` being the maximum file descriptor number passed).
+ * This means that you may run into issues when handling thousands of streams
+ * concurrently and you may want to look into using one of the alternative
+ * event loop implementations listed below in this case.
+ * If your use case is among the many common use cases that involve handling only
+ * dozens or a few hundred streams at once, then this event loop implementation
+ * performs really well.
+ *
+ * If you want to use signal handling (see also [`addSignal()`](#addsignal) below),
+ * this event loop implementation requires `ext-pcntl`.
+ * This extension is only available for Unix-like platforms and does not support
+ * Windows.
+ * It is commonly installed as part of many PHP distributions.
+ * If this extension is missing (or you're running on Windows), signal handling is
+ * not supported and throws a `BadMethodCallException` instead.
+ *
+ * This event loop is known to rely on wall-clock time to schedule future timers
+ * when using any version before PHP 7.3, because a monotonic time source is
+ * only available as of PHP 7.3 (`hrtime()`).
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you schedule a timer to trigger in 30s on PHP < 7.3 and
+ * then adjust your system time forward by 20s, the timer may trigger in 10s.
+ * See also [`addTimer()`](#addtimer) for more details.
+ *
+ * @link https://www.php.net/manual/en/function.stream-select.php
+ */
+final class StreamSelectLoop implements LoopInterface
+{
+ /** @internal */
+ const MICROSECONDS_PER_SECOND = 1000000;
+
+ private $futureTickQueue;
+ private $timers;
+ private $readStreams = array();
+ private $readListeners = array();
+ private $writeStreams = array();
+ private $writeListeners = array();
+ private $running;
+ private $pcntl = false;
+ private $pcntlPoll = false;
+ private $signals;
+
+ public function __construct()
+ {
+ $this->futureTickQueue = new FutureTickQueue();
+ $this->timers = new Timers();
+ $this->pcntl = \function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch');
+ $this->pcntlPoll = $this->pcntl && !\function_exists('pcntl_async_signals');
+ $this->signals = new SignalsHandler();
+
+ // prefer async signals if available (PHP 7.1+) or fall back to dispatching on each tick
+ if ($this->pcntl && !$this->pcntlPoll) {
+ \pcntl_async_signals(true);
+ }
+ }
+
+ public function addReadStream($stream, $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->readStreams[$key])) {
+ $this->readStreams[$key] = $stream;
+ $this->readListeners[$key] = $listener;
+ }
+ }
+
+ public function addWriteStream($stream, $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->writeStreams[$key])) {
+ $this->writeStreams[$key] = $stream;
+ $this->writeListeners[$key] = $listener;
+ }
+ }
+
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ unset(
+ $this->readStreams[$key],
+ $this->readListeners[$key]
+ );
+ }
+
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ unset(
+ $this->writeStreams[$key],
+ $this->writeListeners[$key]
+ );
+ }
+
+ public function addTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, false);
+
+ $this->timers->add($timer);
+
+ return $timer;
+ }
+
+ public function addPeriodicTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, true);
+
+ $this->timers->add($timer);
+
+ return $timer;
+ }
+
+ public function cancelTimer(TimerInterface $timer)
+ {
+ $this->timers->cancel($timer);
+ }
+
+ public function futureTick($listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ public function addSignal($signal, $listener)
+ {
+ if ($this->pcntl === false) {
+ throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"');
+ }
+
+ $first = $this->signals->count($signal) === 0;
+ $this->signals->add($signal, $listener);
+
+ if ($first) {
+ \pcntl_signal($signal, array($this->signals, 'call'));
+ }
+ }
+
+ public function removeSignal($signal, $listener)
+ {
+ if (!$this->signals->count($signal)) {
+ return;
+ }
+
+ $this->signals->remove($signal, $listener);
+
+ if ($this->signals->count($signal) === 0) {
+ \pcntl_signal($signal, \SIG_DFL);
+ }
+ }
+
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->futureTickQueue->tick();
+
+ $this->timers->tick();
+
+ // Future-tick queue has pending callbacks ...
+ if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+ $timeout = 0;
+
+ // There is a pending timer, only block until it is due ...
+ } elseif ($scheduledAt = $this->timers->getFirst()) {
+ $timeout = $scheduledAt - $this->timers->getTime();
+ if ($timeout < 0) {
+ $timeout = 0;
+ } else {
+ // Convert float seconds to int microseconds.
+ // Ensure we do not exceed maximum integer size, which may
+ // cause the loop to tick once every ~35min on 32bit systems.
+ $timeout *= self::MICROSECONDS_PER_SECOND;
+ $timeout = $timeout > \PHP_INT_MAX ? \PHP_INT_MAX : (int)$timeout;
+ }
+
+ // The only possible event is stream or signal activity, so wait forever ...
+ } elseif ($this->readStreams || $this->writeStreams || !$this->signals->isEmpty()) {
+ $timeout = null;
+
+ // There's nothing left to do ...
+ } else {
+ break;
+ }
+
+ $this->waitForStreamActivity($timeout);
+ }
+ }
+
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Wait/check for stream activity, or until the next timer is due.
+ *
+ * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
+ */
+ private function waitForStreamActivity($timeout)
+ {
+ $read = $this->readStreams;
+ $write = $this->writeStreams;
+
+ $available = $this->streamSelect($read, $write, $timeout);
+ if ($this->pcntlPoll) {
+ \pcntl_signal_dispatch();
+ }
+ if (false === $available) {
+ // if a system call has been interrupted,
+ // we cannot rely on it's outcome
+ return;
+ }
+
+ foreach ($read as $stream) {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ \call_user_func($this->readListeners[$key], $stream);
+ }
+ }
+
+ foreach ($write as $stream) {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ \call_user_func($this->writeListeners[$key], $stream);
+ }
+ }
+ }
+
+ /**
+ * Emulate a stream_select() implementation that does not break when passed
+ * empty stream arrays.
+ *
+ * @param array $read An array of read streams to select upon.
+ * @param array $write An array of write streams to select upon.
+ * @param int|null $timeout Activity timeout in microseconds, or null to wait forever.
+ *
+ * @return int|false The total number of streams that are ready for read/write.
+ * Can return false if stream_select() is interrupted by a signal.
+ */
+ private function streamSelect(array &$read, array &$write, $timeout)
+ {
+ if ($read || $write) {
+ // We do not usually use or expose the `exceptfds` parameter passed to the underlying `select`.
+ // However, Windows does not report failed connection attempts in `writefds` passed to `select` like most other platforms.
+ // Instead, it uses `writefds` only for successful connection attempts and `exceptfds` for failed connection attempts.
+ // We work around this by adding all sockets that look like a pending connection attempt to `exceptfds` automatically on Windows and merge it back later.
+ // This ensures the public API matches other loop implementations across all platforms (see also test suite or rather test matrix).
+ // Lacking better APIs, every write-only socket that has not yet read any data is assumed to be in a pending connection attempt state.
+ // @link https://docs.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select
+ $except = null;
+ if (\DIRECTORY_SEPARATOR === '\\') {
+ $except = array();
+ foreach ($write as $key => $socket) {
+ if (!isset($read[$key]) && @\ftell($socket) === 0) {
+ $except[$key] = $socket;
+ }
+ }
+ }
+
+ /** @var ?callable $previous */
+ $previous = \set_error_handler(function ($errno, $errstr) use (&$previous) {
+ // suppress warnings that occur when `stream_select()` is interrupted by a signal
+ $eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : 4;
+ if ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false) {
+ return;
+ }
+
+ // forward any other error to registered error handler or print warning
+ return ($previous !== null) ? \call_user_func_array($previous, \func_get_args()) : false;
+ });
+
+ try {
+ $ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
+ \restore_error_handler();
+ } catch (\Throwable $e) { // @codeCoverageIgnoreStart
+ \restore_error_handler();
+ throw $e;
+ } catch (\Exception $e) {
+ \restore_error_handler();
+ throw $e;
+ } // @codeCoverageIgnoreEnd
+
+ if ($except) {
+ $write = \array_merge($write, $except);
+ }
+ return $ret;
+ }
+
+ if ($timeout > 0) {
+ \usleep($timeout);
+ } elseif ($timeout === null) {
+ // wait forever (we only reach this if we're only awaiting signals)
+ // this may be interrupted and return earlier when a signal is received
+ \sleep(PHP_INT_MAX);
+ }
+
+ return 0;
+ }
+}
diff --git a/vendor/react/event-loop/src/Tick/FutureTickQueue.php b/vendor/react/event-loop/src/Tick/FutureTickQueue.php
new file mode 100644
index 0000000..efabcbc
--- /dev/null
+++ b/vendor/react/event-loop/src/Tick/FutureTickQueue.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace React\EventLoop\Tick;
+
+use SplQueue;
+
+/**
+ * A tick queue implementation that can hold multiple callback functions
+ *
+ * This class should only be used internally, see LoopInterface instead.
+ *
+ * @see LoopInterface
+ * @internal
+ */
+final class FutureTickQueue
+{
+ private $queue;
+
+ public function __construct()
+ {
+ $this->queue = new SplQueue();
+ }
+
+ /**
+ * Add a callback to be invoked on a future tick of the event loop.
+ *
+ * Callbacks are guaranteed to be executed in the order they are enqueued.
+ *
+ * @param callable $listener The callback to invoke.
+ */
+ public function add($listener)
+ {
+ $this->queue->enqueue($listener);
+ }
+
+ /**
+ * Flush the callback queue.
+ */
+ public function tick()
+ {
+ // Only invoke as many callbacks as were on the queue when tick() was called.
+ $count = $this->queue->count();
+
+ while ($count--) {
+ \call_user_func(
+ $this->queue->dequeue()
+ );
+ }
+ }
+
+ /**
+ * Check if the next tick queue is empty.
+ *
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return $this->queue->isEmpty();
+ }
+}
diff --git a/vendor/react/event-loop/src/Timer/Timer.php b/vendor/react/event-loop/src/Timer/Timer.php
new file mode 100644
index 0000000..da3602a
--- /dev/null
+++ b/vendor/react/event-loop/src/Timer/Timer.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace React\EventLoop\Timer;
+
+use React\EventLoop\TimerInterface;
+
+/**
+ * The actual connection implementation for TimerInterface
+ *
+ * This class should only be used internally, see TimerInterface instead.
+ *
+ * @see TimerInterface
+ * @internal
+ */
+final class Timer implements TimerInterface
+{
+ const MIN_INTERVAL = 0.000001;
+
+ private $interval;
+ private $callback;
+ private $periodic;
+
+ /**
+ * Constructor initializes the fields of the Timer
+ *
+ * @param float $interval The interval after which this timer will execute, in seconds
+ * @param callable $callback The callback that will be executed when this timer elapses
+ * @param bool $periodic Whether the time is periodic
+ */
+ public function __construct($interval, $callback, $periodic = false)
+ {
+ if ($interval < self::MIN_INTERVAL) {
+ $interval = self::MIN_INTERVAL;
+ }
+
+ $this->interval = (float) $interval;
+ $this->callback = $callback;
+ $this->periodic = (bool) $periodic;
+ }
+
+ public function getInterval()
+ {
+ return $this->interval;
+ }
+
+ public function getCallback()
+ {
+ return $this->callback;
+ }
+
+ public function isPeriodic()
+ {
+ return $this->periodic;
+ }
+}
diff --git a/vendor/react/event-loop/src/Timer/Timers.php b/vendor/react/event-loop/src/Timer/Timers.php
new file mode 100644
index 0000000..3a33b8c
--- /dev/null
+++ b/vendor/react/event-loop/src/Timer/Timers.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace React\EventLoop\Timer;
+
+use React\EventLoop\TimerInterface;
+
+/**
+ * A scheduler implementation that can hold multiple timer instances
+ *
+ * This class should only be used internally, see TimerInterface instead.
+ *
+ * @see TimerInterface
+ * @internal
+ */
+final class Timers
+{
+ private $time;
+ private $timers = array();
+ private $schedule = array();
+ private $sorted = true;
+ private $useHighResolution;
+
+ public function __construct()
+ {
+ // prefer high-resolution timer, available as of PHP 7.3+
+ $this->useHighResolution = \function_exists('hrtime');
+ }
+
+ public function updateTime()
+ {
+ return $this->time = $this->useHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
+ }
+
+ public function getTime()
+ {
+ return $this->time ?: $this->updateTime();
+ }
+
+ public function add(TimerInterface $timer)
+ {
+ $id = \spl_object_hash($timer);
+ $this->timers[$id] = $timer;
+ $this->schedule[$id] = $timer->getInterval() + $this->updateTime();
+ $this->sorted = false;
+ }
+
+ public function contains(TimerInterface $timer)
+ {
+ return isset($this->timers[\spl_object_hash($timer)]);
+ }
+
+ public function cancel(TimerInterface $timer)
+ {
+ $id = \spl_object_hash($timer);
+ unset($this->timers[$id], $this->schedule[$id]);
+ }
+
+ public function getFirst()
+ {
+ // ensure timers are sorted to simply accessing next (first) one
+ if (!$this->sorted) {
+ $this->sorted = true;
+ \asort($this->schedule);
+ }
+
+ return \reset($this->schedule);
+ }
+
+ public function isEmpty()
+ {
+ return \count($this->timers) === 0;
+ }
+
+ public function tick()
+ {
+ // hot path: skip timers if nothing is scheduled
+ if (!$this->schedule) {
+ return;
+ }
+
+ // ensure timers are sorted so we can execute in order
+ if (!$this->sorted) {
+ $this->sorted = true;
+ \asort($this->schedule);
+ }
+
+ $time = $this->updateTime();
+
+ foreach ($this->schedule as $id => $scheduled) {
+ // schedule is ordered, so loop until first timer that is not scheduled for execution now
+ if ($scheduled >= $time) {
+ break;
+ }
+
+ // skip any timers that are removed while we process the current schedule
+ if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) {
+ continue;
+ }
+
+ $timer = $this->timers[$id];
+ \call_user_func($timer->getCallback(), $timer);
+
+ // re-schedule if this is a periodic timer and it has not been cancelled explicitly already
+ if ($timer->isPeriodic() && isset($this->timers[$id])) {
+ $this->schedule[$id] = $timer->getInterval() + $time;
+ $this->sorted = false;
+ } else {
+ unset($this->timers[$id], $this->schedule[$id]);
+ }
+ }
+ }
+}
diff --git a/vendor/react/event-loop/src/TimerInterface.php b/vendor/react/event-loop/src/TimerInterface.php
new file mode 100644
index 0000000..cdcf773
--- /dev/null
+++ b/vendor/react/event-loop/src/TimerInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace React\EventLoop;
+
+interface TimerInterface
+{
+ /**
+ * Get the interval after which this timer will execute, in seconds
+ *
+ * @return float
+ */
+ public function getInterval();
+
+ /**
+ * Get the callback that will be executed when this timer elapses
+ *
+ * @return callable
+ */
+ public function getCallback();
+
+ /**
+ * Determine whether the time is periodic
+ *
+ * @return bool
+ */
+ public function isPeriodic();
+}
diff --git a/vendor/react/http-client/LICENSE b/vendor/react/http-client/LICENSE
new file mode 100644
index 0000000..a808108
--- /dev/null
+++ b/vendor/react/http-client/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+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/react/http-client/composer.json b/vendor/react/http-client/composer.json
new file mode 100644
index 0000000..9207639
--- /dev/null
+++ b/vendor/react/http-client/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "react/http-client",
+ "description": "Event-driven, streaming HTTP client for ReactPHP",
+ "keywords": ["http"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
+ "react/promise": "^2.1 || ^1.2.1",
+ "react/socket": "^1.0 || ^0.8.4",
+ "react/stream": "^1.0 || ^0.7.1",
+ "ringcentral/psr7": "^1.2"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35",
+ "react/promise-stream": "^1.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\HttpClient\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\HttpClient\\": "tests"
+ }
+ }
+}
diff --git a/vendor/react/http-client/src/ChunkedStreamDecoder.php b/vendor/react/http-client/src/ChunkedStreamDecoder.php
new file mode 100644
index 0000000..bc150ad
--- /dev/null
+++ b/vendor/react/http-client/src/ChunkedStreamDecoder.php
@@ -0,0 +1,207 @@
+<?php
+
+namespace React\HttpClient;
+
+use Evenement\EventEmitter;
+use Exception;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * @internal
+ */
+class ChunkedStreamDecoder extends EventEmitter implements ReadableStreamInterface
+{
+ const CRLF = "\r\n";
+
+ /**
+ * @var string
+ */
+ protected $buffer = '';
+
+ /**
+ * @var int
+ */
+ protected $remainingLength = 0;
+
+ /**
+ * @var bool
+ */
+ protected $nextChunkIsLength = true;
+
+ /**
+ * @var ReadableStreamInterface
+ */
+ protected $stream;
+
+ /**
+ * @var bool
+ */
+ protected $closed = false;
+
+ /**
+ * @var bool
+ */
+ protected $reachedEnd = false;
+
+ /**
+ * @param ReadableStreamInterface $stream
+ */
+ public function __construct(ReadableStreamInterface $stream)
+ {
+ $this->stream = $stream;
+ $this->stream->on('data', array($this, 'handleData'));
+ $this->stream->on('end', array($this, 'handleEnd'));
+ Util::forwardEvents($this->stream, $this, array(
+ 'error',
+ ));
+ }
+
+ /** @internal */
+ public function handleData($data)
+ {
+ $this->buffer .= $data;
+
+ do {
+ $bufferLength = strlen($this->buffer);
+ $continue = $this->iterateBuffer();
+ $iteratedBufferLength = strlen($this->buffer);
+ } while (
+ $continue &&
+ $bufferLength !== $iteratedBufferLength &&
+ $iteratedBufferLength > 0
+ );
+
+ if ($this->buffer === false) {
+ $this->buffer = '';
+ }
+ }
+
+ protected function iterateBuffer()
+ {
+ if (strlen($this->buffer) <= 1) {
+ return false;
+ }
+
+ if ($this->nextChunkIsLength) {
+ $crlfPosition = strpos($this->buffer, static::CRLF);
+ if ($crlfPosition === false && strlen($this->buffer) > 1024) {
+ $this->emit('error', array(
+ new Exception('Chunk length header longer then 1024 bytes'),
+ ));
+ $this->close();
+ return false;
+ }
+ if ($crlfPosition === false) {
+ return false; // Chunk header hasn't completely come in yet
+ }
+ $lengthChunk = substr($this->buffer, 0, $crlfPosition);
+ if (strpos($lengthChunk, ';') !== false) {
+ list($lengthChunk) = explode(';', $lengthChunk, 2);
+ }
+ if ($lengthChunk !== '') {
+ $lengthChunk = ltrim(trim($lengthChunk), "0");
+ if ($lengthChunk === '') {
+ // We've reached the end of the stream
+ $this->reachedEnd = true;
+ $this->emit('end');
+ $this->close();
+ return false;
+ }
+ }
+ $this->nextChunkIsLength = false;
+ if (dechex((int)@hexdec($lengthChunk)) !== strtolower($lengthChunk)) {
+ $this->emit('error', array(
+ new Exception('Unable to validate "' . $lengthChunk . '" as chunk length header'),
+ ));
+ $this->close();
+ return false;
+ }
+ $this->remainingLength = hexdec($lengthChunk);
+ $this->buffer = substr($this->buffer, $crlfPosition + 2);
+ return true;
+ }
+
+ if ($this->remainingLength > 0) {
+ $chunkLength = $this->getChunkLength();
+ if ($chunkLength === 0) {
+ return true;
+ }
+ $this->emit('data', array(
+ substr($this->buffer, 0, $chunkLength),
+ $this
+ ));
+ $this->remainingLength -= $chunkLength;
+ $this->buffer = substr($this->buffer, $chunkLength);
+ return true;
+ }
+
+ $this->nextChunkIsLength = true;
+ $this->buffer = substr($this->buffer, 2);
+ return true;
+ }
+
+ protected function getChunkLength()
+ {
+ $bufferLength = strlen($this->buffer);
+
+ if ($bufferLength >= $this->remainingLength) {
+ return $this->remainingLength;
+ }
+
+ return $bufferLength;
+ }
+
+ public function pause()
+ {
+ $this->stream->pause();
+ }
+
+ public function resume()
+ {
+ $this->stream->resume();
+ }
+
+ public function isReadable()
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ Util::pipe($this, $dest, $options);
+
+ return $dest;
+ }
+
+ public function close()
+ {
+ $this->closed = true;
+ return $this->stream->close();
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ $this->handleData('');
+
+ if ($this->closed) {
+ return;
+ }
+
+ if ($this->buffer === '' && $this->reachedEnd) {
+ $this->emit('end');
+ $this->close();
+ return;
+ }
+
+ $this->emit(
+ 'error',
+ array(
+ new Exception('Stream ended with incomplete control code')
+ )
+ );
+ $this->close();
+ }
+}
diff --git a/vendor/react/http-client/src/Client.php b/vendor/react/http-client/src/Client.php
new file mode 100644
index 0000000..fc14426
--- /dev/null
+++ b/vendor/react/http-client/src/Client.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace React\HttpClient;
+
+use React\EventLoop\LoopInterface;
+use React\Socket\ConnectorInterface;
+use React\Socket\Connector;
+
+class Client
+{
+ private $connector;
+
+ public function __construct(LoopInterface $loop, ConnectorInterface $connector = null)
+ {
+ if ($connector === null) {
+ $connector = new Connector($loop);
+ }
+
+ $this->connector = $connector;
+ }
+
+ public function request($method, $url, array $headers = array(), $protocolVersion = '1.0')
+ {
+ $requestData = new RequestData($method, $url, $headers, $protocolVersion);
+
+ return new Request($this->connector, $requestData);
+ }
+}
diff --git a/vendor/react/http-client/src/Request.php b/vendor/react/http-client/src/Request.php
new file mode 100644
index 0000000..caa242b
--- /dev/null
+++ b/vendor/react/http-client/src/Request.php
@@ -0,0 +1,294 @@
+<?php
+
+namespace React\HttpClient;
+
+use Evenement\EventEmitter;
+use React\Promise;
+use React\Socket\ConnectionInterface;
+use React\Socket\ConnectorInterface;
+use React\Stream\WritableStreamInterface;
+use RingCentral\Psr7 as gPsr;
+
+/**
+ * @event response
+ * @event drain
+ * @event error
+ * @event end
+ */
+class Request extends EventEmitter implements WritableStreamInterface
+{
+ const STATE_INIT = 0;
+ const STATE_WRITING_HEAD = 1;
+ const STATE_HEAD_WRITTEN = 2;
+ const STATE_END = 3;
+
+ private $connector;
+ private $requestData;
+
+ private $stream;
+ private $buffer;
+ private $responseFactory;
+ private $state = self::STATE_INIT;
+ private $ended = false;
+
+ private $pendingWrites = '';
+
+ public function __construct(ConnectorInterface $connector, RequestData $requestData)
+ {
+ $this->connector = $connector;
+ $this->requestData = $requestData;
+ }
+
+ public function isWritable()
+ {
+ return self::STATE_END > $this->state && !$this->ended;
+ }
+
+ private function writeHead()
+ {
+ $this->state = self::STATE_WRITING_HEAD;
+
+ $requestData = $this->requestData;
+ $streamRef = &$this->stream;
+ $stateRef = &$this->state;
+ $pendingWrites = &$this->pendingWrites;
+ $that = $this;
+
+ $promise = $this->connect();
+ $promise->then(
+ function (ConnectionInterface $stream) use ($requestData, &$streamRef, &$stateRef, &$pendingWrites, $that) {
+ $streamRef = $stream;
+
+ $stream->on('drain', array($that, 'handleDrain'));
+ $stream->on('data', array($that, 'handleData'));
+ $stream->on('end', array($that, 'handleEnd'));
+ $stream->on('error', array($that, 'handleError'));
+ $stream->on('close', array($that, 'handleClose'));
+
+ $headers = (string) $requestData;
+
+ $more = $stream->write($headers . $pendingWrites);
+
+ $stateRef = Request::STATE_HEAD_WRITTEN;
+
+ // clear pending writes if non-empty
+ if ($pendingWrites !== '') {
+ $pendingWrites = '';
+
+ if ($more) {
+ $that->emit('drain');
+ }
+ }
+ },
+ array($this, 'closeError')
+ );
+
+ $this->on('close', function() use ($promise) {
+ $promise->cancel();
+ });
+ }
+
+ public function write($data)
+ {
+ if (!$this->isWritable()) {
+ return false;
+ }
+
+ // write directly to connection stream if already available
+ if (self::STATE_HEAD_WRITTEN <= $this->state) {
+ return $this->stream->write($data);
+ }
+
+ // otherwise buffer and try to establish connection
+ $this->pendingWrites .= $data;
+ if (self::STATE_WRITING_HEAD > $this->state) {
+ $this->writeHead();
+ }
+
+ return false;
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->isWritable()) {
+ return;
+ }
+
+ if (null !== $data) {
+ $this->write($data);
+ } else if (self::STATE_WRITING_HEAD > $this->state) {
+ $this->writeHead();
+ }
+
+ $this->ended = true;
+ }
+
+ /** @internal */
+ public function handleDrain()
+ {
+ $this->emit('drain');
+ }
+
+ /** @internal */
+ public function handleData($data)
+ {
+ $this->buffer .= $data;
+
+ // buffer until double CRLF (or double LF for compatibility with legacy servers)
+ if (false !== strpos($this->buffer, "\r\n\r\n") || false !== strpos($this->buffer, "\n\n")) {
+ try {
+ list($response, $bodyChunk) = $this->parseResponse($this->buffer);
+ } catch (\InvalidArgumentException $exception) {
+ $this->emit('error', array($exception));
+ }
+
+ $this->buffer = null;
+
+ $this->stream->removeListener('drain', array($this, 'handleDrain'));
+ $this->stream->removeListener('data', array($this, 'handleData'));
+ $this->stream->removeListener('end', array($this, 'handleEnd'));
+ $this->stream->removeListener('error', array($this, 'handleError'));
+ $this->stream->removeListener('close', array($this, 'handleClose'));
+
+ if (!isset($response)) {
+ return;
+ }
+
+ $response->on('close', array($this, 'close'));
+ $that = $this;
+ $response->on('error', function (\Exception $error) use ($that) {
+ $that->closeError(new \RuntimeException(
+ "An error occured in the response",
+ 0,
+ $error
+ ));
+ });
+
+ $this->emit('response', array($response, $this));
+
+ $this->stream->emit('data', array($bodyChunk));
+ }
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ $this->closeError(new \RuntimeException(
+ "Connection ended before receiving response"
+ ));
+ }
+
+ /** @internal */
+ public function handleError(\Exception $error)
+ {
+ $this->closeError(new \RuntimeException(
+ "An error occurred in the underlying stream",
+ 0,
+ $error
+ ));
+ }
+
+ /** @internal */
+ public function handleClose()
+ {
+ $this->close();
+ }
+
+ /** @internal */
+ public function closeError(\Exception $error)
+ {
+ if (self::STATE_END <= $this->state) {
+ return;
+ }
+ $this->emit('error', array($error));
+ $this->close();
+ }
+
+ public function close()
+ {
+ if (self::STATE_END <= $this->state) {
+ return;
+ }
+
+ $this->state = self::STATE_END;
+ $this->pendingWrites = '';
+
+ if ($this->stream) {
+ $this->stream->close();
+ }
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+
+ protected function parseResponse($data)
+ {
+ $psrResponse = gPsr\parse_response($data);
+ $headers = array_map(function($val) {
+ if (1 === count($val)) {
+ $val = $val[0];
+ }
+
+ return $val;
+ }, $psrResponse->getHeaders());
+
+ $factory = $this->getResponseFactory();
+
+ $response = $factory(
+ 'HTTP',
+ $psrResponse->getProtocolVersion(),
+ $psrResponse->getStatusCode(),
+ $psrResponse->getReasonPhrase(),
+ $headers
+ );
+
+ return array($response, (string)($psrResponse->getBody()));
+ }
+
+ protected function connect()
+ {
+ $scheme = $this->requestData->getScheme();
+ if ($scheme !== 'https' && $scheme !== 'http') {
+ return Promise\reject(
+ new \InvalidArgumentException('Invalid request URL given')
+ );
+ }
+
+ $host = $this->requestData->getHost();
+ $port = $this->requestData->getPort();
+
+ if ($scheme === 'https') {
+ $host = 'tls://' . $host;
+ }
+
+ return $this->connector
+ ->connect($host . ':' . $port);
+ }
+
+ public function setResponseFactory($factory)
+ {
+ $this->responseFactory = $factory;
+ }
+
+ public function getResponseFactory()
+ {
+ if (null === $factory = $this->responseFactory) {
+ $stream = $this->stream;
+
+ $factory = function ($protocol, $version, $code, $reasonPhrase, $headers) use ($stream) {
+ return new Response(
+ $stream,
+ $protocol,
+ $version,
+ $code,
+ $reasonPhrase,
+ $headers
+ );
+ };
+
+ $this->responseFactory = $factory;
+ }
+
+ return $factory;
+ }
+}
diff --git a/vendor/react/http-client/src/RequestData.php b/vendor/react/http-client/src/RequestData.php
new file mode 100644
index 0000000..1c7d5eb
--- /dev/null
+++ b/vendor/react/http-client/src/RequestData.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace React\HttpClient;
+
+class RequestData
+{
+ private $method;
+ private $url;
+ private $headers;
+ private $protocolVersion;
+
+ public function __construct($method, $url, array $headers = array(), $protocolVersion = '1.0')
+ {
+ $this->method = $method;
+ $this->url = $url;
+ $this->headers = $headers;
+ $this->protocolVersion = $protocolVersion;
+ }
+
+ private function mergeDefaultheaders(array $headers)
+ {
+ $port = ($this->getDefaultPort() === $this->getPort()) ? '' : ":{$this->getPort()}";
+ $connectionHeaders = ('1.1' === $this->protocolVersion) ? array('Connection' => 'close') : array();
+ $authHeaders = $this->getAuthHeaders();
+
+ $defaults = array_merge(
+ array(
+ 'Host' => $this->getHost().$port,
+ 'User-Agent' => 'React/alpha',
+ ),
+ $connectionHeaders,
+ $authHeaders
+ );
+
+ // remove all defaults that already exist in $headers
+ $lower = array_change_key_case($headers, CASE_LOWER);
+ foreach ($defaults as $key => $_) {
+ if (isset($lower[strtolower($key)])) {
+ unset($defaults[$key]);
+ }
+ }
+
+ return array_merge($defaults, $headers);
+ }
+
+ public function getScheme()
+ {
+ return parse_url($this->url, PHP_URL_SCHEME);
+ }
+
+ public function getHost()
+ {
+ return parse_url($this->url, PHP_URL_HOST);
+ }
+
+ public function getPort()
+ {
+ return (int) parse_url($this->url, PHP_URL_PORT) ?: $this->getDefaultPort();
+ }
+
+ public function getDefaultPort()
+ {
+ return ('https' === $this->getScheme()) ? 443 : 80;
+ }
+
+ public function getPath()
+ {
+ $path = parse_url($this->url, PHP_URL_PATH);
+ $queryString = parse_url($this->url, PHP_URL_QUERY);
+
+ // assume "/" path by default, but allow "OPTIONS *"
+ if ($path === null) {
+ $path = ($this->method === 'OPTIONS' && $queryString === null) ? '*': '/';
+ }
+ if ($queryString !== null) {
+ $path .= '?' . $queryString;
+ }
+
+ return $path;
+ }
+
+ public function setProtocolVersion($version)
+ {
+ $this->protocolVersion = $version;
+ }
+
+ public function __toString()
+ {
+ $headers = $this->mergeDefaultheaders($this->headers);
+
+ $data = '';
+ $data .= "{$this->method} {$this->getPath()} HTTP/{$this->protocolVersion}\r\n";
+ foreach ($headers as $name => $values) {
+ foreach ((array)$values as $value) {
+ $data .= "$name: $value\r\n";
+ }
+ }
+ $data .= "\r\n";
+
+ return $data;
+ }
+
+ private function getUrlUserPass()
+ {
+ $components = parse_url($this->url);
+
+ if (isset($components['user'])) {
+ return array(
+ 'user' => $components['user'],
+ 'pass' => isset($components['pass']) ? $components['pass'] : null,
+ );
+ }
+ }
+
+ private function getAuthHeaders()
+ {
+ if (null !== $auth = $this->getUrlUserPass()) {
+ return array(
+ 'Authorization' => 'Basic ' . base64_encode($auth['user'].':'.$auth['pass']),
+ );
+ }
+
+ return array();
+ }
+}
diff --git a/vendor/react/http-client/src/Response.php b/vendor/react/http-client/src/Response.php
new file mode 100644
index 0000000..5ed271f
--- /dev/null
+++ b/vendor/react/http-client/src/Response.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace React\HttpClient;
+
+use Evenement\EventEmitter;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * @event data ($bodyChunk)
+ * @event error
+ * @event end
+ */
+class Response extends EventEmitter implements ReadableStreamInterface
+{
+ private $stream;
+ private $protocol;
+ private $version;
+ private $code;
+ private $reasonPhrase;
+ private $headers;
+ private $readable = true;
+
+ public function __construct(ReadableStreamInterface $stream, $protocol, $version, $code, $reasonPhrase, $headers)
+ {
+ $this->stream = $stream;
+ $this->protocol = $protocol;
+ $this->version = $version;
+ $this->code = $code;
+ $this->reasonPhrase = $reasonPhrase;
+ $this->headers = $headers;
+
+ if (strtolower($this->getHeaderLine('Transfer-Encoding')) === 'chunked') {
+ $this->stream = new ChunkedStreamDecoder($stream);
+ $this->removeHeader('Transfer-Encoding');
+ }
+
+ $this->stream->on('data', array($this, 'handleData'));
+ $this->stream->on('error', array($this, 'handleError'));
+ $this->stream->on('end', array($this, 'handleEnd'));
+ $this->stream->on('close', array($this, 'handleClose'));
+ }
+
+ public function getProtocol()
+ {
+ return $this->protocol;
+ }
+
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ public function getCode()
+ {
+ return $this->code;
+ }
+
+ public function getReasonPhrase()
+ {
+ return $this->reasonPhrase;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ private function removeHeader($name)
+ {
+ foreach ($this->headers as $key => $value) {
+ if (strcasecmp($name, $key) === 0) {
+ unset($this->headers[$key]);
+ break;
+ }
+ }
+ }
+
+ private function getHeader($name)
+ {
+ $name = strtolower($name);
+ $normalized = array_change_key_case($this->headers, CASE_LOWER);
+
+ return isset($normalized[$name]) ? (array)$normalized[$name] : array();
+ }
+
+ private function getHeaderLine($name)
+ {
+ return implode(', ' , $this->getHeader($name));
+ }
+
+ /** @internal */
+ public function handleData($data)
+ {
+ if ($this->readable) {
+ $this->emit('data', array($data));
+ }
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ if (!$this->readable) {
+ return;
+ }
+ $this->emit('end');
+ $this->close();
+ }
+
+ /** @internal */
+ public function handleError(\Exception $error)
+ {
+ if (!$this->readable) {
+ return;
+ }
+ $this->emit('error', array(new \RuntimeException(
+ "An error occurred in the underlying stream",
+ 0,
+ $error
+ )));
+
+ $this->close();
+ }
+
+ /** @internal */
+ public function handleClose()
+ {
+ $this->close();
+ }
+
+ public function close()
+ {
+ if (!$this->readable) {
+ return;
+ }
+
+ $this->readable = false;
+ $this->stream->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function pause()
+ {
+ if (!$this->readable) {
+ return;
+ }
+
+ $this->stream->pause();
+ }
+
+ public function resume()
+ {
+ if (!$this->readable) {
+ return;
+ }
+
+ $this->stream->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ Util::pipe($this, $dest, $options);
+
+ return $dest;
+ }
+}
diff --git a/vendor/react/http/LICENSE b/vendor/react/http/LICENSE
new file mode 100644
index 0000000..d6f8901
--- /dev/null
+++ b/vendor/react/http/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/http/composer.json b/vendor/react/http/composer.json
new file mode 100644
index 0000000..4c9a038
--- /dev/null
+++ b/vendor/react/http/composer.json
@@ -0,0 +1,53 @@
+{
+ "name": "react/http",
+ "description": "Event-driven, streaming HTTP client and server implementation for ReactPHP",
+ "keywords": ["HTTP client", "HTTP server", "HTTP", "HTTPS", "event-driven", "streaming", "client", "server", "PSR-7", "async", "ReactPHP"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "fig/http-message-util": "^1.1",
+ "psr/http-message": "^1.0",
+ "react/event-loop": "^1.2",
+ "react/promise": "^2.3 || ^1.2.1",
+ "react/promise-stream": "^1.1",
+ "react/socket": "^1.9",
+ "react/stream": "^1.2",
+ "ringcentral/psr7": "^1.2"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.5",
+ "clue/http-proxy-react": "^1.7",
+ "clue/reactphp-ssh-proxy": "^1.3",
+ "clue/socks-react": "^1.3",
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": { "React\\Http\\": "src" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Http\\": "tests" }
+ }
+}
diff --git a/vendor/react/http/src/Browser.php b/vendor/react/http/src/Browser.php
new file mode 100644
index 0000000..72847f6
--- /dev/null
+++ b/vendor/react/http/src/Browser.php
@@ -0,0 +1,790 @@
+<?php
+
+namespace React\Http;
+
+use Psr\Http\Message\ResponseInterface;
+use RingCentral\Psr7\Request;
+use RingCentral\Psr7\Uri;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Http\Io\ReadableBodyStream;
+use React\Http\Io\Sender;
+use React\Http\Io\Transaction;
+use React\Promise\PromiseInterface;
+use React\Socket\ConnectorInterface;
+use React\Stream\ReadableStreamInterface;
+use InvalidArgumentException;
+
+/**
+ * @final This class is final and shouldn't be extended as it is likely to be marked final in a future release.
+ */
+class Browser
+{
+ private $transaction;
+ private $baseUrl;
+ private $protocolVersion = '1.1';
+
+ /**
+ * The `Browser` is responsible for sending HTTP requests to your HTTP server
+ * and keeps track of pending incoming HTTP responses.
+ *
+ * ```php
+ * $browser = new React\Http\Browser();
+ * ```
+ *
+ * This class takes two optional arguments for more advanced usage:
+ *
+ * ```php
+ * // constructor signature as of v1.5.0
+ * $browser = new React\Http\Browser(?ConnectorInterface $connector = null, ?LoopInterface $loop = null);
+ *
+ * // legacy constructor signature before v1.5.0
+ * $browser = new React\Http\Browser(?LoopInterface $loop = null, ?ConnectorInterface $connector = null);
+ * ```
+ *
+ * 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
+ * )
+ * ));
+ *
+ * $browser = new React\Http\Browser($connector);
+ * ```
+ *
+ * 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 null|ConnectorInterface|LoopInterface $connector
+ * @param null|LoopInterface|ConnectorInterface $loop
+ * @throws \InvalidArgumentException for invalid arguments
+ */
+ public function __construct($connector = null, $loop = null)
+ {
+ // swap arguments for legacy constructor signature
+ if (($connector instanceof LoopInterface || $connector === null) && ($loop instanceof ConnectorInterface || $loop === null)) {
+ $swap = $loop;
+ $loop = $connector;
+ $connector = $swap;
+ }
+
+ if (($connector !== null && !$connector instanceof ConnectorInterface) || ($loop !== null && !$loop instanceof LoopInterface)) {
+ throw new \InvalidArgumentException('Expected "?ConnectorInterface $connector" and "?LoopInterface $loop" arguments');
+ }
+
+ $loop = $loop ?: Loop::get();
+ $this->transaction = new Transaction(
+ Sender::createFromLoop($loop, $connector),
+ $loop
+ );
+ }
+
+ /**
+ * Sends an HTTP GET request
+ *
+ * ```php
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump((string)$response->getBody());
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also [GET request client example](../examples/01-client-get-request.php).
+ *
+ * @param string $url URL for the request.
+ * @param array $headers
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function get($url, array $headers = array())
+ {
+ return $this->requestMayBeStreaming('GET', $url, $headers);
+ }
+
+ /**
+ * Sends an HTTP POST request
+ *
+ * ```php
+ * $browser->post(
+ * $url,
+ * [
+ * 'Content-Type' => 'application/json'
+ * ],
+ * json_encode($data)
+ * )->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump(json_decode((string)$response->getBody()));
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also [POST JSON client example](../examples/04-client-post-json.php).
+ *
+ * This method is also commonly used to submit HTML form data:
+ *
+ * ```php
+ * $data = [
+ * 'user' => 'Alice',
+ * 'password' => 'secret'
+ * ];
+ *
+ * $browser->post(
+ * $url,
+ * [
+ * 'Content-Type' => 'application/x-www-form-urlencoded'
+ * ],
+ * http_build_query($data)
+ * );
+ * ```
+ *
+ * This method will automatically add a matching `Content-Length` request
+ * header if the outgoing request body is a `string`. If you're using a
+ * streaming request body (`ReadableStreamInterface`), it will default to
+ * using `Transfer-Encoding: chunked` or you have to explicitly pass in a
+ * matching `Content-Length` request header like so:
+ *
+ * ```php
+ * $body = new React\Stream\ThroughStream();
+ * Loop::addTimer(1.0, function () use ($body) {
+ * $body->end("hello world");
+ * });
+ *
+ * $browser->post($url, array('Content-Length' => '11'), $body);
+ * ```
+ *
+ * @param string $url URL for the request.
+ * @param array $headers
+ * @param string|ReadableStreamInterface $body
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function post($url, array $headers = array(), $body = '')
+ {
+ return $this->requestMayBeStreaming('POST', $url, $headers, $body);
+ }
+
+ /**
+ * Sends an HTTP HEAD request
+ *
+ * ```php
+ * $browser->head($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump($response->getHeaders());
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * @param string $url URL for the request.
+ * @param array $headers
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function head($url, array $headers = array())
+ {
+ return $this->requestMayBeStreaming('HEAD', $url, $headers);
+ }
+
+ /**
+ * Sends an HTTP PATCH request
+ *
+ * ```php
+ * $browser->patch(
+ * $url,
+ * [
+ * 'Content-Type' => 'application/json'
+ * ],
+ * json_encode($data)
+ * )->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump(json_decode((string)$response->getBody()));
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This method will automatically add a matching `Content-Length` request
+ * header if the outgoing request body is a `string`. If you're using a
+ * streaming request body (`ReadableStreamInterface`), it will default to
+ * using `Transfer-Encoding: chunked` or you have to explicitly pass in a
+ * matching `Content-Length` request header like so:
+ *
+ * ```php
+ * $body = new React\Stream\ThroughStream();
+ * Loop::addTimer(1.0, function () use ($body) {
+ * $body->end("hello world");
+ * });
+ *
+ * $browser->patch($url, array('Content-Length' => '11'), $body);
+ * ```
+ *
+ * @param string $url URL for the request.
+ * @param array $headers
+ * @param string|ReadableStreamInterface $body
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function patch($url, array $headers = array(), $body = '')
+ {
+ return $this->requestMayBeStreaming('PATCH', $url , $headers, $body);
+ }
+
+ /**
+ * Sends an HTTP PUT request
+ *
+ * ```php
+ * $browser->put(
+ * $url,
+ * [
+ * 'Content-Type' => 'text/xml'
+ * ],
+ * $xml->asXML()
+ * )->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump((string)$response->getBody());
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also [PUT XML client example](../examples/05-client-put-xml.php).
+ *
+ * This method will automatically add a matching `Content-Length` request
+ * header if the outgoing request body is a `string`. If you're using a
+ * streaming request body (`ReadableStreamInterface`), it will default to
+ * using `Transfer-Encoding: chunked` or you have to explicitly pass in a
+ * matching `Content-Length` request header like so:
+ *
+ * ```php
+ * $body = new React\Stream\ThroughStream();
+ * Loop::addTimer(1.0, function () use ($body) {
+ * $body->end("hello world");
+ * });
+ *
+ * $browser->put($url, array('Content-Length' => '11'), $body);
+ * ```
+ *
+ * @param string $url URL for the request.
+ * @param array $headers
+ * @param string|ReadableStreamInterface $body
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function put($url, array $headers = array(), $body = '')
+ {
+ return $this->requestMayBeStreaming('PUT', $url, $headers, $body);
+ }
+
+ /**
+ * Sends an HTTP DELETE request
+ *
+ * ```php
+ * $browser->delete($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump((string)$response->getBody());
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * @param string $url URL for the request.
+ * @param array $headers
+ * @param string|ReadableStreamInterface $body
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function delete($url, array $headers = array(), $body = '')
+ {
+ return $this->requestMayBeStreaming('DELETE', $url, $headers, $body);
+ }
+
+ /**
+ * Sends an arbitrary HTTP request.
+ *
+ * The preferred way to send an HTTP request is by using the above
+ * [request methods](#request-methods), for example the [`get()`](#get)
+ * method to send an HTTP `GET` request.
+ *
+ * As an alternative, if you want to use a custom HTTP request method, you
+ * can use this method:
+ *
+ * ```php
+ * $browser->request('OPTIONS', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump((string)$response->getBody());
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This method will automatically add a matching `Content-Length` request
+ * header if the size of the outgoing request body is known and non-empty.
+ * For an empty request body, if will only include a `Content-Length: 0`
+ * request header if the request method usually expects a request body (only
+ * applies to `POST`, `PUT` and `PATCH`).
+ *
+ * If you're using a streaming request body (`ReadableStreamInterface`), it
+ * will default to using `Transfer-Encoding: chunked` or you have to
+ * explicitly pass in a matching `Content-Length` request header like so:
+ *
+ * ```php
+ * $body = new React\Stream\ThroughStream();
+ * Loop::addTimer(1.0, function () use ($body) {
+ * $body->end("hello world");
+ * });
+ *
+ * $browser->request('POST', $url, array('Content-Length' => '11'), $body);
+ * ```
+ *
+ * @param string $method HTTP request method, e.g. GET/HEAD/POST etc.
+ * @param string $url URL for the request
+ * @param array $headers Additional request headers
+ * @param string|ReadableStreamInterface $body HTTP request body contents
+ * @return PromiseInterface<ResponseInterface,\Exception>
+ */
+ public function request($method, $url, array $headers = array(), $body = '')
+ {
+ return $this->withOptions(array('streaming' => false))->requestMayBeStreaming($method, $url, $headers, $body);
+ }
+
+ /**
+ * Sends an arbitrary HTTP request and receives a streaming response without buffering the response body.
+ *
+ * The preferred way to send an HTTP request is by using the above
+ * [request methods](#request-methods), for example the [`get()`](#get)
+ * method to send an HTTP `GET` request. Each of these methods will buffer
+ * the whole response body in memory by default. This is easy to get started
+ * and works reasonably well for smaller responses.
+ *
+ * In some situations, it's a better idea to use a streaming approach, where
+ * only small chunks have to be kept in memory. You can use this method to
+ * send an arbitrary HTTP request and receive a streaming response. It uses
+ * the same HTTP message API, but does not buffer the response body in
+ * memory. It only processes the response body in small chunks as data is
+ * received and forwards this data through [ReactPHP's Stream API](https://github.com/reactphp/stream).
+ * This works for (any number of) responses of arbitrary sizes.
+ *
+ * ```php
+ * $browser->requestStreaming('GET', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * $body = $response->getBody();
+ * assert($body instanceof Psr\Http\Message\StreamInterface);
+ * assert($body instanceof React\Stream\ReadableStreamInterface);
+ *
+ * $body->on('data', function ($chunk) {
+ * echo $chunk;
+ * });
+ *
+ * $body->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ *
+ * $body->on('close', function () {
+ * echo '[DONE]' . PHP_EOL;
+ * });
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface)
+ * and the [streaming response](#streaming-response) for more details,
+ * examples and possible use-cases.
+ *
+ * This method will automatically add a matching `Content-Length` request
+ * header if the size of the outgoing request body is known and non-empty.
+ * For an empty request body, if will only include a `Content-Length: 0`
+ * request header if the request method usually expects a request body (only
+ * applies to `POST`, `PUT` and `PATCH`).
+ *
+ * If you're using a streaming request body (`ReadableStreamInterface`), it
+ * will default to using `Transfer-Encoding: chunked` or you have to
+ * explicitly pass in a matching `Content-Length` request header like so:
+ *
+ * ```php
+ * $body = new React\Stream\ThroughStream();
+ * Loop::addTimer(1.0, function () use ($body) {
+ * $body->end("hello world");
+ * });
+ *
+ * $browser->requestStreaming('POST', $url, array('Content-Length' => '11'), $body);
+ * ```
+ *
+ * @param string $method HTTP request method, e.g. GET/HEAD/POST etc.
+ * @param string $url URL for the request
+ * @param array $headers Additional request headers
+ * @param string|ReadableStreamInterface $body HTTP request body contents
+ * @return PromiseInterface<ResponseInterface,\Exception>
+ */
+ public function requestStreaming($method, $url, $headers = array(), $body = '')
+ {
+ return $this->withOptions(array('streaming' => true))->requestMayBeStreaming($method, $url, $headers, $body);
+ }
+
+ /**
+ * Changes the maximum timeout used for waiting for pending requests.
+ *
+ * You can pass in the number of seconds to use as a new timeout value:
+ *
+ * ```php
+ * $browser = $browser->withTimeout(10.0);
+ * ```
+ *
+ * You can pass in a bool `false` to disable any timeouts. In this case,
+ * requests can stay pending forever:
+ *
+ * ```php
+ * $browser = $browser->withTimeout(false);
+ * ```
+ *
+ * You can pass in a bool `true` to re-enable default timeout handling. This
+ * will respects PHP's `default_socket_timeout` setting (default 60s):
+ *
+ * ```php
+ * $browser = $browser->withTimeout(true);
+ * ```
+ *
+ * See also [timeouts](#timeouts) for more details about timeout handling.
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
+ * method actually returns a *new* [`Browser`](#browser) instance with the
+ * given timeout value applied.
+ *
+ * @param bool|number $timeout
+ * @return self
+ */
+ public function withTimeout($timeout)
+ {
+ if ($timeout === true) {
+ $timeout = null;
+ } elseif ($timeout === false) {
+ $timeout = -1;
+ } elseif ($timeout < 0) {
+ $timeout = 0;
+ }
+
+ return $this->withOptions(array(
+ 'timeout' => $timeout,
+ ));
+ }
+
+ /**
+ * Changes how HTTP redirects will be followed.
+ *
+ * You can pass in the maximum number of redirects to follow:
+ *
+ * ```php
+ * $browser = $browser->withFollowRedirects(5);
+ * ```
+ *
+ * The request will automatically be rejected when the number of redirects
+ * is exceeded. You can pass in a `0` to reject the request for any
+ * redirects encountered:
+ *
+ * ```php
+ * $browser = $browser->withFollowRedirects(0);
+ *
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * // only non-redirected responses will now end up here
+ * var_dump($response->getHeaders());
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * You can pass in a bool `false` to disable following any redirects. In
+ * this case, requests will resolve with the redirection response instead
+ * of following the `Location` response header:
+ *
+ * ```php
+ * $browser = $browser->withFollowRedirects(false);
+ *
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * // any redirects will now end up here
+ * var_dump($response->getHeaderLine('Location'));
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * You can pass in a bool `true` to re-enable default redirect handling.
+ * This defaults to following a maximum of 10 redirects:
+ *
+ * ```php
+ * $browser = $browser->withFollowRedirects(true);
+ * ```
+ *
+ * See also [redirects](#redirects) for more details about redirect handling.
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
+ * method actually returns a *new* [`Browser`](#browser) instance with the
+ * given redirect setting applied.
+ *
+ * @param bool|int $followRedirects
+ * @return self
+ */
+ public function withFollowRedirects($followRedirects)
+ {
+ return $this->withOptions(array(
+ 'followRedirects' => $followRedirects !== false,
+ 'maxRedirects' => \is_bool($followRedirects) ? null : $followRedirects
+ ));
+ }
+
+ /**
+ * Changes whether non-successful HTTP response status codes (4xx and 5xx) will be rejected.
+ *
+ * You can pass in a bool `false` to disable rejecting incoming responses
+ * that use a 4xx or 5xx response status code. In this case, requests will
+ * resolve with the response message indicating an error condition:
+ *
+ * ```php
+ * $browser = $browser->withRejectErrorResponse(false);
+ *
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * // any HTTP response will now end up here
+ * var_dump($response->getStatusCode(), $response->getReasonPhrase());
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * You can pass in a bool `true` to re-enable default status code handling.
+ * This defaults to rejecting any response status codes in the 4xx or 5xx
+ * range:
+ *
+ * ```php
+ * $browser = $browser->withRejectErrorResponse(true);
+ *
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * // any successful HTTP response will now end up here
+ * var_dump($response->getStatusCode(), $response->getReasonPhrase());
+ * }, function (Exception $e) {
+ * if ($e instanceof React\Http\Message\ResponseException) {
+ * // any HTTP response error message will now end up here
+ * $response = $e->getResponse();
+ * var_dump($response->getStatusCode(), $response->getReasonPhrase());
+ * } else {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * }
+ * });
+ * ```
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
+ * method actually returns a *new* [`Browser`](#browser) instance with the
+ * given setting applied.
+ *
+ * @param bool $obeySuccessCode
+ * @return self
+ */
+ public function withRejectErrorResponse($obeySuccessCode)
+ {
+ return $this->withOptions(array(
+ 'obeySuccessCode' => $obeySuccessCode,
+ ));
+ }
+
+ /**
+ * Changes the base URL used to resolve relative URLs to.
+ *
+ * If you configure a base URL, any requests to relative URLs will be
+ * processed by first resolving this relative to the given absolute base
+ * URL. This supports resolving relative path references (like `../` etc.).
+ * This is particularly useful for (RESTful) API calls where all endpoints
+ * (URLs) are located under a common base URL.
+ *
+ * ```php
+ * $browser = $browser->withBase('http://api.example.com/v3/');
+ *
+ * // will request http://api.example.com/v3/users
+ * $browser->get('users')->then(…);
+ * ```
+ *
+ * You can pass in a `null` base URL to return a new instance that does not
+ * use a base URL:
+ *
+ * ```php
+ * $browser = $browser->withBase(null);
+ * ```
+ *
+ * Accordingly, any requests using relative URLs to a browser that does not
+ * use a base URL can not be completed and will be rejected without sending
+ * a request.
+ *
+ * This method will throw an `InvalidArgumentException` if the given
+ * `$baseUrl` argument is not a valid URL.
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method
+ * actually returns a *new* [`Browser`](#browser) instance with the given base URL applied.
+ *
+ * @param string|null $baseUrl absolute base URL
+ * @return self
+ * @throws InvalidArgumentException if the given $baseUrl is not a valid absolute URL
+ * @see self::withoutBase()
+ */
+ public function withBase($baseUrl)
+ {
+ $browser = clone $this;
+ if ($baseUrl === null) {
+ $browser->baseUrl = null;
+ return $browser;
+ }
+
+ $browser->baseUrl = new Uri($baseUrl);
+ if (!\in_array($browser->baseUrl->getScheme(), array('http', 'https')) || $browser->baseUrl->getHost() === '') {
+ throw new \InvalidArgumentException('Base URL must be absolute');
+ }
+
+ return $browser;
+ }
+
+ /**
+ * Changes the HTTP protocol version that will be used for all subsequent requests.
+ *
+ * All the above [request methods](#request-methods) default to sending
+ * requests as HTTP/1.1. This is the preferred HTTP protocol version which
+ * also provides decent backwards-compatibility with legacy HTTP/1.0
+ * servers. As such, there should rarely be a need to explicitly change this
+ * protocol version.
+ *
+ * If you want to explicitly use the legacy HTTP/1.0 protocol version, you
+ * can use this method:
+ *
+ * ```php
+ * $browser = $browser->withProtocolVersion('1.0');
+ *
+ * $browser->get($url)->then(…);
+ * ```
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
+ * method actually returns a *new* [`Browser`](#browser) instance with the
+ * new protocol version applied.
+ *
+ * @param string $protocolVersion HTTP protocol version to use, must be one of "1.1" or "1.0"
+ * @return self
+ * @throws InvalidArgumentException
+ */
+ public function withProtocolVersion($protocolVersion)
+ {
+ if (!\in_array($protocolVersion, array('1.0', '1.1'), true)) {
+ throw new InvalidArgumentException('Invalid HTTP protocol version, must be one of "1.1" or "1.0"');
+ }
+
+ $browser = clone $this;
+ $browser->protocolVersion = (string) $protocolVersion;
+
+ return $browser;
+ }
+
+ /**
+ * Changes the maximum size for buffering a response body.
+ *
+ * The preferred way to send an HTTP request is by using the above
+ * [request methods](#request-methods), for example the [`get()`](#get)
+ * method to send an HTTP `GET` request. Each of these methods will buffer
+ * the whole response body in memory by default. This is easy to get started
+ * and works reasonably well for smaller responses.
+ *
+ * By default, the response body buffer will be limited to 16 MiB. If the
+ * response body exceeds this maximum size, the request will be rejected.
+ *
+ * You can pass in the maximum number of bytes to buffer:
+ *
+ * ```php
+ * $browser = $browser->withResponseBuffer(1024 * 1024);
+ *
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * // response body will not exceed 1 MiB
+ * var_dump($response->getHeaders(), (string) $response->getBody());
+ * }, function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * Note that the response body buffer has to be kept in memory for each
+ * pending request until its transfer is completed and it will only be freed
+ * after a pending request is fulfilled. As such, increasing this maximum
+ * buffer size to allow larger response bodies is usually not recommended.
+ * Instead, you can use the [`requestStreaming()` method](#requeststreaming)
+ * to receive responses with arbitrary sizes without buffering. Accordingly,
+ * this maximum buffer size setting has no effect on streaming responses.
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
+ * method actually returns a *new* [`Browser`](#browser) instance with the
+ * given setting applied.
+ *
+ * @param int $maximumSize
+ * @return self
+ * @see self::requestStreaming()
+ */
+ public function withResponseBuffer($maximumSize)
+ {
+ return $this->withOptions(array(
+ 'maximumSize' => $maximumSize
+ ));
+ }
+
+ /**
+ * Changes the [options](#options) to use:
+ *
+ * The [`Browser`](#browser) class exposes several options for the handling of
+ * HTTP transactions. These options resemble some of PHP's
+ * [HTTP context options](http://php.net/manual/en/context.http.php) and
+ * can be controlled via the following API (and their defaults):
+ *
+ * ```php
+ * // deprecated
+ * $newBrowser = $browser->withOptions(array(
+ * 'timeout' => null, // see withTimeout() instead
+ * 'followRedirects' => true, // see withFollowRedirects() instead
+ * 'maxRedirects' => 10, // see withFollowRedirects() instead
+ * 'obeySuccessCode' => true, // see withRejectErrorResponse() instead
+ * 'streaming' => false, // deprecated, see requestStreaming() instead
+ * ));
+ * ```
+ *
+ * See also [timeouts](#timeouts), [redirects](#redirects) and
+ * [streaming](#streaming) for more details.
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
+ * method actually returns a *new* [`Browser`](#browser) instance with the
+ * options applied.
+ *
+ * @param array $options
+ * @return self
+ * @see self::withTimeout()
+ * @see self::withFollowRedirects()
+ * @see self::withRejectErrorResponse()
+ */
+ private function withOptions(array $options)
+ {
+ $browser = clone $this;
+ $browser->transaction = $this->transaction->withOptions($options);
+
+ return $browser;
+ }
+
+ /**
+ * @param string $method
+ * @param string $url
+ * @param array $headers
+ * @param string|ReadableStreamInterface $body
+ * @return PromiseInterface<ResponseInterface,\Exception>
+ */
+ private function requestMayBeStreaming($method, $url, array $headers = array(), $body = '')
+ {
+ if ($this->baseUrl !== null) {
+ // ensure we're actually below the base URL
+ $url = Uri::resolve($this->baseUrl, $url);
+ }
+
+ if ($body instanceof ReadableStreamInterface) {
+ $body = new ReadableBodyStream($body);
+ }
+
+ return $this->transaction->send(
+ new Request($method, $url, $headers, $body, $this->protocolVersion)
+ );
+ }
+}
diff --git a/vendor/react/http/src/Client/Client.php b/vendor/react/http/src/Client/Client.php
new file mode 100644
index 0000000..7a97349
--- /dev/null
+++ b/vendor/react/http/src/Client/Client.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace React\Http\Client;
+
+use React\EventLoop\LoopInterface;
+use React\Socket\ConnectorInterface;
+use React\Socket\Connector;
+
+/**
+ * @internal
+ */
+class Client
+{
+ private $connector;
+
+ public function __construct(LoopInterface $loop, ConnectorInterface $connector = null)
+ {
+ if ($connector === null) {
+ $connector = new Connector(array(), $loop);
+ }
+
+ $this->connector = $connector;
+ }
+
+ public function request($method, $url, array $headers = array(), $protocolVersion = '1.0')
+ {
+ $requestData = new RequestData($method, $url, $headers, $protocolVersion);
+
+ return new Request($this->connector, $requestData);
+ }
+}
diff --git a/vendor/react/http/src/Client/Request.php b/vendor/react/http/src/Client/Request.php
new file mode 100644
index 0000000..51e0331
--- /dev/null
+++ b/vendor/react/http/src/Client/Request.php
@@ -0,0 +1,237 @@
+<?php
+
+namespace React\Http\Client;
+
+use Evenement\EventEmitter;
+use React\Promise;
+use React\Socket\ConnectionInterface;
+use React\Socket\ConnectorInterface;
+use React\Stream\WritableStreamInterface;
+use RingCentral\Psr7 as gPsr;
+
+/**
+ * @event response
+ * @event drain
+ * @event error
+ * @event end
+ * @internal
+ */
+class Request extends EventEmitter implements WritableStreamInterface
+{
+ const STATE_INIT = 0;
+ const STATE_WRITING_HEAD = 1;
+ const STATE_HEAD_WRITTEN = 2;
+ const STATE_END = 3;
+
+ private $connector;
+ private $requestData;
+
+ private $stream;
+ private $buffer;
+ private $responseFactory;
+ private $state = self::STATE_INIT;
+ private $ended = false;
+
+ private $pendingWrites = '';
+
+ public function __construct(ConnectorInterface $connector, RequestData $requestData)
+ {
+ $this->connector = $connector;
+ $this->requestData = $requestData;
+ }
+
+ public function isWritable()
+ {
+ return self::STATE_END > $this->state && !$this->ended;
+ }
+
+ private function writeHead()
+ {
+ $this->state = self::STATE_WRITING_HEAD;
+
+ $requestData = $this->requestData;
+ $streamRef = &$this->stream;
+ $stateRef = &$this->state;
+ $pendingWrites = &$this->pendingWrites;
+ $that = $this;
+
+ $promise = $this->connect();
+ $promise->then(
+ function (ConnectionInterface $stream) use ($requestData, &$streamRef, &$stateRef, &$pendingWrites, $that) {
+ $streamRef = $stream;
+
+ $stream->on('drain', array($that, 'handleDrain'));
+ $stream->on('data', array($that, 'handleData'));
+ $stream->on('end', array($that, 'handleEnd'));
+ $stream->on('error', array($that, 'handleError'));
+ $stream->on('close', array($that, 'handleClose'));
+
+ $headers = (string) $requestData;
+
+ $more = $stream->write($headers . $pendingWrites);
+
+ $stateRef = Request::STATE_HEAD_WRITTEN;
+
+ // clear pending writes if non-empty
+ if ($pendingWrites !== '') {
+ $pendingWrites = '';
+
+ if ($more) {
+ $that->emit('drain');
+ }
+ }
+ },
+ array($this, 'closeError')
+ );
+
+ $this->on('close', function() use ($promise) {
+ $promise->cancel();
+ });
+ }
+
+ public function write($data)
+ {
+ if (!$this->isWritable()) {
+ return false;
+ }
+
+ // write directly to connection stream if already available
+ if (self::STATE_HEAD_WRITTEN <= $this->state) {
+ return $this->stream->write($data);
+ }
+
+ // otherwise buffer and try to establish connection
+ $this->pendingWrites .= $data;
+ if (self::STATE_WRITING_HEAD > $this->state) {
+ $this->writeHead();
+ }
+
+ return false;
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->isWritable()) {
+ return;
+ }
+
+ if (null !== $data) {
+ $this->write($data);
+ } else if (self::STATE_WRITING_HEAD > $this->state) {
+ $this->writeHead();
+ }
+
+ $this->ended = true;
+ }
+
+ /** @internal */
+ public function handleDrain()
+ {
+ $this->emit('drain');
+ }
+
+ /** @internal */
+ public function handleData($data)
+ {
+ $this->buffer .= $data;
+
+ // buffer until double CRLF (or double LF for compatibility with legacy servers)
+ if (false !== strpos($this->buffer, "\r\n\r\n") || false !== strpos($this->buffer, "\n\n")) {
+ try {
+ $response = gPsr\parse_response($this->buffer);
+ $bodyChunk = (string) $response->getBody();
+ } catch (\InvalidArgumentException $exception) {
+ $this->emit('error', array($exception));
+ }
+
+ $this->buffer = null;
+
+ $this->stream->removeListener('drain', array($this, 'handleDrain'));
+ $this->stream->removeListener('data', array($this, 'handleData'));
+ $this->stream->removeListener('end', array($this, 'handleEnd'));
+ $this->stream->removeListener('error', array($this, 'handleError'));
+ $this->stream->removeListener('close', array($this, 'handleClose'));
+
+ if (!isset($response)) {
+ return;
+ }
+
+ $this->stream->on('close', array($this, 'handleClose'));
+
+ $this->emit('response', array($response, $this->stream));
+
+ $this->stream->emit('data', array($bodyChunk));
+ }
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ $this->closeError(new \RuntimeException(
+ "Connection ended before receiving response"
+ ));
+ }
+
+ /** @internal */
+ public function handleError(\Exception $error)
+ {
+ $this->closeError(new \RuntimeException(
+ "An error occurred in the underlying stream",
+ 0,
+ $error
+ ));
+ }
+
+ /** @internal */
+ public function handleClose()
+ {
+ $this->close();
+ }
+
+ /** @internal */
+ public function closeError(\Exception $error)
+ {
+ if (self::STATE_END <= $this->state) {
+ return;
+ }
+ $this->emit('error', array($error));
+ $this->close();
+ }
+
+ public function close()
+ {
+ if (self::STATE_END <= $this->state) {
+ return;
+ }
+
+ $this->state = self::STATE_END;
+ $this->pendingWrites = '';
+
+ if ($this->stream) {
+ $this->stream->close();
+ }
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+
+ protected function connect()
+ {
+ $scheme = $this->requestData->getScheme();
+ if ($scheme !== 'https' && $scheme !== 'http') {
+ return Promise\reject(
+ new \InvalidArgumentException('Invalid request URL given')
+ );
+ }
+
+ $host = $this->requestData->getHost();
+ $port = $this->requestData->getPort();
+
+ if ($scheme === 'https') {
+ $host = 'tls://' . $host;
+ }
+
+ return $this->connector
+ ->connect($host . ':' . $port);
+ }
+}
diff --git a/vendor/react/http/src/Client/RequestData.php b/vendor/react/http/src/Client/RequestData.php
new file mode 100644
index 0000000..a5908a0
--- /dev/null
+++ b/vendor/react/http/src/Client/RequestData.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace React\Http\Client;
+
+/**
+ * @internal
+ */
+class RequestData
+{
+ private $method;
+ private $url;
+ private $headers;
+ private $protocolVersion;
+
+ public function __construct($method, $url, array $headers = array(), $protocolVersion = '1.0')
+ {
+ $this->method = $method;
+ $this->url = $url;
+ $this->headers = $headers;
+ $this->protocolVersion = $protocolVersion;
+ }
+
+ private function mergeDefaultheaders(array $headers)
+ {
+ $port = ($this->getDefaultPort() === $this->getPort()) ? '' : ":{$this->getPort()}";
+ $connectionHeaders = ('1.1' === $this->protocolVersion) ? array('Connection' => 'close') : array();
+ $authHeaders = $this->getAuthHeaders();
+
+ $defaults = array_merge(
+ array(
+ 'Host' => $this->getHost().$port,
+ 'User-Agent' => 'ReactPHP/1',
+ ),
+ $connectionHeaders,
+ $authHeaders
+ );
+
+ // remove all defaults that already exist in $headers
+ $lower = array_change_key_case($headers, CASE_LOWER);
+ foreach ($defaults as $key => $_) {
+ if (isset($lower[strtolower($key)])) {
+ unset($defaults[$key]);
+ }
+ }
+
+ return array_merge($defaults, $headers);
+ }
+
+ public function getScheme()
+ {
+ return parse_url($this->url, PHP_URL_SCHEME);
+ }
+
+ public function getHost()
+ {
+ return parse_url($this->url, PHP_URL_HOST);
+ }
+
+ public function getPort()
+ {
+ return (int) parse_url($this->url, PHP_URL_PORT) ?: $this->getDefaultPort();
+ }
+
+ public function getDefaultPort()
+ {
+ return ('https' === $this->getScheme()) ? 443 : 80;
+ }
+
+ public function getPath()
+ {
+ $path = parse_url($this->url, PHP_URL_PATH);
+ $queryString = parse_url($this->url, PHP_URL_QUERY);
+
+ // assume "/" path by default, but allow "OPTIONS *"
+ if ($path === null) {
+ $path = ($this->method === 'OPTIONS' && $queryString === null) ? '*': '/';
+ }
+ if ($queryString !== null) {
+ $path .= '?' . $queryString;
+ }
+
+ return $path;
+ }
+
+ public function setProtocolVersion($version)
+ {
+ $this->protocolVersion = $version;
+ }
+
+ public function __toString()
+ {
+ $headers = $this->mergeDefaultheaders($this->headers);
+
+ $data = '';
+ $data .= "{$this->method} {$this->getPath()} HTTP/{$this->protocolVersion}\r\n";
+ foreach ($headers as $name => $values) {
+ foreach ((array)$values as $value) {
+ $data .= "$name: $value\r\n";
+ }
+ }
+ $data .= "\r\n";
+
+ return $data;
+ }
+
+ private function getUrlUserPass()
+ {
+ $components = parse_url($this->url);
+
+ if (isset($components['user'])) {
+ return array(
+ 'user' => $components['user'],
+ 'pass' => isset($components['pass']) ? $components['pass'] : null,
+ );
+ }
+ }
+
+ private function getAuthHeaders()
+ {
+ if (null !== $auth = $this->getUrlUserPass()) {
+ return array(
+ 'Authorization' => 'Basic ' . base64_encode($auth['user'].':'.$auth['pass']),
+ );
+ }
+
+ return array();
+ }
+}
diff --git a/vendor/react/http/src/HttpServer.php b/vendor/react/http/src/HttpServer.php
new file mode 100644
index 0000000..f233473
--- /dev/null
+++ b/vendor/react/http/src/HttpServer.php
@@ -0,0 +1,351 @@
+<?php
+
+namespace React\Http;
+
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Http\Io\IniUtil;
+use React\Http\Io\MiddlewareRunner;
+use React\Http\Io\StreamingServer;
+use React\Http\Middleware\LimitConcurrentRequestsMiddleware;
+use React\Http\Middleware\StreamingRequestMiddleware;
+use React\Http\Middleware\RequestBodyBufferMiddleware;
+use React\Http\Middleware\RequestBodyParserMiddleware;
+use React\Socket\ServerInterface;
+
+/**
+ * The `React\Http\HttpServer` class is responsible for handling incoming connections and then
+ * processing each incoming HTTP request.
+ *
+ * When a complete HTTP request has been received, it will invoke the given
+ * request handler function. This request handler function needs to be passed to
+ * the constructor and will be invoked with the respective [request](#server-request)
+ * object and expects a [response](#server-response) object in return:
+ *
+ * ```php
+ * $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
+ * return new React\Http\Message\Response(
+ * React\Http\Message\Response::STATUS_OK,
+ * array(
+ * 'Content-Type' => 'text/plain'
+ * ),
+ * "Hello World!\n"
+ * );
+ * });
+ * ```
+ *
+ * Each incoming HTTP request message is always represented by the
+ * [PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface),
+ * see also following [request](#server-request) chapter for more details.
+ *
+ * Each outgoing HTTP response message is always represented by the
+ * [PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface),
+ * see also following [response](#server-response) chapter for more details.
+ *
+ * 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.
+ *
+ * In order to start listening for any incoming connections, the `HttpServer` needs
+ * to be attached to an instance of
+ * [`React\Socket\ServerInterface`](https://github.com/reactphp/socket#serverinterface)
+ * through the [`listen()`](#listen) method as described in the following
+ * chapter. In its most simple form, you can attach this to a
+ * [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
+ * in order to start a plaintext HTTP server like this:
+ *
+ * ```php
+ * $http = new React\Http\HttpServer($handler);
+ *
+ * $socket = new React\Socket\SocketServer('0.0.0.0:8080');
+ * $http->listen($socket);
+ * ```
+ *
+ * See also the [`listen()`](#listen) method and
+ * [hello world server example](../examples/51-server-hello-world.php)
+ * for more details.
+ *
+ * By default, the `HttpServer` buffers and parses the complete incoming HTTP
+ * request in memory. It will invoke the given request handler function when the
+ * complete request headers and request body has been received. This means the
+ * [request](#server-request) object passed to your request handler function will be
+ * fully compatible with PSR-7 (http-message). This provides sane defaults for
+ * 80% of the use cases and is the recommended way to use this library unless
+ * you're sure you know what you're doing.
+ *
+ * On the other hand, buffering complete HTTP requests in memory until they can
+ * be processed by your request handler function means that this class has to
+ * employ a number of limits to avoid consuming too much memory. In order to
+ * take the more advanced configuration out your hand, it respects setting from
+ * your [`php.ini`](https://www.php.net/manual/en/ini.core.php) to apply its
+ * default settings. This is a list of PHP settings this class respects with
+ * their respective default values:
+ *
+ * ```
+ * memory_limit 128M
+ * post_max_size 8M // capped at 64K
+ *
+ * enable_post_data_reading 1
+ * max_input_nesting_level 64
+ * max_input_vars 1000
+ *
+ * file_uploads 1
+ * upload_max_filesize 2M
+ * max_file_uploads 20
+ * ```
+ *
+ * In particular, the `post_max_size` setting limits how much memory a single
+ * HTTP request is allowed to consume while buffering its request body. This
+ * needs to be limited because the server can process a large number of requests
+ * concurrently, so the server may potentially consume a large amount of memory
+ * otherwise. To support higher concurrency by default, this value is capped
+ * at `64K`. If you assign a higher value, it will only allow `64K` by default.
+ * If a request exceeds this limit, its request body will be ignored and it will
+ * be processed like a request with no request body at all. See below for
+ * explicit configuration to override this setting.
+ *
+ * By default, this class will try to avoid consuming more than half of your
+ * `memory_limit` for buffering multiple concurrent HTTP requests. As such, with
+ * the above default settings of `128M` max, it will try to consume no more than
+ * `64M` for buffering multiple concurrent HTTP requests. As a consequence, it
+ * will limit the concurrency to `1024` HTTP requests with the above defaults.
+ *
+ * It is imperative that you assign reasonable values to your PHP ini settings.
+ * It is usually recommended to not support buffering incoming HTTP requests
+ * with a large HTTP request body (e.g. large file uploads). If you want to
+ * increase this buffer size, you will have to also increase the total memory
+ * limit to allow for more concurrent requests (set `memory_limit 512M` or more)
+ * or explicitly limit concurrency.
+ *
+ * In order to override the above buffering defaults, you can configure the
+ * `HttpServer` explicitly. You can use the
+ * [`LimitConcurrentRequestsMiddleware`](#limitconcurrentrequestsmiddleware) and
+ * [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) (see below)
+ * to explicitly configure the total number of requests that can be handled at
+ * once like this:
+ *
+ * ```php
+ * $http = new React\Http\HttpServer(
+ * new React\Http\Middleware\StreamingRequestMiddleware(),
+ * new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
+ * new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
+ * new React\Http\Middleware\RequestBodyParserMiddleware(),
+ * $handler
+ * ));
+ * ```
+ *
+ * In this example, we allow processing up to 100 concurrent requests at once
+ * and each request can buffer up to `2M`. This means you may have to keep a
+ * maximum of `200M` of memory for incoming request body buffers. Accordingly,
+ * you need to adjust the `memory_limit` ini setting to allow for these buffers
+ * plus your actual application logic memory requirements (think `512M` or more).
+ *
+ * > Internally, this class automatically assigns these middleware handlers
+ * automatically when no [`StreamingRequestMiddleware`](#streamingrequestmiddleware)
+ * is given. Accordingly, you can use this example to override all default
+ * settings to implement custom limits.
+ *
+ * As an alternative to buffering the complete request body in memory, you can
+ * also use a streaming approach where only small chunks of data have to be kept
+ * in memory:
+ *
+ * ```php
+ * $http = new React\Http\HttpServer(
+ * new React\Http\Middleware\StreamingRequestMiddleware(),
+ * $handler
+ * );
+ * ```
+ *
+ * In this case, it will invoke the request handler function once the HTTP
+ * request headers have been received, i.e. before receiving the potentially
+ * much larger HTTP request body. This means the [request](#server-request) passed to
+ * your request handler function may not be fully compatible with PSR-7. This is
+ * specifically designed to help with more advanced use cases where you want to
+ * have full control over consuming the incoming HTTP request body and
+ * concurrency settings. See also [streaming incoming request](#streaming-incoming-request)
+ * below for more details.
+ *
+ * > Changelog v1.5.0: This class has been renamed to `HttpServer` from the
+ * previous `Server` class in order to avoid any ambiguities.
+ * The previous name has been deprecated and should not be used anymore.
+ */
+final class HttpServer extends EventEmitter
+{
+ /**
+ * The maximum buffer size used for each request.
+ *
+ * This needs to be limited because the server can process a large number of
+ * requests concurrently, so the server may potentially consume a large
+ * amount of memory otherwise.
+ *
+ * See `RequestBodyBufferMiddleware` to override this setting.
+ *
+ * @internal
+ */
+ const MAXIMUM_BUFFER_SIZE = 65536; // 64 KiB
+
+ /**
+ * @var StreamingServer
+ */
+ private $streamingServer;
+
+ /**
+ * Creates an HTTP server that invokes the given callback for each incoming HTTP request
+ *
+ * In order to process any connections, the server needs to be attached to an
+ * instance of `React\Socket\ServerInterface` which emits underlying streaming
+ * connections in order to then parse incoming data as HTTP.
+ * See also [listen()](#listen) for more details.
+ *
+ * @param callable|LoopInterface $requestHandlerOrLoop
+ * @param callable[] ...$requestHandler
+ * @see self::listen()
+ */
+ public function __construct($requestHandlerOrLoop)
+ {
+ $requestHandlers = \func_get_args();
+ if (reset($requestHandlers) instanceof LoopInterface) {
+ $loop = \array_shift($requestHandlers);
+ } else {
+ $loop = Loop::get();
+ }
+
+ $requestHandlersCount = \count($requestHandlers);
+ if ($requestHandlersCount === 0 || \count(\array_filter($requestHandlers, 'is_callable')) < $requestHandlersCount) {
+ throw new \InvalidArgumentException('Invalid request handler given');
+ }
+
+ $streaming = false;
+ foreach ((array) $requestHandlers as $handler) {
+ if ($handler instanceof StreamingRequestMiddleware) {
+ $streaming = true;
+ break;
+ }
+ }
+
+ $middleware = array();
+ if (!$streaming) {
+ $maxSize = $this->getMaxRequestSize();
+ $concurrency = $this->getConcurrentRequestsLimit(\ini_get('memory_limit'), $maxSize);
+ if ($concurrency !== null) {
+ $middleware[] = new LimitConcurrentRequestsMiddleware($concurrency);
+ }
+ $middleware[] = new RequestBodyBufferMiddleware($maxSize);
+ // Checking for an empty string because that is what a boolean
+ // false is returned as by ini_get depending on the PHP version.
+ // @link http://php.net/manual/en/ini.core.php#ini.enable-post-data-reading
+ // @link http://php.net/manual/en/function.ini-get.php#refsect1-function.ini-get-notes
+ // @link https://3v4l.org/qJtsa
+ $enablePostDataReading = \ini_get('enable_post_data_reading');
+ if ($enablePostDataReading !== '') {
+ $middleware[] = new RequestBodyParserMiddleware();
+ }
+ }
+
+ $middleware = \array_merge($middleware, $requestHandlers);
+
+ /**
+ * Filter out any configuration middleware, no need to run requests through something that isn't
+ * doing anything with the request.
+ */
+ $middleware = \array_filter($middleware, function ($handler) {
+ return !($handler instanceof StreamingRequestMiddleware);
+ });
+
+ $this->streamingServer = new StreamingServer($loop, new MiddlewareRunner($middleware));
+
+ $that = $this;
+ $this->streamingServer->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ /**
+ * Starts listening for HTTP requests on the given socket server instance
+ *
+ * The given [`React\Socket\ServerInterface`](https://github.com/reactphp/socket#serverinterface)
+ * is responsible for emitting the underlying streaming connections. This
+ * HTTP server needs to be attached to it in order to process any
+ * connections and pase incoming streaming data as incoming HTTP request
+ * messages. In its most common form, you can attach this to a
+ * [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
+ * in order to start a plaintext HTTP server like this:
+ *
+ * ```php
+ * $http = new React\Http\HttpServer($handler);
+ *
+ * $socket = new React\Socket\SocketServer('0.0.0.0:8080');
+ * $http->listen($socket);
+ * ```
+ *
+ * See also [hello world server example](../examples/51-server-hello-world.php)
+ * for more details.
+ *
+ * This example will start listening for HTTP requests on the alternative
+ * HTTP port `8080` on all interfaces (publicly). As an alternative, it is
+ * very common to use a reverse proxy and let this HTTP server listen on the
+ * localhost (loopback) interface only by using the listen address
+ * `127.0.0.1:8080` instead. This way, you host your application(s) on the
+ * default HTTP port `80` and only route specific requests to this HTTP
+ * server.
+ *
+ * Likewise, it's usually recommended to use a reverse proxy setup to accept
+ * secure HTTPS requests on default HTTPS port `443` (TLS termination) and
+ * only route plaintext requests to this HTTP server. As an alternative, you
+ * can also accept secure HTTPS requests with this HTTP server by attaching
+ * this to a [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
+ * using a secure TLS listen address, a certificate file and optional
+ * `passphrase` like this:
+ *
+ * ```php
+ * $http = new React\Http\HttpServer($handler);
+ *
+ * $socket = new React\Socket\SocketServer('tls://0.0.0.0:8443', array(
+ * 'tls' => array(
+ * 'local_cert' => __DIR__ . '/localhost.pem'
+ * )
+ * ));
+ * $http->listen($socket);
+ * ```
+ *
+ * See also [hello world HTTPS example](../examples/61-server-hello-world-https.php)
+ * for more details.
+ *
+ * @param ServerInterface $socket
+ */
+ public function listen(ServerInterface $socket)
+ {
+ $this->streamingServer->listen($socket);
+ }
+
+ /**
+ * @param string $memory_limit
+ * @param string $post_max_size
+ * @return ?int
+ */
+ private function getConcurrentRequestsLimit($memory_limit, $post_max_size)
+ {
+ if ($memory_limit == -1) {
+ return null;
+ }
+
+ $availableMemory = IniUtil::iniSizeToBytes($memory_limit) / 2;
+ $concurrentRequests = (int) \ceil($availableMemory / IniUtil::iniSizeToBytes($post_max_size));
+
+ return $concurrentRequests;
+ }
+
+ /**
+ * @param ?string $post_max_size
+ * @return int
+ */
+ private function getMaxRequestSize($post_max_size = null)
+ {
+ $maxSize = IniUtil::iniSizeToBytes($post_max_size === null ? \ini_get('post_max_size') : $post_max_size);
+
+ return ($maxSize === 0 || $maxSize >= self::MAXIMUM_BUFFER_SIZE) ? self::MAXIMUM_BUFFER_SIZE : $maxSize;
+ }
+}
diff --git a/vendor/react/http/src/Io/BufferedBody.php b/vendor/react/http/src/Io/BufferedBody.php
new file mode 100644
index 0000000..4a4d839
--- /dev/null
+++ b/vendor/react/http/src/Io/BufferedBody.php
@@ -0,0 +1,179 @@
+<?php
+
+namespace React\Http\Io;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * [Internal] PSR-7 message body implementation using an in-memory buffer
+ *
+ * @internal
+ */
+class BufferedBody implements StreamInterface
+{
+ private $buffer = '';
+ private $position = 0;
+ private $closed = false;
+
+ /**
+ * @param string $buffer
+ */
+ public function __construct($buffer)
+ {
+ $this->buffer = $buffer;
+ }
+
+ public function __toString()
+ {
+ if ($this->closed) {
+ return '';
+ }
+
+ $this->seek(0);
+
+ return $this->getContents();
+ }
+
+ public function close()
+ {
+ $this->buffer = '';
+ $this->position = 0;
+ $this->closed = true;
+ }
+
+ public function detach()
+ {
+ $this->close();
+
+ return null;
+ }
+
+ public function getSize()
+ {
+ return $this->closed ? null : \strlen($this->buffer);
+ }
+
+ public function tell()
+ {
+ if ($this->closed) {
+ throw new \RuntimeException('Unable to tell position of closed stream');
+ }
+
+ return $this->position;
+ }
+
+ public function eof()
+ {
+ return $this->position >= \strlen($this->buffer);
+ }
+
+ public function isSeekable()
+ {
+ return !$this->closed;
+ }
+
+ public function seek($offset, $whence = \SEEK_SET)
+ {
+ if ($this->closed) {
+ throw new \RuntimeException('Unable to seek on closed stream');
+ }
+
+ $old = $this->position;
+
+ if ($whence === \SEEK_SET) {
+ $this->position = $offset;
+ } elseif ($whence === \SEEK_CUR) {
+ $this->position += $offset;
+ } elseif ($whence === \SEEK_END) {
+ $this->position = \strlen($this->buffer) + $offset;
+ } else {
+ throw new \InvalidArgumentException('Invalid seek mode given');
+ }
+
+ if (!\is_int($this->position) || $this->position < 0) {
+ $this->position = $old;
+ throw new \RuntimeException('Unable to seek to position');
+ }
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function isWritable()
+ {
+ return !$this->closed;
+ }
+
+ public function write($string)
+ {
+ if ($this->closed) {
+ throw new \RuntimeException('Unable to write to closed stream');
+ }
+
+ if ($string === '') {
+ return 0;
+ }
+
+ if ($this->position > 0 && !isset($this->buffer[$this->position - 1])) {
+ $this->buffer = \str_pad($this->buffer, $this->position, "\0");
+ }
+
+ $len = \strlen($string);
+ $this->buffer = \substr($this->buffer, 0, $this->position) . $string . \substr($this->buffer, $this->position + $len);
+ $this->position += $len;
+
+ return $len;
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed;
+ }
+
+ public function read($length)
+ {
+ if ($this->closed) {
+ throw new \RuntimeException('Unable to read from closed stream');
+ }
+
+ if ($length < 1) {
+ throw new \InvalidArgumentException('Invalid read length given');
+ }
+
+ if ($this->position + $length > \strlen($this->buffer)) {
+ $length = \strlen($this->buffer) - $this->position;
+ }
+
+ if (!isset($this->buffer[$this->position])) {
+ return '';
+ }
+
+ $pos = $this->position;
+ $this->position += $length;
+
+ return \substr($this->buffer, $pos, $length);
+ }
+
+ public function getContents()
+ {
+ if ($this->closed) {
+ throw new \RuntimeException('Unable to read from closed stream');
+ }
+
+ if (!isset($this->buffer[$this->position])) {
+ return '';
+ }
+
+ $pos = $this->position;
+ $this->position = \strlen($this->buffer);
+
+ return \substr($this->buffer, $pos);
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $key === null ? array() : null;
+ }
+}
diff --git a/vendor/react/http/src/Io/ChunkedDecoder.php b/vendor/react/http/src/Io/ChunkedDecoder.php
new file mode 100644
index 0000000..2f58f42
--- /dev/null
+++ b/vendor/react/http/src/Io/ChunkedDecoder.php
@@ -0,0 +1,175 @@
+<?php
+
+namespace React\Http\Io;
+
+use Evenement\EventEmitter;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+use Exception;
+
+/**
+ * [Internal] Decodes "Transfer-Encoding: chunked" from given stream and returns only payload data.
+ *
+ * This is used internally to decode incoming requests with this encoding.
+ *
+ * @internal
+ */
+class ChunkedDecoder extends EventEmitter implements ReadableStreamInterface
+{
+ const CRLF = "\r\n";
+ const MAX_CHUNK_HEADER_SIZE = 1024;
+
+ private $closed = false;
+ private $input;
+ private $buffer = '';
+ private $chunkSize = 0;
+ private $transferredSize = 0;
+ private $headerCompleted = false;
+
+ public function __construct(ReadableStreamInterface $input)
+ {
+ $this->input = $input;
+
+ $this->input->on('data', array($this, 'handleData'));
+ $this->input->on('end', array($this, 'handleEnd'));
+ $this->input->on('error', array($this, 'handleError'));
+ $this->input->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed && $this->input->isReadable();
+ }
+
+ public function pause()
+ {
+ $this->input->pause();
+ }
+
+ public function resume()
+ {
+ $this->input->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ Util::pipe($this, $dest, $options);
+
+ return $dest;
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->buffer = '';
+
+ $this->closed = true;
+
+ $this->input->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ if (!$this->closed) {
+ $this->handleError(new Exception('Unexpected end event'));
+ }
+ }
+
+ /** @internal */
+ public function handleError(Exception $e)
+ {
+ $this->emit('error', array($e));
+ $this->close();
+ }
+
+ /** @internal */
+ public function handleData($data)
+ {
+ $this->buffer .= $data;
+
+ while ($this->buffer !== '') {
+ if (!$this->headerCompleted) {
+ $positionCrlf = \strpos($this->buffer, static::CRLF);
+
+ if ($positionCrlf === false) {
+ // Header shouldn't be bigger than 1024 bytes
+ if (isset($this->buffer[static::MAX_CHUNK_HEADER_SIZE])) {
+ $this->handleError(new Exception('Chunk header size inclusive extension bigger than' . static::MAX_CHUNK_HEADER_SIZE. ' bytes'));
+ }
+ return;
+ }
+
+ $header = \strtolower((string)\substr($this->buffer, 0, $positionCrlf));
+ $hexValue = $header;
+
+ if (\strpos($header, ';') !== false) {
+ $array = \explode(';', $header);
+ $hexValue = $array[0];
+ }
+
+ if ($hexValue !== '') {
+ $hexValue = \ltrim(\trim($hexValue), "0");
+ if ($hexValue === '') {
+ $hexValue = "0";
+ }
+ }
+
+ $this->chunkSize = @\hexdec($hexValue);
+ if (!\is_int($this->chunkSize) || \dechex($this->chunkSize) !== $hexValue) {
+ $this->handleError(new Exception($hexValue . ' is not a valid hexadecimal number'));
+ return;
+ }
+
+ $this->buffer = (string)\substr($this->buffer, $positionCrlf + 2);
+ $this->headerCompleted = true;
+ if ($this->buffer === '') {
+ return;
+ }
+ }
+
+ $chunk = (string)\substr($this->buffer, 0, $this->chunkSize - $this->transferredSize);
+
+ if ($chunk !== '') {
+ $this->transferredSize += \strlen($chunk);
+ $this->emit('data', array($chunk));
+ $this->buffer = (string)\substr($this->buffer, \strlen($chunk));
+ }
+
+ $positionCrlf = \strpos($this->buffer, static::CRLF);
+
+ if ($positionCrlf === 0) {
+ if ($this->chunkSize === 0) {
+ $this->emit('end');
+ $this->close();
+ return;
+ }
+ $this->chunkSize = 0;
+ $this->headerCompleted = false;
+ $this->transferredSize = 0;
+ $this->buffer = (string)\substr($this->buffer, 2);
+ } elseif ($this->chunkSize === 0) {
+ // end chunk received, skip all trailer data
+ $this->buffer = (string)\substr($this->buffer, $positionCrlf);
+ }
+
+ if ($positionCrlf !== 0 && $this->chunkSize !== 0 && $this->chunkSize === $this->transferredSize && \strlen($this->buffer) > 2) {
+ // the first 2 characters are not CRLF, send error event
+ $this->handleError(new Exception('Chunk does not end with a CRLF'));
+ return;
+ }
+
+ if ($positionCrlf !== 0 && \strlen($this->buffer) < 2) {
+ // No CRLF found, wait for additional data which could be a CRLF
+ return;
+ }
+ }
+ }
+}
diff --git a/vendor/react/http/src/Io/ChunkedEncoder.php b/vendor/react/http/src/Io/ChunkedEncoder.php
new file mode 100644
index 0000000..c84ef54
--- /dev/null
+++ b/vendor/react/http/src/Io/ChunkedEncoder.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace React\Http\Io;
+
+use Evenement\EventEmitter;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * [Internal] Encodes given payload stream with "Transfer-Encoding: chunked" and emits encoded data
+ *
+ * This is used internally to encode outgoing requests with this encoding.
+ *
+ * @internal
+ */
+class ChunkedEncoder extends EventEmitter implements ReadableStreamInterface
+{
+ private $input;
+ private $closed = false;
+
+ public function __construct(ReadableStreamInterface $input)
+ {
+ $this->input = $input;
+
+ $this->input->on('data', array($this, 'handleData'));
+ $this->input->on('end', array($this, 'handleEnd'));
+ $this->input->on('error', array($this, 'handleError'));
+ $this->input->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed && $this->input->isReadable();
+ }
+
+ public function pause()
+ {
+ $this->input->pause();
+ }
+
+ public function resume()
+ {
+ $this->input->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+ $this->input->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleData($data)
+ {
+ if ($data !== '') {
+ $this->emit('data', array(
+ \dechex(\strlen($data)) . "\r\n" . $data . "\r\n"
+ ));
+ }
+ }
+
+ /** @internal */
+ public function handleError(\Exception $e)
+ {
+ $this->emit('error', array($e));
+ $this->close();
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ $this->emit('data', array("0\r\n\r\n"));
+
+ if (!$this->closed) {
+ $this->emit('end');
+ $this->close();
+ }
+ }
+}
diff --git a/vendor/react/http/src/Io/CloseProtectionStream.php b/vendor/react/http/src/Io/CloseProtectionStream.php
new file mode 100644
index 0000000..2e1ed6e
--- /dev/null
+++ b/vendor/react/http/src/Io/CloseProtectionStream.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace React\Http\Io;
+
+use Evenement\EventEmitter;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * [Internal] Protects a given stream from actually closing and only discards its incoming data instead.
+ *
+ * This is used internally to prevent the underlying connection from closing, so
+ * that we can still send back a response over the same stream.
+ *
+ * @internal
+ * */
+class CloseProtectionStream extends EventEmitter implements ReadableStreamInterface
+{
+ private $input;
+ private $closed = false;
+ private $paused = false;
+
+ /**
+ * @param ReadableStreamInterface $input stream that will be discarded instead of closing it on an 'close' event.
+ */
+ public function __construct(ReadableStreamInterface $input)
+ {
+ $this->input = $input;
+
+ $this->input->on('data', array($this, 'handleData'));
+ $this->input->on('end', array($this, 'handleEnd'));
+ $this->input->on('error', array($this, 'handleError'));
+ $this->input->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed && $this->input->isReadable();
+ }
+
+ public function pause()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->paused = true;
+ $this->input->pause();
+ }
+
+ public function resume()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->paused = false;
+ $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;
+
+ // stop listening for incoming events
+ $this->input->removeListener('data', array($this, 'handleData'));
+ $this->input->removeListener('error', array($this, 'handleError'));
+ $this->input->removeListener('end', array($this, 'handleEnd'));
+ $this->input->removeListener('close', array($this, 'close'));
+
+ // resume the stream to ensure we discard everything from incoming connection
+ if ($this->paused) {
+ $this->paused = false;
+ $this->input->resume();
+ }
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleData($data)
+ {
+ $this->emit('data', array($data));
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ $this->emit('end');
+ $this->close();
+ }
+
+ /** @internal */
+ public function handleError(\Exception $e)
+ {
+ $this->emit('error', array($e));
+ }
+}
diff --git a/vendor/react/http/src/Io/EmptyBodyStream.php b/vendor/react/http/src/Io/EmptyBodyStream.php
new file mode 100644
index 0000000..5056219
--- /dev/null
+++ b/vendor/react/http/src/Io/EmptyBodyStream.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace React\Http\Io;
+
+use Evenement\EventEmitter;
+use Psr\Http\Message\StreamInterface;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * [Internal] Bridge between an empty StreamInterface from PSR-7 and ReadableStreamInterface from ReactPHP
+ *
+ * This class is used in the server to represent an empty body stream of an
+ * incoming response from the client. This is similar to the `HttpBodyStream`,
+ * but is specifically designed for the common case of having an empty message
+ * body.
+ *
+ * Note that this is an internal class only and nothing you should usually care
+ * about. See the `StreamInterface` and `ReadableStreamInterface` for more
+ * details.
+ *
+ * @see HttpBodyStream
+ * @see StreamInterface
+ * @see ReadableStreamInterface
+ * @internal
+ */
+class EmptyBodyStream extends EventEmitter implements StreamInterface, ReadableStreamInterface
+{
+ private $closed = false;
+
+ public function isReadable()
+ {
+ return !$this->closed;
+ }
+
+ public function pause()
+ {
+ // NOOP
+ }
+
+ public function resume()
+ {
+ // NOOP
+ }
+
+ 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->emit('close');
+ $this->removeAllListeners();
+ }
+
+ public function getSize()
+ {
+ return 0;
+ }
+
+ /** @ignore */
+ public function __toString()
+ {
+ return '';
+ }
+
+ /** @ignore */
+ public function detach()
+ {
+ return null;
+ }
+
+ /** @ignore */
+ public function tell()
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function eof()
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ /** @ignore */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function rewind()
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function isWritable()
+ {
+ return false;
+ }
+
+ /** @ignore */
+ public function write($string)
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function read($length)
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function getContents()
+ {
+ return '';
+ }
+
+ /** @ignore */
+ public function getMetadata($key = null)
+ {
+ return ($key === null) ? array() : null;
+ }
+}
diff --git a/vendor/react/http/src/Io/HttpBodyStream.php b/vendor/react/http/src/Io/HttpBodyStream.php
new file mode 100644
index 0000000..25d15a1
--- /dev/null
+++ b/vendor/react/http/src/Io/HttpBodyStream.php
@@ -0,0 +1,182 @@
+<?php
+
+namespace React\Http\Io;
+
+use Evenement\EventEmitter;
+use Psr\Http\Message\StreamInterface;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * [Internal] Bridge between StreamInterface from PSR-7 and ReadableStreamInterface from ReactPHP
+ *
+ * This class is used in the server to stream the body of an incoming response
+ * from the client. This allows us to stream big amounts of data without having
+ * to buffer this data. Similarly, this used to stream the body of an outgoing
+ * request body to the client. The data will be sent directly to the client.
+ *
+ * Note that this is an internal class only and nothing you should usually care
+ * about. See the `StreamInterface` and `ReadableStreamInterface` for more
+ * details.
+ *
+ * @see StreamInterface
+ * @see ReadableStreamInterface
+ * @internal
+ */
+class HttpBodyStream extends EventEmitter implements StreamInterface, ReadableStreamInterface
+{
+ public $input;
+ private $closed = false;
+ private $size;
+
+ /**
+ * @param ReadableStreamInterface $input Stream data from $stream as a body of a PSR-7 object4
+ * @param int|null $size size of the data body
+ */
+ public function __construct(ReadableStreamInterface $input, $size)
+ {
+ $this->input = $input;
+ $this->size = $size;
+
+ $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->input->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ /** @ignore */
+ public function __toString()
+ {
+ return '';
+ }
+
+ /** @ignore */
+ public function detach()
+ {
+ return null;
+ }
+
+ /** @ignore */
+ public function tell()
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function eof()
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ /** @ignore */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function rewind()
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function isWritable()
+ {
+ return false;
+ }
+
+ /** @ignore */
+ public function write($string)
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function read($length)
+ {
+ throw new \BadMethodCallException();
+ }
+
+ /** @ignore */
+ public function getContents()
+ {
+ return '';
+ }
+
+ /** @ignore */
+ public function getMetadata($key = null)
+ {
+ return null;
+ }
+
+ /** @internal */
+ public function handleData($data)
+ {
+ $this->emit('data', array($data));
+ }
+
+ /** @internal */
+ public function handleError(\Exception $e)
+ {
+ $this->emit('error', array($e));
+ $this->close();
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ if (!$this->closed) {
+ $this->emit('end');
+ $this->close();
+ }
+ }
+}
diff --git a/vendor/react/http/src/Io/IniUtil.php b/vendor/react/http/src/Io/IniUtil.php
new file mode 100644
index 0000000..612aae2
--- /dev/null
+++ b/vendor/react/http/src/Io/IniUtil.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace React\Http\Io;
+
+/**
+ * @internal
+ */
+final class IniUtil
+{
+ /**
+ * Convert a ini like size to a numeric size in bytes.
+ *
+ * @param string $size
+ * @return int
+ */
+ public static function iniSizeToBytes($size)
+ {
+ if (\is_numeric($size)) {
+ return (int)$size;
+ }
+
+ $suffix = \strtoupper(\substr($size, -1));
+ $strippedSize = \substr($size, 0, -1);
+
+ if (!\is_numeric($strippedSize)) {
+ throw new \InvalidArgumentException("$size is not a valid ini size");
+ }
+
+ if ($strippedSize <= 0) {
+ throw new \InvalidArgumentException("Expect $size to be higher isn't zero or lower");
+ }
+
+ if ($suffix === 'K') {
+ return $strippedSize * 1024;
+ }
+ if ($suffix === 'M') {
+ return $strippedSize * 1024 * 1024;
+ }
+ if ($suffix === 'G') {
+ return $strippedSize * 1024 * 1024 * 1024;
+ }
+ if ($suffix === 'T') {
+ return $strippedSize * 1024 * 1024 * 1024 * 1024;
+ }
+
+ return (int)$size;
+ }
+}
diff --git a/vendor/react/http/src/Io/LengthLimitedStream.php b/vendor/react/http/src/Io/LengthLimitedStream.php
new file mode 100644
index 0000000..bc64c54
--- /dev/null
+++ b/vendor/react/http/src/Io/LengthLimitedStream.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace React\Http\Io;
+
+use Evenement\EventEmitter;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * [Internal] Limits the amount of data the given stream can emit
+ *
+ * This is used internally to limit the size of the underlying connection stream
+ * to the size defined by the "Content-Length" header of the incoming request.
+ *
+ * @internal
+ */
+class LengthLimitedStream extends EventEmitter implements ReadableStreamInterface
+{
+ private $stream;
+ private $closed = false;
+ private $transferredLength = 0;
+ private $maxLength;
+
+ public function __construct(ReadableStreamInterface $stream, $maxLength)
+ {
+ $this->stream = $stream;
+ $this->maxLength = $maxLength;
+
+ $this->stream->on('data', array($this, 'handleData'));
+ $this->stream->on('end', array($this, 'handleEnd'));
+ $this->stream->on('error', array($this, 'handleError'));
+ $this->stream->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed && $this->stream->isReadable();
+ }
+
+ public function pause()
+ {
+ $this->stream->pause();
+ }
+
+ public function resume()
+ {
+ $this->stream->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->stream->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleData($data)
+ {
+ if (($this->transferredLength + \strlen($data)) > $this->maxLength) {
+ // Only emit data until the value of 'Content-Length' is reached, the rest will be ignored
+ $data = (string)\substr($data, 0, $this->maxLength - $this->transferredLength);
+ }
+
+ if ($data !== '') {
+ $this->transferredLength += \strlen($data);
+ $this->emit('data', array($data));
+ }
+
+ if ($this->transferredLength === $this->maxLength) {
+ // 'Content-Length' reached, stream will end
+ $this->emit('end');
+ $this->close();
+ $this->stream->removeListener('data', array($this, 'handleData'));
+ }
+ }
+
+ /** @internal */
+ public function handleError(\Exception $e)
+ {
+ $this->emit('error', array($e));
+ $this->close();
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ if (!$this->closed) {
+ $this->handleError(new \Exception('Unexpected end event'));
+ }
+ }
+
+}
diff --git a/vendor/react/http/src/Io/MiddlewareRunner.php b/vendor/react/http/src/Io/MiddlewareRunner.php
new file mode 100644
index 0000000..dedf6ff
--- /dev/null
+++ b/vendor/react/http/src/Io/MiddlewareRunner.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace React\Http\Io;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use React\Promise\PromiseInterface;
+
+/**
+ * [Internal] Middleware runner to expose an array of middleware request handlers as a single request handler callable
+ *
+ * @internal
+ */
+final class MiddlewareRunner
+{
+ /**
+ * @var callable[]
+ */
+ private $middleware;
+
+ /**
+ * @param callable[] $middleware
+ */
+ public function __construct(array $middleware)
+ {
+ $this->middleware = \array_values($middleware);
+ }
+
+ /**
+ * @param ServerRequestInterface $request
+ * @return ResponseInterface|PromiseInterface<ResponseInterface>
+ * @throws \Exception
+ */
+ public function __invoke(ServerRequestInterface $request)
+ {
+ if (empty($this->middleware)) {
+ throw new \RuntimeException('No middleware to run');
+ }
+
+ return $this->call($request, 0);
+ }
+
+ /** @internal */
+ public function call(ServerRequestInterface $request, $position)
+ {
+ // final request handler will be invoked without a next handler
+ if (!isset($this->middleware[$position + 1])) {
+ $handler = $this->middleware[$position];
+ return $handler($request);
+ }
+
+ $that = $this;
+ $next = function (ServerRequestInterface $request) use ($that, $position) {
+ return $that->call($request, $position + 1);
+ };
+
+ // invoke middleware request handler with next handler
+ $handler = $this->middleware[$position];
+ return $handler($request, $next);
+ }
+}
diff --git a/vendor/react/http/src/Io/MultipartParser.php b/vendor/react/http/src/Io/MultipartParser.php
new file mode 100644
index 0000000..536694f
--- /dev/null
+++ b/vendor/react/http/src/Io/MultipartParser.php
@@ -0,0 +1,328 @@
+<?php
+
+namespace React\Http\Io;
+
+use Psr\Http\Message\ServerRequestInterface;
+
+/**
+ * [Internal] Parses a string body with "Content-Type: multipart/form-data" into structured data
+ *
+ * This is use internally to parse incoming request bodies into structured data
+ * that resembles PHP's `$_POST` and `$_FILES` superglobals.
+ *
+ * @internal
+ * @link https://tools.ietf.org/html/rfc7578
+ * @link https://tools.ietf.org/html/rfc2046#section-5.1.1
+ */
+final class MultipartParser
+{
+ /**
+ * @var ServerRequestInterface|null
+ */
+ private $request;
+
+ /**
+ * @var int|null
+ */
+ private $maxFileSize;
+
+ /**
+ * ini setting "max_input_vars"
+ *
+ * Does not exist in PHP < 5.3.9 or HHVM, so assume PHP's default 1000 here.
+ *
+ * @var int
+ * @link http://php.net/manual/en/info.configuration.php#ini.max-input-vars
+ */
+ private $maxInputVars = 1000;
+
+ /**
+ * ini setting "max_input_nesting_level"
+ *
+ * Does not exist in HHVM, but assumes hard coded to 64 (PHP's default).
+ *
+ * @var int
+ * @link http://php.net/manual/en/info.configuration.php#ini.max-input-nesting-level
+ */
+ private $maxInputNestingLevel = 64;
+
+ /**
+ * ini setting "upload_max_filesize"
+ *
+ * @var int
+ */
+ private $uploadMaxFilesize;
+
+ /**
+ * ini setting "max_file_uploads"
+ *
+ * Additionally, setting "file_uploads = off" effectively sets this to zero.
+ *
+ * @var int
+ */
+ private $maxFileUploads;
+
+ private $postCount = 0;
+ private $filesCount = 0;
+ private $emptyCount = 0;
+
+ /**
+ * @param int|string|null $uploadMaxFilesize
+ * @param int|null $maxFileUploads
+ */
+ public function __construct($uploadMaxFilesize = null, $maxFileUploads = null)
+ {
+ $var = \ini_get('max_input_vars');
+ if ($var !== false) {
+ $this->maxInputVars = (int)$var;
+ }
+ $var = \ini_get('max_input_nesting_level');
+ if ($var !== false) {
+ $this->maxInputNestingLevel = (int)$var;
+ }
+
+ if ($uploadMaxFilesize === null) {
+ $uploadMaxFilesize = \ini_get('upload_max_filesize');
+ }
+
+ $this->uploadMaxFilesize = IniUtil::iniSizeToBytes($uploadMaxFilesize);
+ $this->maxFileUploads = $maxFileUploads === null ? (\ini_get('file_uploads') === '' ? 0 : (int)\ini_get('max_file_uploads')) : (int)$maxFileUploads;
+ }
+
+ public function parse(ServerRequestInterface $request)
+ {
+ $contentType = $request->getHeaderLine('content-type');
+ if(!\preg_match('/boundary="?(.*?)"?$/', $contentType, $matches)) {
+ return $request;
+ }
+
+ $this->request = $request;
+ $this->parseBody('--' . $matches[1], (string)$request->getBody());
+
+ $request = $this->request;
+ $this->request = null;
+ $this->postCount = 0;
+ $this->filesCount = 0;
+ $this->emptyCount = 0;
+ $this->maxFileSize = null;
+
+ return $request;
+ }
+
+ private function parseBody($boundary, $buffer)
+ {
+ $len = \strlen($boundary);
+
+ // ignore everything before initial boundary (SHOULD be empty)
+ $start = \strpos($buffer, $boundary . "\r\n");
+
+ while ($start !== false) {
+ // search following boundary (preceded by newline)
+ // ignore last if not followed by boundary (SHOULD end with "--")
+ $start += $len + 2;
+ $end = \strpos($buffer, "\r\n" . $boundary, $start);
+ if ($end === false) {
+ break;
+ }
+
+ // parse one part and continue searching for next
+ $this->parsePart(\substr($buffer, $start, $end - $start));
+ $start = $end;
+ }
+ }
+
+ private function parsePart($chunk)
+ {
+ $pos = \strpos($chunk, "\r\n\r\n");
+ if ($pos === false) {
+ return;
+ }
+
+ $headers = $this->parseHeaders((string)substr($chunk, 0, $pos));
+ $body = (string)\substr($chunk, $pos + 4);
+
+ if (!isset($headers['content-disposition'])) {
+ return;
+ }
+
+ $name = $this->getParameterFromHeader($headers['content-disposition'], 'name');
+ if ($name === null) {
+ return;
+ }
+
+ $filename = $this->getParameterFromHeader($headers['content-disposition'], 'filename');
+ if ($filename !== null) {
+ $this->parseFile(
+ $name,
+ $filename,
+ isset($headers['content-type'][0]) ? $headers['content-type'][0] : null,
+ $body
+ );
+ } else {
+ $this->parsePost($name, $body);
+ }
+ }
+
+ private function parseFile($name, $filename, $contentType, $contents)
+ {
+ $file = $this->parseUploadedFile($filename, $contentType, $contents);
+ if ($file === null) {
+ return;
+ }
+
+ $this->request = $this->request->withUploadedFiles($this->extractPost(
+ $this->request->getUploadedFiles(),
+ $name,
+ $file
+ ));
+ }
+
+ private function parseUploadedFile($filename, $contentType, $contents)
+ {
+ $size = \strlen($contents);
+
+ // no file selected (zero size and empty filename)
+ if ($size === 0 && $filename === '') {
+ // ignore excessive number of empty file uploads
+ if (++$this->emptyCount + $this->filesCount > $this->maxInputVars) {
+ return;
+ }
+
+ return new UploadedFile(
+ new BufferedBody(''),
+ $size,
+ \UPLOAD_ERR_NO_FILE,
+ $filename,
+ $contentType
+ );
+ }
+
+ // ignore excessive number of file uploads
+ if (++$this->filesCount > $this->maxFileUploads) {
+ return;
+ }
+
+ // file exceeds "upload_max_filesize" ini setting
+ if ($size > $this->uploadMaxFilesize) {
+ return new UploadedFile(
+ new BufferedBody(''),
+ $size,
+ \UPLOAD_ERR_INI_SIZE,
+ $filename,
+ $contentType
+ );
+ }
+
+ // file exceeds MAX_FILE_SIZE value
+ if ($this->maxFileSize !== null && $size > $this->maxFileSize) {
+ return new UploadedFile(
+ new BufferedBody(''),
+ $size,
+ \UPLOAD_ERR_FORM_SIZE,
+ $filename,
+ $contentType
+ );
+ }
+
+ return new UploadedFile(
+ new BufferedBody($contents),
+ $size,
+ \UPLOAD_ERR_OK,
+ $filename,
+ $contentType
+ );
+ }
+
+ private function parsePost($name, $value)
+ {
+ // ignore excessive number of post fields
+ if (++$this->postCount > $this->maxInputVars) {
+ return;
+ }
+
+ $this->request = $this->request->withParsedBody($this->extractPost(
+ $this->request->getParsedBody(),
+ $name,
+ $value
+ ));
+
+ if (\strtoupper($name) === 'MAX_FILE_SIZE') {
+ $this->maxFileSize = (int)$value;
+
+ if ($this->maxFileSize === 0) {
+ $this->maxFileSize = null;
+ }
+ }
+ }
+
+ private function parseHeaders($header)
+ {
+ $headers = array();
+
+ foreach (\explode("\r\n", \trim($header)) as $line) {
+ $parts = \explode(':', $line, 2);
+ if (!isset($parts[1])) {
+ continue;
+ }
+
+ $key = \strtolower(trim($parts[0]));
+ $values = \explode(';', $parts[1]);
+ $values = \array_map('trim', $values);
+ $headers[$key] = $values;
+ }
+
+ return $headers;
+ }
+
+ private function getParameterFromHeader(array $header, $parameter)
+ {
+ foreach ($header as $part) {
+ if (\preg_match('/' . $parameter . '="?(.*?)"?$/', $part, $matches)) {
+ return $matches[1];
+ }
+ }
+
+ return null;
+ }
+
+ private function extractPost($postFields, $key, $value)
+ {
+ $chunks = \explode('[', $key);
+ if (\count($chunks) == 1) {
+ $postFields[$key] = $value;
+ return $postFields;
+ }
+
+ // ignore this key if maximum nesting level is exceeded
+ if (isset($chunks[$this->maxInputNestingLevel])) {
+ return $postFields;
+ }
+
+ $chunkKey = \rtrim($chunks[0], ']');
+ $parent = &$postFields;
+ for ($i = 1; isset($chunks[$i]); $i++) {
+ $previousChunkKey = $chunkKey;
+
+ if ($previousChunkKey === '') {
+ $parent[] = array();
+ \end($parent);
+ $parent = &$parent[\key($parent)];
+ } else {
+ if (!isset($parent[$previousChunkKey]) || !\is_array($parent[$previousChunkKey])) {
+ $parent[$previousChunkKey] = array();
+ }
+ $parent = &$parent[$previousChunkKey];
+ }
+
+ $chunkKey = \rtrim($chunks[$i], ']');
+ }
+
+ if ($chunkKey === '') {
+ $parent[] = $value;
+ } else {
+ $parent[$chunkKey] = $value;
+ }
+
+ return $postFields;
+ }
+}
diff --git a/vendor/react/http/src/Io/PauseBufferStream.php b/vendor/react/http/src/Io/PauseBufferStream.php
new file mode 100644
index 0000000..fb5ed45
--- /dev/null
+++ b/vendor/react/http/src/Io/PauseBufferStream.php
@@ -0,0 +1,188 @@
+<?php
+
+namespace React\Http\Io;
+
+use Evenement\EventEmitter;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * [Internal] Pauses a given stream and buffers all events while paused
+ *
+ * This class is used to buffer all events that happen on a given stream while
+ * it is paused. This allows you to pause a stream and no longer watch for any
+ * of its events. Once the stream is resumed, all buffered events will be
+ * emitted. Explicitly closing the resulting stream clears all buffers.
+ *
+ * Note that this is an internal class only and nothing you should usually care
+ * about.
+ *
+ * @see ReadableStreamInterface
+ * @internal
+ */
+class PauseBufferStream extends EventEmitter implements ReadableStreamInterface
+{
+ private $input;
+ private $closed = false;
+ private $paused = false;
+ private $dataPaused = '';
+ private $endPaused = false;
+ private $closePaused = false;
+ private $errorPaused;
+ private $implicit = false;
+
+ public function __construct(ReadableStreamInterface $input)
+ {
+ $this->input = $input;
+
+ $this->input->on('data', array($this, 'handleData'));
+ $this->input->on('end', array($this, 'handleEnd'));
+ $this->input->on('error', array($this, 'handleError'));
+ $this->input->on('close', array($this, 'handleClose'));
+ }
+
+ /**
+ * pause and remember this was not explicitly from user control
+ *
+ * @internal
+ */
+ public function pauseImplicit()
+ {
+ $this->pause();
+ $this->implicit = true;
+ }
+
+ /**
+ * resume only if this was previously paused implicitly and not explicitly from user control
+ *
+ * @internal
+ */
+ public function resumeImplicit()
+ {
+ if ($this->implicit) {
+ $this->resume();
+ }
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed;
+ }
+
+ public function pause()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->input->pause();
+ $this->paused = true;
+ $this->implicit = false;
+ }
+
+ public function resume()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->paused = false;
+ $this->implicit = false;
+
+ if ($this->dataPaused !== '') {
+ $this->emit('data', array($this->dataPaused));
+ $this->dataPaused = '';
+ }
+
+ if ($this->errorPaused) {
+ $this->emit('error', array($this->errorPaused));
+ return $this->close();
+ }
+
+ if ($this->endPaused) {
+ $this->endPaused = false;
+ $this->emit('end');
+ return $this->close();
+ }
+
+ if ($this->closePaused) {
+ $this->closePaused = false;
+ return $this->close();
+ }
+
+ $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->dataPaused = '';
+ $this->endPaused = $this->closePaused = false;
+ $this->errorPaused = null;
+
+ $this->input->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleData($data)
+ {
+ if ($this->paused) {
+ $this->dataPaused .= $data;
+ return;
+ }
+
+ $this->emit('data', array($data));
+ }
+
+ /** @internal */
+ public function handleError(\Exception $e)
+ {
+ if ($this->paused) {
+ $this->errorPaused = $e;
+ return;
+ }
+
+ $this->emit('error', array($e));
+ $this->close();
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ if ($this->paused) {
+ $this->endPaused = true;
+ return;
+ }
+
+ if (!$this->closed) {
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /** @internal */
+ public function handleClose()
+ {
+ if ($this->paused) {
+ $this->closePaused = true;
+ return;
+ }
+
+ $this->close();
+ }
+}
diff --git a/vendor/react/http/src/Io/ReadableBodyStream.php b/vendor/react/http/src/Io/ReadableBodyStream.php
new file mode 100644
index 0000000..daef45f
--- /dev/null
+++ b/vendor/react/http/src/Io/ReadableBodyStream.php
@@ -0,0 +1,153 @@
+<?php
+
+namespace React\Http\Io;
+
+use Evenement\EventEmitter;
+use Psr\Http\Message\StreamInterface;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * @internal
+ */
+class ReadableBodyStream extends EventEmitter implements ReadableStreamInterface, StreamInterface
+{
+ private $input;
+ private $position = 0;
+ private $size;
+ private $closed = false;
+
+ public function __construct(ReadableStreamInterface $input, $size = null)
+ {
+ $this->input = $input;
+ $this->size = $size;
+
+ $that = $this;
+ $pos =& $this->position;
+ $input->on('data', function ($data) use ($that, &$pos, $size) {
+ $that->emit('data', array($data));
+
+ $pos += \strlen($data);
+ if ($size !== null && $pos >= $size) {
+ $that->handleEnd();
+ }
+ });
+ $input->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ $that->close();
+ });
+ $input->on('end', array($that, 'handleEnd'));
+ $input->on('close', array($that, 'close'));
+ }
+
+ public function close()
+ {
+ if (!$this->closed) {
+ $this->closed = true;
+ $this->input->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+ }
+
+ public function isReadable()
+ {
+ return $this->input->isReadable();
+ }
+
+ public function pause()
+ {
+ $this->input->pause();
+ }
+
+ public function resume()
+ {
+ $this->input->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ Util::pipe($this, $dest, $options);
+
+ return $dest;
+ }
+
+ public function eof()
+ {
+ return !$this->isReadable();
+ }
+
+ public function __toString()
+ {
+ return '';
+ }
+
+ public function detach()
+ {
+ throw new \BadMethodCallException();
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function tell()
+ {
+ throw new \BadMethodCallException();
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \BadMethodCallException();
+ }
+
+ public function rewind()
+ {
+ throw new \BadMethodCallException();
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ throw new \BadMethodCallException();
+ }
+
+ public function read($length)
+ {
+ throw new \BadMethodCallException();
+ }
+
+ public function getContents()
+ {
+ throw new \BadMethodCallException();
+ }
+
+ public function getMetadata($key = null)
+ {
+ return ($key === null) ? array() : null;
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ if ($this->position !== $this->size && $this->size !== null) {
+ $this->emit('error', array(new \UnderflowException('Unexpected end of response body after ' . $this->position . '/' . $this->size . ' bytes')));
+ } else {
+ $this->emit('end');
+ }
+
+ $this->close();
+ }
+}
diff --git a/vendor/react/http/src/Io/RequestHeaderParser.php b/vendor/react/http/src/Io/RequestHeaderParser.php
new file mode 100644
index 0000000..e5554c4
--- /dev/null
+++ b/vendor/react/http/src/Io/RequestHeaderParser.php
@@ -0,0 +1,281 @@
+<?php
+
+namespace React\Http\Io;
+
+use Evenement\EventEmitter;
+use Psr\Http\Message\ServerRequestInterface;
+use React\Http\Message\Response;
+use React\Http\Message\ServerRequest;
+use React\Socket\ConnectionInterface;
+use Exception;
+
+/**
+ * [Internal] Parses an incoming request header from an input stream
+ *
+ * This is used internally to parse the request header from the connection and
+ * then process the remaining connection as the request body.
+ *
+ * @event headers
+ * @event error
+ *
+ * @internal
+ */
+class RequestHeaderParser extends EventEmitter
+{
+ private $maxSize = 8192;
+
+ public function handle(ConnectionInterface $conn)
+ {
+ $buffer = '';
+ $maxSize = $this->maxSize;
+ $that = $this;
+ $conn->on('data', $fn = function ($data) use (&$buffer, &$fn, $conn, $maxSize, $that) {
+ // append chunk of data to buffer and look for end of request headers
+ $buffer .= $data;
+ $endOfHeader = \strpos($buffer, "\r\n\r\n");
+
+ // reject request if buffer size is exceeded
+ if ($endOfHeader > $maxSize || ($endOfHeader === false && isset($buffer[$maxSize]))) {
+ $conn->removeListener('data', $fn);
+ $fn = null;
+
+ $that->emit('error', array(
+ new \OverflowException("Maximum header size of {$maxSize} exceeded.", Response::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE),
+ $conn
+ ));
+ return;
+ }
+
+ // ignore incomplete requests
+ if ($endOfHeader === false) {
+ return;
+ }
+
+ // request headers received => try to parse request
+ $conn->removeListener('data', $fn);
+ $fn = null;
+
+ try {
+ $request = $that->parseRequest(
+ (string)\substr($buffer, 0, $endOfHeader + 2),
+ $conn->getRemoteAddress(),
+ $conn->getLocalAddress()
+ );
+ } catch (Exception $exception) {
+ $buffer = '';
+ $that->emit('error', array(
+ $exception,
+ $conn
+ ));
+ return;
+ }
+
+ $contentLength = 0;
+ if ($request->hasHeader('Transfer-Encoding')) {
+ $contentLength = null;
+ } elseif ($request->hasHeader('Content-Length')) {
+ $contentLength = (int)$request->getHeaderLine('Content-Length');
+ }
+
+ if ($contentLength === 0) {
+ // happy path: request body is known to be empty
+ $stream = new EmptyBodyStream();
+ $request = $request->withBody($stream);
+ } else {
+ // otherwise body is present => delimit using Content-Length or ChunkedDecoder
+ $stream = new CloseProtectionStream($conn);
+ if ($contentLength !== null) {
+ $stream = new LengthLimitedStream($stream, $contentLength);
+ } else {
+ $stream = new ChunkedDecoder($stream);
+ }
+
+ $request = $request->withBody(new HttpBodyStream($stream, $contentLength));
+ }
+
+ $bodyBuffer = isset($buffer[$endOfHeader + 4]) ? \substr($buffer, $endOfHeader + 4) : '';
+ $buffer = '';
+ $that->emit('headers', array($request, $conn));
+
+ if ($bodyBuffer !== '') {
+ $conn->emit('data', array($bodyBuffer));
+ }
+
+ // happy path: request body is known to be empty => immediately end stream
+ if ($contentLength === 0) {
+ $stream->emit('end');
+ $stream->close();
+ }
+ });
+ }
+
+ /**
+ * @param string $headers buffer string containing request headers only
+ * @param ?string $remoteSocketUri
+ * @param ?string $localSocketUri
+ * @return ServerRequestInterface
+ * @throws \InvalidArgumentException
+ * @internal
+ */
+ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
+ {
+ // additional, stricter safe-guard for request line
+ // because request parser doesn't properly cope with invalid ones
+ $start = array();
+ if (!\preg_match('#^(?<method>[^ ]+) (?<target>[^ ]+) HTTP/(?<version>\d\.\d)#m', $headers, $start)) {
+ throw new \InvalidArgumentException('Unable to parse invalid request-line');
+ }
+
+ // only support HTTP/1.1 and HTTP/1.0 requests
+ if ($start['version'] !== '1.1' && $start['version'] !== '1.0') {
+ throw new \InvalidArgumentException('Received request with invalid protocol version', Response::STATUS_VERSION_NOT_SUPPORTED);
+ }
+
+ // match all request header fields into array, thanks to @kelunik for checking the HTTP specs and coming up with this regex
+ $matches = array();
+ $n = \preg_match_all('/^([^()<>@,;:\\\"\/\[\]?={}\x01-\x20\x7F]++):[\x20\x09]*+((?:[\x20\x09]*+[\x21-\x7E\x80-\xFF]++)*+)[\x20\x09]*+[\r]?+\n/m', $headers, $matches, \PREG_SET_ORDER);
+
+ // check number of valid header fields matches number of lines + request line
+ if (\substr_count($headers, "\n") !== $n + 1) {
+ throw new \InvalidArgumentException('Unable to parse invalid request header fields');
+ }
+
+ // format all header fields into associative array
+ $host = null;
+ $fields = array();
+ foreach ($matches as $match) {
+ $fields[$match[1]][] = $match[2];
+
+ // match `Host` request header
+ if ($host === null && \strtolower($match[1]) === 'host') {
+ $host = $match[2];
+ }
+ }
+
+ // create new obj implementing ServerRequestInterface by preserving all
+ // previous properties and restoring original request-target
+ $serverParams = array(
+ 'REQUEST_TIME' => \time(),
+ 'REQUEST_TIME_FLOAT' => \microtime(true)
+ );
+
+ // scheme is `http` unless TLS is used
+ $localParts = $localSocketUri === null ? array() : \parse_url($localSocketUri);
+ if (isset($localParts['scheme']) && $localParts['scheme'] === 'tls') {
+ $scheme = 'https://';
+ $serverParams['HTTPS'] = 'on';
+ } else {
+ $scheme = 'http://';
+ }
+
+ // default host if unset comes from local socket address or defaults to localhost
+ $hasHost = $host !== null;
+ if ($host === null) {
+ $host = isset($localParts['host'], $localParts['port']) ? $localParts['host'] . ':' . $localParts['port'] : '127.0.0.1';
+ }
+
+ if ($start['method'] === 'OPTIONS' && $start['target'] === '*') {
+ // support asterisk-form for `OPTIONS *` request line only
+ $uri = $scheme . $host;
+ } elseif ($start['method'] === 'CONNECT') {
+ $parts = \parse_url('tcp://' . $start['target']);
+
+ // check this is a valid authority-form request-target (host:port)
+ if (!isset($parts['scheme'], $parts['host'], $parts['port']) || \count($parts) !== 3) {
+ throw new \InvalidArgumentException('CONNECT method MUST use authority-form request target');
+ }
+ $uri = $scheme . $start['target'];
+ } else {
+ // support absolute-form or origin-form for proxy requests
+ if ($start['target'][0] === '/') {
+ $uri = $scheme . $host . $start['target'];
+ } else {
+ // ensure absolute-form request-target contains a valid URI
+ $parts = \parse_url($start['target']);
+
+ // make sure value contains valid host component (IP or hostname), but no fragment
+ if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'http' || isset($parts['fragment'])) {
+ throw new \InvalidArgumentException('Invalid absolute-form request-target');
+ }
+
+ $uri = $start['target'];
+ }
+ }
+
+ // apply REMOTE_ADDR and REMOTE_PORT if source address is known
+ // address should always be known, unless this is over Unix domain sockets (UDS)
+ if ($remoteSocketUri !== null) {
+ $remoteAddress = \parse_url($remoteSocketUri);
+ $serverParams['REMOTE_ADDR'] = $remoteAddress['host'];
+ $serverParams['REMOTE_PORT'] = $remoteAddress['port'];
+ }
+
+ // apply SERVER_ADDR and SERVER_PORT if server address is known
+ // address should always be known, even for Unix domain sockets (UDS)
+ // but skip UDS as it doesn't have a concept of host/port.
+ if ($localSocketUri !== null && isset($localParts['host'], $localParts['port'])) {
+ $serverParams['SERVER_ADDR'] = $localParts['host'];
+ $serverParams['SERVER_PORT'] = $localParts['port'];
+ }
+
+ $request = new ServerRequest(
+ $start['method'],
+ $uri,
+ $fields,
+ '',
+ $start['version'],
+ $serverParams
+ );
+
+ // only assign request target if it is not in origin-form (happy path for most normal requests)
+ if ($start['target'][0] !== '/') {
+ $request = $request->withRequestTarget($start['target']);
+ }
+
+ if ($hasHost) {
+ // Optional Host request header value MUST be valid (host and optional port)
+ $parts = \parse_url('http://' . $request->getHeaderLine('Host'));
+
+ // make sure value contains valid host component (IP or hostname)
+ if (!$parts || !isset($parts['scheme'], $parts['host'])) {
+ $parts = false;
+ }
+
+ // make sure value does not contain any other URI component
+ if (\is_array($parts)) {
+ unset($parts['scheme'], $parts['host'], $parts['port']);
+ }
+ if ($parts === false || $parts) {
+ throw new \InvalidArgumentException('Invalid Host header value');
+ }
+ } elseif (!$hasHost && $start['version'] === '1.1' && $start['method'] !== 'CONNECT') {
+ // require Host request header for HTTP/1.1 (except for CONNECT method)
+ throw new \InvalidArgumentException('Missing required Host request header');
+ } elseif (!$hasHost) {
+ // remove default Host request header for HTTP/1.0 when not explicitly given
+ $request = $request->withoutHeader('Host');
+ }
+
+ // ensure message boundaries are valid according to Content-Length and Transfer-Encoding request headers
+ if ($request->hasHeader('Transfer-Encoding')) {
+ if (\strtolower($request->getHeaderLine('Transfer-Encoding')) !== 'chunked') {
+ throw new \InvalidArgumentException('Only chunked-encoding is allowed for Transfer-Encoding', Response::STATUS_NOT_IMPLEMENTED);
+ }
+
+ // Transfer-Encoding: chunked and Content-Length header MUST NOT be used at the same time
+ // as per https://tools.ietf.org/html/rfc7230#section-3.3.3
+ if ($request->hasHeader('Content-Length')) {
+ throw new \InvalidArgumentException('Using both `Transfer-Encoding: chunked` and `Content-Length` is not allowed', Response::STATUS_BAD_REQUEST);
+ }
+ } elseif ($request->hasHeader('Content-Length')) {
+ $string = $request->getHeaderLine('Content-Length');
+
+ if ((string)(int)$string !== $string) {
+ // Content-Length value is not an integer or not a single integer
+ throw new \InvalidArgumentException('The value of `Content-Length` is not valid', Response::STATUS_BAD_REQUEST);
+ }
+ }
+
+ return $request;
+ }
+}
diff --git a/vendor/react/http/src/Io/Sender.php b/vendor/react/http/src/Io/Sender.php
new file mode 100644
index 0000000..2f04c79
--- /dev/null
+++ b/vendor/react/http/src/Io/Sender.php
@@ -0,0 +1,160 @@
+<?php
+
+namespace React\Http\Io;
+
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use React\EventLoop\LoopInterface;
+use React\Http\Client\Client as HttpClient;
+use React\Http\Message\Response;
+use React\Promise\PromiseInterface;
+use React\Promise\Deferred;
+use React\Socket\ConnectorInterface;
+use React\Stream\ReadableStreamInterface;
+
+/**
+ * [Internal] Sends requests and receives responses
+ *
+ * The `Sender` is responsible for passing the [`RequestInterface`](#requestinterface) objects to
+ * the underlying [`HttpClient`](https://github.com/reactphp/http-client) library
+ * and keeps track of its transmission and converts its reponses back to [`ResponseInterface`](#responseinterface) objects.
+ *
+ * It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
+ * and the default [`Connector`](https://github.com/reactphp/socket-client) and [DNS `Resolver`](https://github.com/reactphp/dns).
+ *
+ * The `Sender` class mostly exists in order to abstract changes on the underlying
+ * components away from this package in order to provide backwards and forwards
+ * compatibility.
+ *
+ * @internal You SHOULD NOT rely on this API, it is subject to change without prior notice!
+ * @see Browser
+ */
+class Sender
+{
+ /**
+ * create a new default sender attached to the given event loop
+ *
+ * This method is used internally to create the "default sender".
+ *
+ * You may also use this method if you need custom DNS or connector
+ * settings. You can use this method manually like this:
+ *
+ * ```php
+ * $connector = new \React\Socket\Connector(array(), $loop);
+ * $sender = \React\Http\Io\Sender::createFromLoop($loop, $connector);
+ * ```
+ *
+ * @param LoopInterface $loop
+ * @param ConnectorInterface|null $connector
+ * @return self
+ */
+ public static function createFromLoop(LoopInterface $loop, ConnectorInterface $connector = null)
+ {
+ return new self(new HttpClient($loop, $connector));
+ }
+
+ private $http;
+
+ /**
+ * [internal] Instantiate Sender
+ *
+ * @param HttpClient $http
+ * @internal
+ */
+ public function __construct(HttpClient $http)
+ {
+ $this->http = $http;
+ }
+
+ /**
+ *
+ * @internal
+ * @param RequestInterface $request
+ * @return PromiseInterface Promise<ResponseInterface, Exception>
+ */
+ public function send(RequestInterface $request)
+ {
+ $body = $request->getBody();
+ $size = $body->getSize();
+
+ if ($size !== null && $size !== 0) {
+ // automatically assign a "Content-Length" request header if the body size is known and non-empty
+ $request = $request->withHeader('Content-Length', (string)$size);
+ } elseif ($size === 0 && \in_array($request->getMethod(), array('POST', 'PUT', 'PATCH'))) {
+ // only assign a "Content-Length: 0" request header if the body is expected for certain methods
+ $request = $request->withHeader('Content-Length', '0');
+ } elseif ($body instanceof ReadableStreamInterface && $body->isReadable() && !$request->hasHeader('Content-Length')) {
+ // use "Transfer-Encoding: chunked" when this is a streaming body and body size is unknown
+ $request = $request->withHeader('Transfer-Encoding', 'chunked');
+ } else {
+ // do not use chunked encoding if size is known or if this is an empty request body
+ $size = 0;
+ }
+
+ $headers = array();
+ foreach ($request->getHeaders() as $name => $values) {
+ $headers[$name] = implode(', ', $values);
+ }
+
+ $requestStream = $this->http->request($request->getMethod(), (string)$request->getUri(), $headers, $request->getProtocolVersion());
+
+ $deferred = new Deferred(function ($_, $reject) use ($requestStream) {
+ // close request stream if request is cancelled
+ $reject(new \RuntimeException('Request cancelled'));
+ $requestStream->close();
+ });
+
+ $requestStream->on('error', function($error) use ($deferred) {
+ $deferred->reject($error);
+ });
+
+ $requestStream->on('response', function (ResponseInterface $response, ReadableStreamInterface $body) use ($deferred, $request) {
+ $length = null;
+ $code = $response->getStatusCode();
+ if ($request->getMethod() === 'HEAD' || ($code >= 100 && $code < 200) || $code == Response::STATUS_NO_CONTENT || $code == Response::STATUS_NOT_MODIFIED) {
+ $length = 0;
+ } elseif (\strtolower($response->getHeaderLine('Transfer-Encoding')) === 'chunked') {
+ $body = new ChunkedDecoder($body);
+ } elseif ($response->hasHeader('Content-Length')) {
+ $length = (int) $response->getHeaderLine('Content-Length');
+ }
+
+ $deferred->resolve($response->withBody(new ReadableBodyStream($body, $length)));
+ });
+
+ if ($body instanceof ReadableStreamInterface) {
+ if ($body->isReadable()) {
+ // length unknown => apply chunked transfer-encoding
+ if ($size === null) {
+ $body = new ChunkedEncoder($body);
+ }
+
+ // pipe body into request stream
+ // add dummy write to immediately start request even if body does not emit any data yet
+ $body->pipe($requestStream);
+ $requestStream->write('');
+
+ $body->on('close', $close = function () use ($deferred, $requestStream) {
+ $deferred->reject(new \RuntimeException('Request failed because request body closed unexpectedly'));
+ $requestStream->close();
+ });
+ $body->on('error', function ($e) use ($deferred, $requestStream, $close, $body) {
+ $body->removeListener('close', $close);
+ $deferred->reject(new \RuntimeException('Request failed because request body reported an error', 0, $e));
+ $requestStream->close();
+ });
+ $body->on('end', function () use ($close, $body) {
+ $body->removeListener('close', $close);
+ });
+ } else {
+ // stream is not readable => end request without body
+ $requestStream->end();
+ }
+ } else {
+ // body is fully buffered => write as one chunk
+ $requestStream->end((string)$body);
+ }
+
+ return $deferred->promise();
+ }
+}
diff --git a/vendor/react/http/src/Io/StreamingServer.php b/vendor/react/http/src/Io/StreamingServer.php
new file mode 100644
index 0000000..7818f0b
--- /dev/null
+++ b/vendor/react/http/src/Io/StreamingServer.php
@@ -0,0 +1,383 @@
+<?php
+
+namespace React\Http\Io;
+
+use Evenement\EventEmitter;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use React\EventLoop\LoopInterface;
+use React\Http\Message\Response;
+use React\Http\Message\ServerRequest;
+use React\Promise;
+use React\Promise\CancellablePromiseInterface;
+use React\Promise\PromiseInterface;
+use React\Socket\ConnectionInterface;
+use React\Socket\ServerInterface;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * The internal `StreamingServer` class is responsible for handling incoming connections and then
+ * processing each incoming HTTP request.
+ *
+ * Unlike the [`HttpServer`](#httpserver) class, it does not buffer and parse the incoming
+ * HTTP request body by default. This means that the request handler will be
+ * invoked with a streaming request body. Once the request headers have been
+ * received, it will invoke the request handler function. This request handler
+ * function needs to be passed to the constructor and will be invoked with the
+ * respective [request](#request) object and expects a [response](#response)
+ * object in return:
+ *
+ * ```php
+ * $server = new StreamingServer($loop, function (ServerRequestInterface $request) {
+ * return new Response(
+ * Response::STATUS_OK,
+ * array(
+ * 'Content-Type' => 'text/plain'
+ * ),
+ * "Hello World!\n"
+ * );
+ * });
+ * ```
+ *
+ * Each incoming HTTP request message is always represented by the
+ * [PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface),
+ * see also following [request](#request) chapter for more details.
+ * Each outgoing HTTP response message is always represented by the
+ * [PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface),
+ * see also following [response](#response) chapter for more details.
+ *
+ * In order to process any connections, the server needs to be attached to an
+ * instance of `React\Socket\ServerInterface` through the [`listen()`](#listen) method
+ * as described in the following chapter. In its most simple form, you can attach
+ * this to a [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
+ * in order to start a plaintext HTTP server like this:
+ *
+ * ```php
+ * $server = new StreamingServer($loop, $handler);
+ *
+ * $socket = new React\Socket\SocketServer('0.0.0.0:8080', array(), $loop);
+ * $server->listen($socket);
+ * ```
+ *
+ * See also the [`listen()`](#listen) method and the [first example](examples) for more details.
+ *
+ * The `StreamingServer` class is considered advanced usage and unless you know
+ * what you're doing, you're recommended to use the [`HttpServer`](#httpserver) class
+ * instead. The `StreamingServer` class is specifically designed to help with
+ * more advanced use cases where you want to have full control over consuming
+ * the incoming HTTP request body and concurrency settings.
+ *
+ * In particular, this class does not buffer and parse the incoming HTTP request
+ * in memory. It will invoke the request handler function once the HTTP request
+ * headers have been received, i.e. before receiving the potentially much larger
+ * HTTP request body. This means the [request](#request) passed to your request
+ * handler function may not be fully compatible with PSR-7. See also
+ * [streaming request](#streaming-request) below for more details.
+ *
+ * @see \React\Http\HttpServer
+ * @see \React\Http\Message\Response
+ * @see self::listen()
+ * @internal
+ */
+final class StreamingServer extends EventEmitter
+{
+ private $callback;
+ private $parser;
+ private $loop;
+
+ /**
+ * Creates an HTTP server that invokes the given callback for each incoming HTTP request
+ *
+ * In order to process any connections, the server needs to be attached to an
+ * instance of `React\Socket\ServerInterface` which emits underlying streaming
+ * connections in order to then parse incoming data as HTTP.
+ * See also [listen()](#listen) for more details.
+ *
+ * @param LoopInterface $loop
+ * @param callable $requestHandler
+ * @see self::listen()
+ */
+ public function __construct(LoopInterface $loop, $requestHandler)
+ {
+ if (!\is_callable($requestHandler)) {
+ throw new \InvalidArgumentException('Invalid request handler given');
+ }
+
+ $this->loop = $loop;
+
+ $this->callback = $requestHandler;
+ $this->parser = new RequestHeaderParser();
+
+ $that = $this;
+ $this->parser->on('headers', function (ServerRequestInterface $request, ConnectionInterface $conn) use ($that) {
+ $that->handleRequest($conn, $request);
+ });
+
+ $this->parser->on('error', function(\Exception $e, ConnectionInterface $conn) use ($that) {
+ $that->emit('error', array($e));
+
+ // parsing failed => assume dummy request and send appropriate error
+ $that->writeError(
+ $conn,
+ $e->getCode() !== 0 ? $e->getCode() : Response::STATUS_BAD_REQUEST,
+ new ServerRequest('GET', '/')
+ );
+ });
+ }
+
+ /**
+ * Starts listening for HTTP requests on the given socket server instance
+ *
+ * @param ServerInterface $socket
+ * @see \React\Http\HttpServer::listen()
+ */
+ public function listen(ServerInterface $socket)
+ {
+ $socket->on('connection', array($this->parser, 'handle'));
+ }
+
+ /** @internal */
+ public function handleRequest(ConnectionInterface $conn, ServerRequestInterface $request)
+ {
+ if ($request->getProtocolVersion() !== '1.0' && '100-continue' === \strtolower($request->getHeaderLine('Expect'))) {
+ $conn->write("HTTP/1.1 100 Continue\r\n\r\n");
+ }
+
+ // execute request handler callback
+ $callback = $this->callback;
+ try {
+ $response = $callback($request);
+ } catch (\Exception $error) {
+ // request handler callback throws an Exception
+ $response = Promise\reject($error);
+ } catch (\Throwable $error) { // @codeCoverageIgnoreStart
+ // request handler callback throws a PHP7+ Error
+ $response = Promise\reject($error); // @codeCoverageIgnoreEnd
+ }
+
+ // cancel pending promise once connection closes
+ if ($response instanceof CancellablePromiseInterface) {
+ $conn->on('close', function () use ($response) {
+ $response->cancel();
+ });
+ }
+
+ // happy path: response returned, handle and return immediately
+ if ($response instanceof ResponseInterface) {
+ return $this->handleResponse($conn, $request, $response);
+ }
+
+ // did not return a promise? this is an error, convert into one for rejection below.
+ if (!$response instanceof PromiseInterface) {
+ $response = Promise\resolve($response);
+ }
+
+ $that = $this;
+ $response->then(
+ function ($response) use ($that, $conn, $request) {
+ if (!$response instanceof ResponseInterface) {
+ $message = 'The response callback is expected to resolve with an object implementing Psr\Http\Message\ResponseInterface, but resolved with "%s" instead.';
+ $message = \sprintf($message, \is_object($response) ? \get_class($response) : \gettype($response));
+ $exception = new \RuntimeException($message);
+
+ $that->emit('error', array($exception));
+ return $that->writeError($conn, Response::STATUS_INTERNAL_SERVER_ERROR, $request);
+ }
+ $that->handleResponse($conn, $request, $response);
+ },
+ function ($error) use ($that, $conn, $request) {
+ $message = 'The response callback is expected to resolve with an object implementing Psr\Http\Message\ResponseInterface, but rejected with "%s" instead.';
+ $message = \sprintf($message, \is_object($error) ? \get_class($error) : \gettype($error));
+
+ $previous = null;
+
+ if ($error instanceof \Throwable || $error instanceof \Exception) {
+ $previous = $error;
+ }
+
+ $exception = new \RuntimeException($message, 0, $previous);
+
+ $that->emit('error', array($exception));
+ return $that->writeError($conn, Response::STATUS_INTERNAL_SERVER_ERROR, $request);
+ }
+ );
+ }
+
+ /** @internal */
+ public function writeError(ConnectionInterface $conn, $code, ServerRequestInterface $request)
+ {
+ $response = new Response(
+ $code,
+ array(
+ 'Content-Type' => 'text/plain',
+ 'Connection' => 'close' // we do not want to keep the connection open after an error
+ ),
+ 'Error ' . $code
+ );
+
+ // append reason phrase to response body if known
+ $reason = $response->getReasonPhrase();
+ if ($reason !== '') {
+ $body = $response->getBody();
+ $body->seek(0, SEEK_END);
+ $body->write(': ' . $reason);
+ }
+
+ $this->handleResponse($conn, $request, $response);
+ }
+
+
+ /** @internal */
+ public function handleResponse(ConnectionInterface $connection, ServerRequestInterface $request, ResponseInterface $response)
+ {
+ // return early and close response body if connection is already closed
+ $body = $response->getBody();
+ if (!$connection->isWritable()) {
+ $body->close();
+ return;
+ }
+
+ $code = $response->getStatusCode();
+ $method = $request->getMethod();
+
+ // assign HTTP protocol version from request automatically
+ $version = $request->getProtocolVersion();
+ $response = $response->withProtocolVersion($version);
+
+ // assign default "Server" header automatically
+ if (!$response->hasHeader('Server')) {
+ $response = $response->withHeader('Server', 'ReactPHP/1');
+ } elseif ($response->getHeaderLine('Server') === ''){
+ $response = $response->withoutHeader('Server');
+ }
+
+ // assign default "Date" header from current time automatically
+ if (!$response->hasHeader('Date')) {
+ // IMF-fixdate = day-name "," SP date1 SP time-of-day SP GMT
+ $response = $response->withHeader('Date', gmdate('D, d M Y H:i:s') . ' GMT');
+ } elseif ($response->getHeaderLine('Date') === ''){
+ $response = $response->withoutHeader('Date');
+ }
+
+ // assign "Content-Length" header automatically
+ $chunked = false;
+ if (($method === 'CONNECT' && $code >= 200 && $code < 300) || ($code >= 100 && $code < 200) || $code === Response::STATUS_NO_CONTENT) {
+ // 2xx response to CONNECT and 1xx and 204 MUST NOT include Content-Length or Transfer-Encoding header
+ $response = $response->withoutHeader('Content-Length');
+ } elseif ($code === Response::STATUS_NOT_MODIFIED && ($response->hasHeader('Content-Length') || $body->getSize() === 0)) {
+ // 304 Not Modified: preserve explicit Content-Length and preserve missing header if body is empty
+ } elseif ($body->getSize() !== null) {
+ // assign Content-Length header when using a "normal" buffered body string
+ $response = $response->withHeader('Content-Length', (string)$body->getSize());
+ } elseif (!$response->hasHeader('Content-Length') && $version === '1.1') {
+ // assign chunked transfer-encoding if no 'content-length' is given for HTTP/1.1 responses
+ $chunked = true;
+ }
+
+ // assign "Transfer-Encoding" header automatically
+ if ($chunked) {
+ $response = $response->withHeader('Transfer-Encoding', 'chunked');
+ } else {
+ // remove any Transfer-Encoding headers unless automatically enabled above
+ $response = $response->withoutHeader('Transfer-Encoding');
+ }
+
+ // assign "Connection" header automatically
+ $persist = false;
+ if ($code === Response::STATUS_SWITCHING_PROTOCOLS) {
+ // 101 (Switching Protocols) response uses Connection: upgrade header
+ // This implies that this stream now uses another protocol and we
+ // may not persist this connection for additional requests.
+ $response = $response->withHeader('Connection', 'upgrade');
+ } elseif (\strtolower($request->getHeaderLine('Connection')) === 'close' || \strtolower($response->getHeaderLine('Connection')) === 'close') {
+ // obey explicit "Connection: close" request header or response header if present
+ $response = $response->withHeader('Connection', 'close');
+ } elseif ($version === '1.1') {
+ // HTTP/1.1 assumes persistent connection support by default, so we don't need to inform client
+ $persist = true;
+ } elseif (strtolower($request->getHeaderLine('Connection')) === 'keep-alive') {
+ // obey explicit "Connection: keep-alive" request header and inform client
+ $persist = true;
+ $response = $response->withHeader('Connection', 'keep-alive');
+ } else {
+ // remove any Connection headers unless automatically enabled above
+ $response = $response->withoutHeader('Connection');
+ }
+
+ // 101 (Switching Protocols) response (for Upgrade request) forwards upgraded data through duplex stream
+ // 2xx (Successful) response to CONNECT forwards tunneled application data through duplex stream
+ if (($code === Response::STATUS_SWITCHING_PROTOCOLS || ($method === 'CONNECT' && $code >= 200 && $code < 300)) && $body instanceof HttpBodyStream && $body->input instanceof WritableStreamInterface) {
+ if ($request->getBody()->isReadable()) {
+ // request is still streaming => wait for request close before forwarding following data from connection
+ $request->getBody()->on('close', function () use ($connection, $body) {
+ if ($body->input->isWritable()) {
+ $connection->pipe($body->input);
+ $connection->resume();
+ }
+ });
+ } elseif ($body->input->isWritable()) {
+ // request already closed => forward following data from connection
+ $connection->pipe($body->input);
+ $connection->resume();
+ }
+ }
+
+ // build HTTP response header by appending status line and header fields
+ $headers = "HTTP/" . $version . " " . $code . " " . $response->getReasonPhrase() . "\r\n";
+ foreach ($response->getHeaders() as $name => $values) {
+ foreach ($values as $value) {
+ $headers .= $name . ": " . $value . "\r\n";
+ }
+ }
+
+ // response to HEAD and 1xx, 204 and 304 responses MUST NOT include a body
+ // exclude status 101 (Switching Protocols) here for Upgrade request handling above
+ if ($method === 'HEAD' || ($code >= 100 && $code < 200 && $code !== Response::STATUS_SWITCHING_PROTOCOLS) || $code === Response::STATUS_NO_CONTENT || $code === Response::STATUS_NOT_MODIFIED) {
+ $body->close();
+ $body = '';
+ }
+
+ // this is a non-streaming response body or the body stream already closed?
+ if (!$body instanceof ReadableStreamInterface || !$body->isReadable()) {
+ // add final chunk if a streaming body is already closed and uses `Transfer-Encoding: chunked`
+ if ($body instanceof ReadableStreamInterface && $chunked) {
+ $body = "0\r\n\r\n";
+ }
+
+ // write response headers and body
+ $connection->write($headers . "\r\n" . $body);
+
+ // either wait for next request over persistent connection or end connection
+ if ($persist) {
+ $this->parser->handle($connection);
+ } else {
+ $connection->end();
+ }
+ return;
+ }
+
+ $connection->write($headers . "\r\n");
+
+ if ($chunked) {
+ $body = new ChunkedEncoder($body);
+ }
+
+ // Close response stream once connection closes.
+ // Note that this TCP/IP close detection may take some time,
+ // in particular this may only fire on a later read/write attempt.
+ $connection->on('close', array($body, 'close'));
+
+ // write streaming body and then wait for next request over persistent connection
+ if ($persist) {
+ $body->pipe($connection, array('end' => false));
+ $parser = $this->parser;
+ $body->on('end', function () use ($connection, $parser, $body) {
+ $connection->removeListener('close', array($body, 'close'));
+ $parser->handle($connection);
+ });
+ } else {
+ $body->pipe($connection);
+ }
+ }
+}
diff --git a/vendor/react/http/src/Io/Transaction.php b/vendor/react/http/src/Io/Transaction.php
new file mode 100644
index 0000000..7bf7008
--- /dev/null
+++ b/vendor/react/http/src/Io/Transaction.php
@@ -0,0 +1,303 @@
+<?php
+
+namespace React\Http\Io;
+
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\UriInterface;
+use RingCentral\Psr7\Request;
+use RingCentral\Psr7\Uri;
+use React\EventLoop\LoopInterface;
+use React\Http\Message\ResponseException;
+use React\Promise\Deferred;
+use React\Promise\PromiseInterface;
+use React\Stream\ReadableStreamInterface;
+
+/**
+ * @internal
+ */
+class Transaction
+{
+ private $sender;
+ private $loop;
+
+ // context: http.timeout (ini_get('default_socket_timeout'): 60)
+ private $timeout;
+
+ // context: http.follow_location (true)
+ private $followRedirects = true;
+
+ // context: http.max_redirects (10)
+ private $maxRedirects = 10;
+
+ // context: http.ignore_errors (false)
+ private $obeySuccessCode = true;
+
+ private $streaming = false;
+
+ private $maximumSize = 16777216; // 16 MiB = 2^24 bytes
+
+ public function __construct(Sender $sender, LoopInterface $loop)
+ {
+ $this->sender = $sender;
+ $this->loop = $loop;
+ }
+
+ /**
+ * @param array $options
+ * @return self returns new instance, without modifying existing instance
+ */
+ public function withOptions(array $options)
+ {
+ $transaction = clone $this;
+ foreach ($options as $name => $value) {
+ if (property_exists($transaction, $name)) {
+ // restore default value if null is given
+ if ($value === null) {
+ $default = new self($this->sender, $this->loop);
+ $value = $default->$name;
+ }
+
+ $transaction->$name = $value;
+ }
+ }
+
+ return $transaction;
+ }
+
+ public function send(RequestInterface $request)
+ {
+ $deferred = new Deferred(function () use (&$deferred) {
+ if (isset($deferred->pending)) {
+ $deferred->pending->cancel();
+ unset($deferred->pending);
+ }
+ });
+
+ $deferred->numRequests = 0;
+
+ // use timeout from options or default to PHP's default_socket_timeout (60)
+ $timeout = (float)($this->timeout !== null ? $this->timeout : ini_get("default_socket_timeout"));
+
+ $loop = $this->loop;
+ $this->next($request, $deferred)->then(
+ function (ResponseInterface $response) use ($deferred, $loop, &$timeout) {
+ if (isset($deferred->timeout)) {
+ $loop->cancelTimer($deferred->timeout);
+ unset($deferred->timeout);
+ }
+ $timeout = -1;
+ $deferred->resolve($response);
+ },
+ function ($e) use ($deferred, $loop, &$timeout) {
+ if (isset($deferred->timeout)) {
+ $loop->cancelTimer($deferred->timeout);
+ unset($deferred->timeout);
+ }
+ $timeout = -1;
+ $deferred->reject($e);
+ }
+ );
+
+ if ($timeout < 0) {
+ return $deferred->promise();
+ }
+
+ $body = $request->getBody();
+ if ($body instanceof ReadableStreamInterface && $body->isReadable()) {
+ $that = $this;
+ $body->on('close', function () use ($that, $deferred, &$timeout) {
+ if ($timeout >= 0) {
+ $that->applyTimeout($deferred, $timeout);
+ }
+ });
+ } else {
+ $this->applyTimeout($deferred, $timeout);
+ }
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @internal
+ * @param Deferred $deferred
+ * @param number $timeout
+ * @return void
+ */
+ public function applyTimeout(Deferred $deferred, $timeout)
+ {
+ $deferred->timeout = $this->loop->addTimer($timeout, function () use ($timeout, $deferred) {
+ $deferred->reject(new \RuntimeException(
+ 'Request timed out after ' . $timeout . ' seconds'
+ ));
+ if (isset($deferred->pending)) {
+ $deferred->pending->cancel();
+ unset($deferred->pending);
+ }
+ });
+ }
+
+ private function next(RequestInterface $request, Deferred $deferred)
+ {
+ $this->progress('request', array($request));
+
+ $that = $this;
+ ++$deferred->numRequests;
+
+ $promise = $this->sender->send($request);
+
+ if (!$this->streaming) {
+ $promise = $promise->then(function ($response) use ($deferred, $that) {
+ return $that->bufferResponse($response, $deferred);
+ });
+ }
+
+ $deferred->pending = $promise;
+
+ return $promise->then(
+ function (ResponseInterface $response) use ($request, $that, $deferred) {
+ return $that->onResponse($response, $request, $deferred);
+ }
+ );
+ }
+
+ /**
+ * @internal
+ * @param ResponseInterface $response
+ * @return PromiseInterface Promise<ResponseInterface, Exception>
+ */
+ public function bufferResponse(ResponseInterface $response, $deferred)
+ {
+ $stream = $response->getBody();
+
+ $size = $stream->getSize();
+ if ($size !== null && $size > $this->maximumSize) {
+ $stream->close();
+ return \React\Promise\reject(new \OverflowException(
+ 'Response body size of ' . $size . ' bytes exceeds maximum of ' . $this->maximumSize . ' bytes',
+ \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0
+ ));
+ }
+
+ // body is not streaming => already buffered
+ if (!$stream instanceof ReadableStreamInterface) {
+ return \React\Promise\resolve($response);
+ }
+
+ // buffer stream and resolve with buffered body
+ $maximumSize = $this->maximumSize;
+ $promise = \React\Promise\Stream\buffer($stream, $maximumSize)->then(
+ function ($body) use ($response) {
+ return $response->withBody(new BufferedBody($body));
+ },
+ function ($e) use ($stream, $maximumSize) {
+ // try to close stream if buffering fails (or is cancelled)
+ $stream->close();
+
+ if ($e instanceof \OverflowException) {
+ $e = new \OverflowException(
+ 'Response body size exceeds maximum of ' . $maximumSize . ' bytes',
+ \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0
+ );
+ }
+
+ throw $e;
+ }
+ );
+
+ $deferred->pending = $promise;
+
+ return $promise;
+ }
+
+ /**
+ * @internal
+ * @param ResponseInterface $response
+ * @param RequestInterface $request
+ * @throws ResponseException
+ * @return ResponseInterface|PromiseInterface
+ */
+ public function onResponse(ResponseInterface $response, RequestInterface $request, $deferred)
+ {
+ $this->progress('response', array($response, $request));
+
+ // follow 3xx (Redirection) response status codes if Location header is present and not explicitly disabled
+ // @link https://tools.ietf.org/html/rfc7231#section-6.4
+ if ($this->followRedirects && ($response->getStatusCode() >= 300 && $response->getStatusCode() < 400) && $response->hasHeader('Location')) {
+ return $this->onResponseRedirect($response, $request, $deferred);
+ }
+
+ // only status codes 200-399 are considered to be valid, reject otherwise
+ if ($this->obeySuccessCode && ($response->getStatusCode() < 200 || $response->getStatusCode() >= 400)) {
+ throw new ResponseException($response);
+ }
+
+ // resolve our initial promise
+ return $response;
+ }
+
+ /**
+ * @param ResponseInterface $response
+ * @param RequestInterface $request
+ * @return PromiseInterface
+ * @throws \RuntimeException
+ */
+ private function onResponseRedirect(ResponseInterface $response, RequestInterface $request, Deferred $deferred)
+ {
+ // resolve location relative to last request URI
+ $location = Uri::resolve($request->getUri(), $response->getHeaderLine('Location'));
+
+ $request = $this->makeRedirectRequest($request, $location);
+ $this->progress('redirect', array($request));
+
+ if ($deferred->numRequests >= $this->maxRedirects) {
+ throw new \RuntimeException('Maximum number of redirects (' . $this->maxRedirects . ') exceeded');
+ }
+
+ return $this->next($request, $deferred);
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param UriInterface $location
+ * @return RequestInterface
+ */
+ private function makeRedirectRequest(RequestInterface $request, UriInterface $location)
+ {
+ $originalHost = $request->getUri()->getHost();
+ $request = $request
+ ->withoutHeader('Host')
+ ->withoutHeader('Content-Type')
+ ->withoutHeader('Content-Length');
+
+ // Remove authorization if changing hostnames (but not if just changing ports or protocols).
+ if ($location->getHost() !== $originalHost) {
+ $request = $request->withoutHeader('Authorization');
+ }
+
+ // naïve approach..
+ $method = ($request->getMethod() === 'HEAD') ? 'HEAD' : 'GET';
+
+ return new Request($method, $location, $request->getHeaders());
+ }
+
+ private function progress($name, array $args = array())
+ {
+ return;
+
+ echo $name;
+
+ foreach ($args as $arg) {
+ echo ' ';
+ if ($arg instanceof ResponseInterface) {
+ echo 'HTTP/' . $arg->getProtocolVersion() . ' ' . $arg->getStatusCode() . ' ' . $arg->getReasonPhrase();
+ } elseif ($arg instanceof RequestInterface) {
+ echo $arg->getMethod() . ' ' . $arg->getRequestTarget() . ' HTTP/' . $arg->getProtocolVersion();
+ } else {
+ echo $arg;
+ }
+ }
+
+ echo PHP_EOL;
+ }
+}
diff --git a/vendor/react/http/src/Io/UploadedFile.php b/vendor/react/http/src/Io/UploadedFile.php
new file mode 100644
index 0000000..f2a6c9e
--- /dev/null
+++ b/vendor/react/http/src/Io/UploadedFile.php
@@ -0,0 +1,130 @@
+<?php
+
+namespace React\Http\Io;
+
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UploadedFileInterface;
+use InvalidArgumentException;
+use RuntimeException;
+
+/**
+ * [Internal] Implementation of the PSR-7 `UploadedFileInterface`
+ *
+ * This is used internally to represent each incoming file upload.
+ *
+ * Note that this is an internal class only and nothing you should usually care
+ * about. See the `UploadedFileInterface` for more details.
+ *
+ * @see UploadedFileInterface
+ * @internal
+ */
+final class UploadedFile implements UploadedFileInterface
+{
+ /**
+ * @var StreamInterface
+ */
+ private $stream;
+
+ /**
+ * @var int
+ */
+ private $size;
+
+ /**
+ * @var int
+ */
+ private $error;
+
+ /**
+ * @var string
+ */
+ private $filename;
+
+ /**
+ * @var string
+ */
+ private $mediaType;
+
+ /**
+ * @param StreamInterface $stream
+ * @param int $size
+ * @param int $error
+ * @param string $filename
+ * @param string $mediaType
+ */
+ public function __construct(StreamInterface $stream, $size, $error, $filename, $mediaType)
+ {
+ $this->stream = $stream;
+ $this->size = $size;
+
+ if (!\is_int($error) || !\in_array($error, array(
+ \UPLOAD_ERR_OK,
+ \UPLOAD_ERR_INI_SIZE,
+ \UPLOAD_ERR_FORM_SIZE,
+ \UPLOAD_ERR_PARTIAL,
+ \UPLOAD_ERR_NO_FILE,
+ \UPLOAD_ERR_NO_TMP_DIR,
+ \UPLOAD_ERR_CANT_WRITE,
+ \UPLOAD_ERR_EXTENSION,
+ ))) {
+ throw new InvalidArgumentException(
+ 'Invalid error code, must be an UPLOAD_ERR_* constant'
+ );
+ }
+ $this->error = $error;
+ $this->filename = $filename;
+ $this->mediaType = $mediaType;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStream()
+ {
+ if ($this->error !== \UPLOAD_ERR_OK) {
+ throw new RuntimeException('Cannot retrieve stream due to upload error');
+ }
+
+ return $this->stream;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function moveTo($targetPath)
+ {
+ throw new RuntimeException('Not implemented');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getClientFilename()
+ {
+ return $this->filename;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getClientMediaType()
+ {
+ return $this->mediaType;
+ }
+}
diff --git a/vendor/react/http/src/Message/Response.php b/vendor/react/http/src/Message/Response.php
new file mode 100644
index 0000000..edd6245
--- /dev/null
+++ b/vendor/react/http/src/Message/Response.php
@@ -0,0 +1,291 @@
+<?php
+
+namespace React\Http\Message;
+
+use Fig\Http\Message\StatusCodeInterface;
+use Psr\Http\Message\StreamInterface;
+use React\Http\Io\BufferedBody;
+use React\Http\Io\HttpBodyStream;
+use React\Stream\ReadableStreamInterface;
+use RingCentral\Psr7\Response as Psr7Response;
+
+/**
+ * Represents an outgoing server response message.
+ *
+ * ```php
+ * $response = new React\Http\Message\Response(
+ * React\Http\Message\Response::STATUS_OK,
+ * array(
+ * 'Content-Type' => 'text/html'
+ * ),
+ * "<html>Hello world!</html>\n"
+ * );
+ * ```
+ *
+ * This class implements the
+ * [PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface)
+ * which in turn extends the
+ * [PSR-7 `MessageInterface`](https://www.php-fig.org/psr/psr-7/#31-psrhttpmessagemessageinterface).
+ *
+ * On top of this, this class implements the
+ * [PSR-7 Message Util `StatusCodeInterface`](https://github.com/php-fig/http-message-util/blob/master/src/StatusCodeInterface.php)
+ * which means that most common HTTP status codes are available as class
+ * constants with the `STATUS_*` prefix. For instance, the `200 OK` and
+ * `404 Not Found` status codes can used as `Response::STATUS_OK` and
+ * `Response::STATUS_NOT_FOUND` respectively.
+ *
+ * > Internally, this implementation builds on top of an existing incoming
+ * response message and only adds required streaming support. This base class is
+ * considered an implementation detail that may change in the future.
+ *
+ * @see \Psr\Http\Message\ResponseInterface
+ */
+final class Response extends Psr7Response implements StatusCodeInterface
+{
+ /**
+ * Create an HTML response
+ *
+ * ```php
+ * $html = <<<HTML
+ * <!doctype html>
+ * <html>
+ * <body>Hello wörld!</body>
+ * </html>
+ *
+ * HTML;
+ *
+ * $response = React\Http\Message\Response::html($html);
+ * ```
+ *
+ * This is a convenient shortcut method that returns the equivalent of this:
+ *
+ * ```
+ * $response = new React\Http\Message\Response(
+ * React\Http\Message\Response::STATUS_OK,
+ * [
+ * 'Content-Type' => 'text/html; charset=utf-8'
+ * ],
+ * $html
+ * );
+ * ```
+ *
+ * This method always returns a response with a `200 OK` status code and
+ * the appropriate `Content-Type` response header for the given HTTP source
+ * string encoded in UTF-8 (Unicode). It's generally recommended to end the
+ * given plaintext string with a trailing newline.
+ *
+ * If you want to use a different status code or custom HTTP response
+ * headers, you can manipulate the returned response object using the
+ * provided PSR-7 methods or directly instantiate a custom HTTP response
+ * object using the `Response` constructor:
+ *
+ * ```php
+ * $response = React\Http\Message\Response::html(
+ * "<h1>Error</h1>\n<p>Invalid user name given.</p>\n"
+ * )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
+ * ```
+ *
+ * @param string $html
+ * @return self
+ */
+ public static function html($html)
+ {
+ return new self(self::STATUS_OK, array('Content-Type' => 'text/html; charset=utf-8'), $html);
+ }
+
+ /**
+ * Create a JSON response
+ *
+ * ```php
+ * $response = React\Http\Message\Response::json(['name' => 'Alice']);
+ * ```
+ *
+ * This is a convenient shortcut method that returns the equivalent of this:
+ *
+ * ```
+ * $response = new React\Http\Message\Response(
+ * React\Http\Message\Response::STATUS_OK,
+ * [
+ * 'Content-Type' => 'application/json'
+ * ],
+ * json_encode(
+ * ['name' => 'Alice'],
+ * JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION
+ * ) . "\n"
+ * );
+ * ```
+ *
+ * This method always returns a response with a `200 OK` status code and
+ * the appropriate `Content-Type` response header for the given structured
+ * data encoded as a JSON text.
+ *
+ * The given structured data will be encoded as a JSON text. Any `string`
+ * values in the data must be encoded in UTF-8 (Unicode). If the encoding
+ * fails, this method will throw an `InvalidArgumentException`.
+ *
+ * By default, the given structured data will be encoded with the flags as
+ * shown above. This includes pretty printing (PHP 5.4+) and preserving
+ * zero fractions for `float` values (PHP 5.6.6+) to ease debugging. It is
+ * assumed any additional data overhead is usually compensated by using HTTP
+ * response compression.
+ *
+ * If you want to use a different status code or custom HTTP response
+ * headers, you can manipulate the returned response object using the
+ * provided PSR-7 methods or directly instantiate a custom HTTP response
+ * object using the `Response` constructor:
+ *
+ * ```php
+ * $response = React\Http\Message\Response::json(
+ * ['error' => 'Invalid user name given']
+ * )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
+ * ```
+ *
+ * @param mixed $data
+ * @return self
+ * @throws \InvalidArgumentException when encoding fails
+ */
+ public static function json($data)
+ {
+ $json = @\json_encode(
+ $data,
+ (\defined('JSON_PRETTY_PRINT') ? \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE : 0) | (\defined('JSON_PRESERVE_ZERO_FRACTION') ? \JSON_PRESERVE_ZERO_FRACTION : 0)
+ );
+
+ // throw on error, now `false` but used to be `(string) "null"` before PHP 5.5
+ if ($json === false || (\PHP_VERSION_ID < 50500 && \json_last_error() !== \JSON_ERROR_NONE)) {
+ throw new \InvalidArgumentException(
+ 'Unable to encode given data as JSON' . (\function_exists('json_last_error_msg') ? ': ' . \json_last_error_msg() : ''),
+ \json_last_error()
+ );
+ }
+
+ return new self(self::STATUS_OK, array('Content-Type' => 'application/json'), $json . "\n");
+ }
+
+ /**
+ * Create a plaintext response
+ *
+ * ```php
+ * $response = React\Http\Message\Response::plaintext("Hello wörld!\n");
+ * ```
+ *
+ * This is a convenient shortcut method that returns the equivalent of this:
+ *
+ * ```
+ * $response = new React\Http\Message\Response(
+ * React\Http\Message\Response::STATUS_OK,
+ * [
+ * 'Content-Type' => 'text/plain; charset=utf-8'
+ * ],
+ * "Hello wörld!\n"
+ * );
+ * ```
+ *
+ * This method always returns a response with a `200 OK` status code and
+ * the appropriate `Content-Type` response header for the given plaintext
+ * string encoded in UTF-8 (Unicode). It's generally recommended to end the
+ * given plaintext string with a trailing newline.
+ *
+ * If you want to use a different status code or custom HTTP response
+ * headers, you can manipulate the returned response object using the
+ * provided PSR-7 methods or directly instantiate a custom HTTP response
+ * object using the `Response` constructor:
+ *
+ * ```php
+ * $response = React\Http\Message\Response::plaintext(
+ * "Error: Invalid user name given.\n"
+ * )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
+ * ```
+ *
+ * @param string $text
+ * @return self
+ */
+ public static function plaintext($text)
+ {
+ return new self(self::STATUS_OK, array('Content-Type' => 'text/plain; charset=utf-8'), $text);
+ }
+
+ /**
+ * Create an XML response
+ *
+ * ```php
+ * $xml = <<<XML
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <body>
+ * <greeting>Hello wörld!</greeting>
+ * </body>
+ *
+ * XML;
+ *
+ * $response = React\Http\Message\Response::xml($xml);
+ * ```
+ *
+ * This is a convenient shortcut method that returns the equivalent of this:
+ *
+ * ```
+ * $response = new React\Http\Message\Response(
+ * React\Http\Message\Response::STATUS_OK,
+ * [
+ * 'Content-Type' => 'application/xml'
+ * ],
+ * $xml
+ * );
+ * ```
+ *
+ * This method always returns a response with a `200 OK` status code and
+ * the appropriate `Content-Type` response header for the given XML source
+ * string. It's generally recommended to use UTF-8 (Unicode) and specify
+ * this as part of the leading XML declaration and to end the given XML
+ * source string with a trailing newline.
+ *
+ * If you want to use a different status code or custom HTTP response
+ * headers, you can manipulate the returned response object using the
+ * provided PSR-7 methods or directly instantiate a custom HTTP response
+ * object using the `Response` constructor:
+ *
+ * ```php
+ * $response = React\Http\Message\Response::xml(
+ * "<error><message>Invalid user name given.</message></error>\n"
+ * )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
+ * ```
+ *
+ * @param string $xml
+ * @return self
+ */
+ public static function xml($xml)
+ {
+ return new self(self::STATUS_OK, array('Content-Type' => 'application/xml'), $xml);
+ }
+
+ /**
+ * @param int $status HTTP status code (e.g. 200/404), see `self::STATUS_*` constants
+ * @param array<string,string|string[]> $headers additional response headers
+ * @param string|ReadableStreamInterface|StreamInterface $body response body
+ * @param string $version HTTP protocol version (e.g. 1.1/1.0)
+ * @param ?string $reason custom HTTP response phrase
+ * @throws \InvalidArgumentException for an invalid body
+ */
+ public function __construct(
+ $status = self::STATUS_OK,
+ array $headers = array(),
+ $body = '',
+ $version = '1.1',
+ $reason = null
+ ) {
+ if (\is_string($body)) {
+ $body = new BufferedBody($body);
+ } elseif ($body instanceof ReadableStreamInterface && !$body instanceof StreamInterface) {
+ $body = new HttpBodyStream($body, null);
+ } elseif (!$body instanceof StreamInterface) {
+ throw new \InvalidArgumentException('Invalid response body given');
+ }
+
+ parent::__construct(
+ $status,
+ $headers,
+ $body,
+ $version,
+ $reason
+ );
+ }
+}
diff --git a/vendor/react/http/src/Message/ResponseException.php b/vendor/react/http/src/Message/ResponseException.php
new file mode 100644
index 0000000..f4912c9
--- /dev/null
+++ b/vendor/react/http/src/Message/ResponseException.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace React\Http\Message;
+
+use RuntimeException;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * The `React\Http\Message\ResponseException` is an `Exception` sub-class that will be used to reject
+ * a request promise if the remote server returns a non-success status code
+ * (anything but 2xx or 3xx).
+ * You can control this behavior via the [`withRejectErrorResponse()` method](#withrejecterrorresponse).
+ *
+ * The `getCode(): int` method can be used to
+ * return the HTTP response status code.
+ */
+final class ResponseException extends RuntimeException
+{
+ private $response;
+
+ public function __construct(ResponseInterface $response, $message = null, $code = null, $previous = null)
+ {
+ if ($message === null) {
+ $message = 'HTTP status code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ')';
+ }
+ if ($code === null) {
+ $code = $response->getStatusCode();
+ }
+ parent::__construct($message, $code, $previous);
+
+ $this->response = $response;
+ }
+
+ /**
+ * Access its underlying response object.
+ *
+ * @return ResponseInterface
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+}
diff --git a/vendor/react/http/src/Message/ServerRequest.php b/vendor/react/http/src/Message/ServerRequest.php
new file mode 100644
index 0000000..f446f24
--- /dev/null
+++ b/vendor/react/http/src/Message/ServerRequest.php
@@ -0,0 +1,197 @@
+<?php
+
+namespace React\Http\Message;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UriInterface;
+use React\Http\Io\BufferedBody;
+use React\Http\Io\HttpBodyStream;
+use React\Stream\ReadableStreamInterface;
+use RingCentral\Psr7\Request;
+
+/**
+ * Respresents an incoming server request message.
+ *
+ * This class implements the
+ * [PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface)
+ * which extends the
+ * [PSR-7 `RequestInterface`](https://www.php-fig.org/psr/psr-7/#32-psrhttpmessagerequestinterface)
+ * which in turn extends the
+ * [PSR-7 `MessageInterface`](https://www.php-fig.org/psr/psr-7/#31-psrhttpmessagemessageinterface).
+ *
+ * This is mostly used internally to represent each incoming request message.
+ * Likewise, you can also use this class in test cases to test how your web
+ * application reacts to certain HTTP requests.
+ *
+ * > Internally, this implementation builds on top of an existing outgoing
+ * request message and only adds required server methods. This base class is
+ * considered an implementation detail that may change in the future.
+ *
+ * @see ServerRequestInterface
+ */
+final class ServerRequest extends Request implements ServerRequestInterface
+{
+ private $attributes = array();
+
+ private $serverParams;
+ private $fileParams = array();
+ private $cookies = array();
+ private $queryParams = array();
+ private $parsedBody;
+
+ /**
+ * @param string $method HTTP method for the request.
+ * @param string|UriInterface $url URL for the request.
+ * @param array<string,string|string[]> $headers Headers for the message.
+ * @param string|ReadableStreamInterface|StreamInterface $body Message body.
+ * @param string $version HTTP protocol version.
+ * @param array<string,string> $serverParams server-side parameters
+ * @throws \InvalidArgumentException for an invalid URL or body
+ */
+ public function __construct(
+ $method,
+ $url,
+ array $headers = array(),
+ $body = '',
+ $version = '1.1',
+ $serverParams = array()
+ ) {
+ $stream = null;
+ if (\is_string($body)) {
+ $body = new BufferedBody($body);
+ } elseif ($body instanceof ReadableStreamInterface && !$body instanceof StreamInterface) {
+ $stream = $body;
+ $body = null;
+ } elseif (!$body instanceof StreamInterface) {
+ throw new \InvalidArgumentException('Invalid server request body given');
+ }
+
+ $this->serverParams = $serverParams;
+ parent::__construct($method, $url, $headers, $body, $version);
+
+ if ($stream !== null) {
+ $size = (int) $this->getHeaderLine('Content-Length');
+ if (\strtolower($this->getHeaderLine('Transfer-Encoding')) === 'chunked') {
+ $size = null;
+ }
+ $this->stream = new HttpBodyStream($stream, $size);
+ }
+
+ $query = $this->getUri()->getQuery();
+ if ($query !== '') {
+ \parse_str($query, $this->queryParams);
+ }
+
+ // Multiple cookie headers are not allowed according
+ // to https://tools.ietf.org/html/rfc6265#section-5.4
+ $cookieHeaders = $this->getHeader("Cookie");
+
+ if (count($cookieHeaders) === 1) {
+ $this->cookies = $this->parseCookie($cookieHeaders[0]);
+ }
+ }
+
+ public function getServerParams()
+ {
+ return $this->serverParams;
+ }
+
+ public function getCookieParams()
+ {
+ return $this->cookies;
+ }
+
+ public function withCookieParams(array $cookies)
+ {
+ $new = clone $this;
+ $new->cookies = $cookies;
+ return $new;
+ }
+
+ public function getQueryParams()
+ {
+ return $this->queryParams;
+ }
+
+ public function withQueryParams(array $query)
+ {
+ $new = clone $this;
+ $new->queryParams = $query;
+ return $new;
+ }
+
+ public function getUploadedFiles()
+ {
+ return $this->fileParams;
+ }
+
+ public function withUploadedFiles(array $uploadedFiles)
+ {
+ $new = clone $this;
+ $new->fileParams = $uploadedFiles;
+ return $new;
+ }
+
+ public function getParsedBody()
+ {
+ return $this->parsedBody;
+ }
+
+ public function withParsedBody($data)
+ {
+ $new = clone $this;
+ $new->parsedBody = $data;
+ return $new;
+ }
+
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ public function getAttribute($name, $default = null)
+ {
+ if (!\array_key_exists($name, $this->attributes)) {
+ return $default;
+ }
+ return $this->attributes[$name];
+ }
+
+ public function withAttribute($name, $value)
+ {
+ $new = clone $this;
+ $new->attributes[$name] = $value;
+ return $new;
+ }
+
+ public function withoutAttribute($name)
+ {
+ $new = clone $this;
+ unset($new->attributes[$name]);
+ return $new;
+ }
+
+ /**
+ * @param string $cookie
+ * @return array
+ */
+ private function parseCookie($cookie)
+ {
+ $cookieArray = \explode(';', $cookie);
+ $result = array();
+
+ foreach ($cookieArray as $pair) {
+ $pair = \trim($pair);
+ $nameValuePair = \explode('=', $pair, 2);
+
+ if (\count($nameValuePair) === 2) {
+ $key = \urldecode($nameValuePair[0]);
+ $value = \urldecode($nameValuePair[1]);
+ $result[$key] = $value;
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/react/http/src/Middleware/LimitConcurrentRequestsMiddleware.php b/vendor/react/http/src/Middleware/LimitConcurrentRequestsMiddleware.php
new file mode 100644
index 0000000..5333810
--- /dev/null
+++ b/vendor/react/http/src/Middleware/LimitConcurrentRequestsMiddleware.php
@@ -0,0 +1,211 @@
+<?php
+
+namespace React\Http\Middleware;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use React\Http\Io\HttpBodyStream;
+use React\Http\Io\PauseBufferStream;
+use React\Promise;
+use React\Promise\PromiseInterface;
+use React\Promise\Deferred;
+use React\Stream\ReadableStreamInterface;
+
+/**
+ * Limits how many next handlers can be executed concurrently.
+ *
+ * If this middleware is invoked, it will check if the number of pending
+ * handlers is below the allowed limit and then simply invoke the next handler
+ * and it will return whatever the next handler returns (or throws).
+ *
+ * If the number of pending handlers exceeds the allowed limit, the request will
+ * be queued (and its streaming body will be paused) and it will return a pending
+ * promise.
+ * Once a pending handler returns (or throws), it will pick the oldest request
+ * from this queue and invokes the next handler (and its streaming body will be
+ * resumed).
+ *
+ * The following example shows how this middleware can be used to ensure no more
+ * than 10 handlers will be invoked at once:
+ *
+ * ```php
+ * $http = new React\Http\HttpServer(
+ * new React\Http\Middleware\StreamingRequestMiddleware(),
+ * new React\Http\Middleware\LimitConcurrentRequestsMiddleware(10),
+ * $handler
+ * );
+ * ```
+ *
+ * Similarly, this middleware is often used in combination with the
+ * [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) (see below)
+ * to limit the total number of requests that can be buffered at once:
+ *
+ * ```php
+ * $http = new React\Http\HttpServer(
+ * new React\Http\Middleware\StreamingRequestMiddleware(),
+ * new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
+ * new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
+ * new React\Http\Middleware\RequestBodyParserMiddleware(),
+ * $handler
+ * );
+ * ```
+ *
+ * More sophisticated examples include limiting the total number of requests
+ * that can be buffered at once and then ensure the actual request handler only
+ * processes one request after another without any concurrency:
+ *
+ * ```php
+ * $http = new React\Http\HttpServer(
+ * new React\Http\Middleware\StreamingRequestMiddleware(),
+ * new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
+ * new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
+ * new React\Http\Middleware\RequestBodyParserMiddleware(),
+ * new React\Http\Middleware\LimitConcurrentRequestsMiddleware(1), // only execute 1 handler (no concurrency)
+ * $handler
+ * );
+ * ```
+ *
+ * @see RequestBodyBufferMiddleware
+ */
+final class LimitConcurrentRequestsMiddleware
+{
+ private $limit;
+ private $pending = 0;
+ private $queue = array();
+
+ /**
+ * @param int $limit Maximum amount of concurrent requests handled.
+ *
+ * For example when $limit is set to 10, 10 requests will flow to $next
+ * while more incoming requests have to wait until one is done.
+ */
+ public function __construct($limit)
+ {
+ $this->limit = $limit;
+ }
+
+ public function __invoke(ServerRequestInterface $request, $next)
+ {
+ // happy path: simply invoke next request handler if we're below limit
+ if ($this->pending < $this->limit) {
+ ++$this->pending;
+
+ try {
+ $response = $next($request);
+ } catch (\Exception $e) {
+ $this->processQueue();
+ throw $e;
+ } catch (\Throwable $e) { // @codeCoverageIgnoreStart
+ // handle Errors just like Exceptions (PHP 7+ only)
+ $this->processQueue();
+ throw $e; // @codeCoverageIgnoreEnd
+ }
+
+ // happy path: if next request handler returned immediately,
+ // we can simply try to invoke the next queued request
+ if ($response instanceof ResponseInterface) {
+ $this->processQueue();
+ return $response;
+ }
+
+ // if the next handler returns a pending promise, we have to
+ // await its resolution before invoking next queued request
+ return $this->await(Promise\resolve($response));
+ }
+
+ // if we reach this point, then this request will need to be queued
+ // check if the body is streaming, in which case we need to buffer everything
+ $body = $request->getBody();
+ if ($body instanceof ReadableStreamInterface) {
+ // pause actual body to stop emitting data until the handler is called
+ $size = $body->getSize();
+ $body = new PauseBufferStream($body);
+ $body->pauseImplicit();
+
+ // replace with buffering body to ensure any readable events will be buffered
+ $request = $request->withBody(new HttpBodyStream(
+ $body,
+ $size
+ ));
+ }
+
+ // get next queue position
+ $queue =& $this->queue;
+ $queue[] = null;
+ \end($queue);
+ $id = \key($queue);
+
+ $deferred = new Deferred(function ($_, $reject) use (&$queue, $id) {
+ // queued promise cancelled before its next handler is invoked
+ // remove from queue and reject explicitly
+ unset($queue[$id]);
+ $reject(new \RuntimeException('Cancelled queued next handler'));
+ });
+
+ // queue request and process queue if pending does not exceed limit
+ $queue[$id] = $deferred;
+
+ $pending = &$this->pending;
+ $that = $this;
+ return $deferred->promise()->then(function () use ($request, $next, $body, &$pending, $that) {
+ // invoke next request handler
+ ++$pending;
+
+ try {
+ $response = $next($request);
+ } catch (\Exception $e) {
+ $that->processQueue();
+ throw $e;
+ } catch (\Throwable $e) { // @codeCoverageIgnoreStart
+ // handle Errors just like Exceptions (PHP 7+ only)
+ $that->processQueue();
+ throw $e; // @codeCoverageIgnoreEnd
+ }
+
+ // resume readable stream and replay buffered events
+ if ($body instanceof PauseBufferStream) {
+ $body->resumeImplicit();
+ }
+
+ // if the next handler returns a pending promise, we have to
+ // await its resolution before invoking next queued request
+ return $that->await(Promise\resolve($response));
+ });
+ }
+
+ /**
+ * @internal
+ * @param PromiseInterface $promise
+ * @return PromiseInterface
+ */
+ public function await(PromiseInterface $promise)
+ {
+ $that = $this;
+
+ return $promise->then(function ($response) use ($that) {
+ $that->processQueue();
+
+ return $response;
+ }, 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 request waiting
+ if (--$this->pending >= $this->limit || !$this->queue) {
+ return;
+ }
+
+ $first = \reset($this->queue);
+ unset($this->queue[key($this->queue)]);
+
+ $first->resolve();
+ }
+}
diff --git a/vendor/react/http/src/Middleware/RequestBodyBufferMiddleware.php b/vendor/react/http/src/Middleware/RequestBodyBufferMiddleware.php
new file mode 100644
index 0000000..c13a5de
--- /dev/null
+++ b/vendor/react/http/src/Middleware/RequestBodyBufferMiddleware.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace React\Http\Middleware;
+
+use OverflowException;
+use Psr\Http\Message\ServerRequestInterface;
+use React\Http\Io\BufferedBody;
+use React\Http\Io\IniUtil;
+use React\Promise\Stream;
+use React\Stream\ReadableStreamInterface;
+
+final class RequestBodyBufferMiddleware
+{
+ private $sizeLimit;
+
+ /**
+ * @param int|string|null $sizeLimit Either an int with the max request body size
+ * in bytes or an ini like size string
+ * or null to use post_max_size from PHP's
+ * configuration. (Note that the value from
+ * the CLI configuration will be used.)
+ */
+ public function __construct($sizeLimit = null)
+ {
+ if ($sizeLimit === null) {
+ $sizeLimit = \ini_get('post_max_size');
+ }
+
+ $this->sizeLimit = IniUtil::iniSizeToBytes($sizeLimit);
+ }
+
+ public function __invoke(ServerRequestInterface $request, $stack)
+ {
+ $body = $request->getBody();
+ $size = $body->getSize();
+
+ // happy path: skip if body is known to be empty (or is already buffered)
+ if ($size === 0 || !$body instanceof ReadableStreamInterface) {
+ // replace with empty body if body is streaming (or buffered size exceeds limit)
+ if ($body instanceof ReadableStreamInterface || $size > $this->sizeLimit) {
+ $request = $request->withBody(new BufferedBody(''));
+ }
+
+ return $stack($request);
+ }
+
+ // request body of known size exceeding limit
+ $sizeLimit = $this->sizeLimit;
+ if ($size > $this->sizeLimit) {
+ $sizeLimit = 0;
+ }
+
+ return Stream\buffer($body, $sizeLimit)->then(function ($buffer) use ($request, $stack) {
+ $request = $request->withBody(new BufferedBody($buffer));
+
+ return $stack($request);
+ }, function ($error) use ($stack, $request, $body) {
+ // On buffer overflow keep the request body stream in,
+ // but ignore the contents and wait for the close event
+ // before passing the request on to the next middleware.
+ if ($error instanceof OverflowException) {
+ return Stream\first($body, 'close')->then(function () use ($stack, $request) {
+ return $stack($request);
+ });
+ }
+
+ throw $error;
+ });
+ }
+}
diff --git a/vendor/react/http/src/Middleware/RequestBodyParserMiddleware.php b/vendor/react/http/src/Middleware/RequestBodyParserMiddleware.php
new file mode 100644
index 0000000..be5ba16
--- /dev/null
+++ b/vendor/react/http/src/Middleware/RequestBodyParserMiddleware.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace React\Http\Middleware;
+
+use Psr\Http\Message\ServerRequestInterface;
+use React\Http\Io\MultipartParser;
+
+final class RequestBodyParserMiddleware
+{
+ private $multipart;
+
+ /**
+ * @param int|string|null $uploadMaxFilesize
+ * @param int|null $maxFileUploads
+ */
+ public function __construct($uploadMaxFilesize = null, $maxFileUploads = null)
+ {
+ $this->multipart = new MultipartParser($uploadMaxFilesize, $maxFileUploads);
+ }
+
+ public function __invoke(ServerRequestInterface $request, $next)
+ {
+ $type = \strtolower($request->getHeaderLine('Content-Type'));
+ list ($type) = \explode(';', $type);
+
+ if ($type === 'application/x-www-form-urlencoded') {
+ return $next($this->parseFormUrlencoded($request));
+ }
+
+ if ($type === 'multipart/form-data') {
+ return $next($this->multipart->parse($request));
+ }
+
+ return $next($request);
+ }
+
+ private function parseFormUrlencoded(ServerRequestInterface $request)
+ {
+ // parse string into array structure
+ // ignore warnings due to excessive data structures (max_input_vars and max_input_nesting_level)
+ $ret = array();
+ @\parse_str((string)$request->getBody(), $ret);
+
+ return $request->withParsedBody($ret);
+ }
+}
diff --git a/vendor/react/http/src/Middleware/StreamingRequestMiddleware.php b/vendor/react/http/src/Middleware/StreamingRequestMiddleware.php
new file mode 100644
index 0000000..6ab74b7
--- /dev/null
+++ b/vendor/react/http/src/Middleware/StreamingRequestMiddleware.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace React\Http\Middleware;
+
+use Psr\Http\Message\ServerRequestInterface;
+
+/**
+ * Process incoming requests with a streaming request body (without buffering).
+ *
+ * This allows you to process requests of any size without buffering the request
+ * body in memory. Instead, it will represent the request body as a
+ * [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface)
+ * that emit chunks of incoming data as it is received:
+ *
+ * ```php
+ * $http = new React\Http\HttpServer(
+ * new React\Http\Middleware\StreamingRequestMiddleware(),
+ * function (Psr\Http\Message\ServerRequestInterface $request) {
+ * $body = $request->getBody();
+ * assert($body instanceof Psr\Http\Message\StreamInterface);
+ * assert($body instanceof React\Stream\ReadableStreamInterface);
+ *
+ * return new React\Promise\Promise(function ($resolve) use ($body) {
+ * $bytes = 0;
+ * $body->on('data', function ($chunk) use (&$bytes) {
+ * $bytes += \count($chunk);
+ * });
+ * $body->on('close', function () use (&$bytes, $resolve) {
+ * $resolve(new React\Http\Response(
+ * 200,
+ * [],
+ * "Received $bytes bytes\n"
+ * ));
+ * });
+ * });
+ * }
+ * );
+ * ```
+ *
+ * See also [streaming incoming request](../../README.md#streaming-incoming-request)
+ * for more details.
+ *
+ * Additionally, this middleware can be used in combination with the
+ * [`LimitConcurrentRequestsMiddleware`](#limitconcurrentrequestsmiddleware) and
+ * [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) (see below)
+ * to explicitly configure the total number of requests that can be handled at
+ * once:
+ *
+ * ```php
+ * $http = new React\Http\HttpServer(
+ * new React\Http\Middleware\StreamingRequestMiddleware(),
+ * new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
+ * new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
+ * new React\Http\Middleware\RequestBodyParserMiddleware(),
+ * $handler
+ * );
+ * ```
+ *
+ * > Internally, this class is used as a "marker" to not trigger the default
+ * request buffering behavior in the `HttpServer`. It does not implement any logic
+ * on its own.
+ */
+final class StreamingRequestMiddleware
+{
+ public function __invoke(ServerRequestInterface $request, $next)
+ {
+ return $next($request);
+ }
+}
diff --git a/vendor/react/http/src/Server.php b/vendor/react/http/src/Server.php
new file mode 100644
index 0000000..9bb9cf7
--- /dev/null
+++ b/vendor/react/http/src/Server.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace React\Http;
+
+// Deprecated `Server` is an alias for new `HttpServer` to ensure existing code continues to work as-is.
+\class_alias(__NAMESPACE__ . '\\HttpServer', __NAMESPACE__ . '\\Server', true);
+
+// Aid static analysis and IDE autocompletion about this deprecation,
+// but don't actually execute during runtime because `HttpServer` is final.
+if (!\class_exists(__NAMESPACE__ . '\\Server', false)) {
+ /**
+ * @deprecated 1.5.0 See HttpServer instead
+ * @see HttpServer
+ */
+ final class Server extends HttpServer
+ {
+ }
+}
diff --git a/vendor/react/promise-stream/LICENSE b/vendor/react/promise-stream/LICENSE
new file mode 100644
index 0000000..25e7071
--- /dev/null
+++ b/vendor/react/promise-stream/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden
+
+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/react/promise-stream/composer.json b/vendor/react/promise-stream/composer.json
new file mode 100644
index 0000000..ee3972c
--- /dev/null
+++ b/vendor/react/promise-stream/composer.json
@@ -0,0 +1,47 @@
+{
+ "name": "react/promise-stream",
+ "description": "The missing link between Promise-land and Stream-land for ReactPHP",
+ "keywords": ["unwrap", "stream", "buffer", "promise", "ReactPHP", "async"],
+ "homepage": "https://github.com/reactphp/promise-stream",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "React\\Promise\\Stream\\" : "src/" },
+ "files": [ "src/functions_include.php" ]
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Promise\\Stream\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.6",
+ "react/promise": "^2.1 || ^1.2"
+ },
+ "require-dev": {
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
+ "react/promise-timer": "^1.0",
+ "clue/block-react": "^1.0",
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/vendor/react/promise-stream/src/UnwrapReadableStream.php b/vendor/react/promise-stream/src/UnwrapReadableStream.php
new file mode 100644
index 0000000..acd23be
--- /dev/null
+++ b/vendor/react/promise-stream/src/UnwrapReadableStream.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace React\Promise\Stream;
+
+use Evenement\EventEmitter;
+use InvalidArgumentException;
+use React\Promise\CancellablePromiseInterface;
+use React\Promise\PromiseInterface;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * @internal
+ * @see unwrapReadable() instead
+ */
+class UnwrapReadableStream extends EventEmitter implements ReadableStreamInterface
+{
+ private $promise;
+ private $closed = false;
+
+ /**
+ * Instantiate new unwrapped readable stream for given `Promise` which resolves with a `ReadableStreamInterface`.
+ *
+ * @param PromiseInterface $promise Promise<ReadableStreamInterface, Exception>
+ */
+ public function __construct(PromiseInterface $promise)
+ {
+ $out = $this;
+ $closed =& $this->closed;
+
+ $this->promise = $promise->then(
+ function ($stream) {
+ if (!$stream instanceof ReadableStreamInterface) {
+ throw new InvalidArgumentException('Not a readable stream');
+ }
+ return $stream;
+ }
+ )->then(
+ function (ReadableStreamInterface $stream) use ($out, &$closed) {
+ // stream is already closed, make sure to close output stream
+ if (!$stream->isReadable()) {
+ $out->close();
+ return $stream;
+ }
+
+ // resolves but output is already closed, make sure to close stream silently
+ if ($closed) {
+ $stream->close();
+ return $stream;
+ }
+
+ // stream any writes into output stream
+ $stream->on('data', function ($data) use ($out) {
+ $out->emit('data', array($data, $out));
+ });
+
+ // forward end events and close
+ $stream->on('end', function () use ($out, &$closed) {
+ if (!$closed) {
+ $out->emit('end', array($out));
+ $out->close();
+ }
+ });
+
+ // error events cancel output stream
+ $stream->on('error', function ($error) use ($out) {
+ $out->emit('error', array($error, $out));
+ $out->close();
+ });
+
+ // close both streams once either side closes
+ $stream->on('close', array($out, 'close'));
+ $out->on('close', array($stream, 'close'));
+
+ return $stream;
+ },
+ function ($e) use ($out, &$closed) {
+ if (!$closed) {
+ $out->emit('error', array($e, $out));
+ $out->close();
+ }
+
+ // resume() and pause() may attach to this promise, so ensure we actually reject here
+ throw $e;
+ }
+ );
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed;
+ }
+
+ public function pause()
+ {
+ if ($this->promise !== null) {
+ $this->promise->then(function (ReadableStreamInterface $stream) {
+ $stream->pause();
+ });
+ }
+ }
+
+ public function resume()
+ {
+ if ($this->promise !== null) {
+ $this->promise->then(function (ReadableStreamInterface $stream) {
+ $stream->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;
+
+ // try to cancel promise once the stream closes
+ if ($this->promise instanceof CancellablePromiseInterface) {
+ $this->promise->cancel();
+ }
+ $this->promise = null;
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/vendor/react/promise-stream/src/UnwrapWritableStream.php b/vendor/react/promise-stream/src/UnwrapWritableStream.php
new file mode 100644
index 0000000..f19e706
--- /dev/null
+++ b/vendor/react/promise-stream/src/UnwrapWritableStream.php
@@ -0,0 +1,164 @@
+<?php
+
+namespace React\Promise\Stream;
+
+use Evenement\EventEmitter;
+use InvalidArgumentException;
+use React\Promise\CancellablePromiseInterface;
+use React\Promise\PromiseInterface;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * @internal
+ * @see unwrapWritable() instead
+ */
+class UnwrapWritableStream extends EventEmitter implements WritableStreamInterface
+{
+ private $promise;
+ private $stream;
+ private $buffer = array();
+ private $closed = false;
+ private $ending = false;
+
+ /**
+ * Instantiate new unwrapped writable stream for given `Promise` which resolves with a `WritableStreamInterface`.
+ *
+ * @param PromiseInterface $promise Promise<WritableStreamInterface, Exception>
+ */
+ public function __construct(PromiseInterface $promise)
+ {
+ $out = $this;
+ $store =& $this->stream;
+ $buffer =& $this->buffer;
+ $ending =& $this->ending;
+ $closed =& $this->closed;
+
+ $this->promise = $promise->then(
+ function ($stream) {
+ if (!$stream instanceof WritableStreamInterface) {
+ throw new InvalidArgumentException('Not a writable stream');
+ }
+ return $stream;
+ }
+ )->then(
+ function (WritableStreamInterface $stream) use ($out, &$store, &$buffer, &$ending, &$closed) {
+ // stream is already closed, make sure to close output stream
+ if (!$stream->isWritable()) {
+ $out->close();
+ return $stream;
+ }
+
+ // resolves but output is already closed, make sure to close stream silently
+ if ($closed) {
+ $stream->close();
+ return $stream;
+ }
+
+ // forward drain events for back pressure
+ $stream->on('drain', function () use ($out) {
+ $out->emit('drain', array($out));
+ });
+
+ // error events cancel output stream
+ $stream->on('error', function ($error) use ($out) {
+ $out->emit('error', array($error, $out));
+ $out->close();
+ });
+
+ // close both streams once either side closes
+ $stream->on('close', array($out, 'close'));
+ $out->on('close', array($stream, 'close'));
+
+ if ($buffer) {
+ // flush buffer to stream and check if its buffer is not exceeded
+ $drained = true;
+ foreach ($buffer as $chunk) {
+ if (!$stream->write($chunk)) {
+ $drained = false;
+ }
+ }
+ $buffer = array();
+
+ if ($drained) {
+ // signal drain event, because the output stream previous signalled a full buffer
+ $out->emit('drain', array($out));
+ }
+ }
+
+ if ($ending) {
+ $stream->end();
+ } else {
+ $store = $stream;
+ }
+
+ return $stream;
+ },
+ function ($e) use ($out, &$closed) {
+ if (!$closed) {
+ $out->emit('error', array($e, $out));
+ $out->close();
+ }
+ }
+ );
+ }
+
+ public function write($data)
+ {
+ if ($this->ending) {
+ return false;
+ }
+
+ // forward to inner stream if possible
+ if ($this->stream !== null) {
+ return $this->stream->write($data);
+ }
+
+ // append to buffer and signal the buffer is full
+ $this->buffer[] = $data;
+ return false;
+ }
+
+ public function end($data = null)
+ {
+ if ($this->ending) {
+ return;
+ }
+
+ $this->ending = true;
+
+ // forward to inner stream if possible
+ if ($this->stream !== null) {
+ return $this->stream->end($data);
+ }
+
+ // append to buffer
+ if ($data !== null) {
+ $this->buffer[] = $data;
+ }
+ }
+
+ public function isWritable()
+ {
+ return !$this->ending;
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->buffer = array();
+ $this->ending = true;
+ $this->closed = true;
+
+ // try to cancel promise once the stream closes
+ if ($this->promise instanceof CancellablePromiseInterface) {
+ $this->promise->cancel();
+ }
+ $this->promise = $this->stream = null;
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/vendor/react/promise-stream/src/functions.php b/vendor/react/promise-stream/src/functions.php
new file mode 100644
index 0000000..da66de8
--- /dev/null
+++ b/vendor/react/promise-stream/src/functions.php
@@ -0,0 +1,370 @@
+<?php
+
+namespace React\Promise\Stream;
+
+use Evenement\EventEmitterInterface;
+use React\Promise;
+use React\Promise\PromiseInterface;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * Create a `Promise` which will be fulfilled with the stream data buffer.
+ *
+ * ```php
+ * $stream = accessSomeJsonStream();
+ *
+ * React\Promise\Stream\buffer($stream)->then(function (string $contents) {
+ * var_dump(json_decode($contents));
+ * });
+ * ```
+ *
+ * The promise will be fulfilled with a `string` of all data chunks concatenated once the stream closes.
+ *
+ * The promise will be fulfilled with an empty `string` if the stream is already closed.
+ *
+ * The promise will be rejected with a `RuntimeException` if the stream emits an error.
+ *
+ * The promise will be rejected with a `RuntimeException` if it is cancelled.
+ *
+ * The optional `$maxLength` argument defaults to no limit. In case the maximum
+ * length is given and the stream emits more data before the end, the promise
+ * will be rejected with an `OverflowException`.
+ *
+ * ```php
+ * $stream = accessSomeToLargeStream();
+ *
+ * React\Promise\Stream\buffer($stream, 1024)->then(function ($contents) {
+ * var_dump(json_decode($contents));
+ * }, function ($error) {
+ * // Reaching here when the stream buffer goes above the max size,
+ * // in this example that is 1024 bytes,
+ * // or when the stream emits an error.
+ * });
+ * ```
+ *
+ * @param ReadableStreamInterface<string> $stream
+ * @param ?int $maxLength Maximum number of bytes to buffer or null for unlimited.
+ * @return PromiseInterface<string,\RuntimeException>
+ */
+function buffer(ReadableStreamInterface $stream, $maxLength = null)
+{
+ // stream already ended => resolve with empty buffer
+ if (!$stream->isReadable()) {
+ return Promise\resolve('');
+ }
+
+ $buffer = '';
+
+ $promise = new Promise\Promise(function ($resolve, $reject) use ($stream, $maxLength, &$buffer, &$bufferer) {
+ $bufferer = function ($data) use (&$buffer, $reject, $maxLength) {
+ $buffer .= $data;
+
+ if ($maxLength !== null && isset($buffer[$maxLength])) {
+ $reject(new \OverflowException('Buffer exceeded maximum length'));
+ }
+ };
+
+ $stream->on('data', $bufferer);
+
+ $stream->on('error', function (\Exception $e) use ($reject) {
+ $reject(new \RuntimeException(
+ 'An error occured on the underlying stream while buffering: ' . $e->getMessage(),
+ $e->getCode(),
+ $e
+ ));
+ });
+
+ $stream->on('close', function () use ($resolve, &$buffer) {
+ $resolve($buffer);
+ });
+ }, function ($_, $reject) {
+ $reject(new \RuntimeException('Cancelled buffering'));
+ });
+
+ return $promise->then(null, function (\Exception $error) use (&$buffer, $bufferer, $stream) {
+ // promise rejected => clear buffer and buffering
+ $buffer = '';
+ $stream->removeListener('data', $bufferer);
+
+ throw $error;
+ });
+}
+
+/**
+ * Create a `Promise` which will be fulfilled once the given event triggers for the first time.
+ *
+ * ```php
+ * $stream = accessSomeJsonStream();
+ *
+ * React\Promise\Stream\first($stream)->then(function (string $chunk) {
+ * echo 'The first chunk arrived: ' . $chunk;
+ * });
+ * ```
+ *
+ * The promise will be fulfilled with a `mixed` value of whatever the first event
+ * emitted or `null` if the event does not pass any data.
+ * If you do not pass a custom event name, then it will wait for the first "data"
+ * event.
+ * For common streams of type `ReadableStreamInterface<string>`, this means it will be
+ * fulfilled with a `string` containing the first data chunk.
+ *
+ * The promise will be rejected with a `RuntimeException` if the stream emits an error
+ * – unless you're waiting for the "error" event, in which case it will be fulfilled.
+ *
+ * The promise will be rejected with a `RuntimeException` once the stream closes
+ * – unless you're waiting for the "close" event, in which case it will be fulfilled.
+ *
+ * The promise will be rejected with a `RuntimeException` if the stream is already closed.
+ *
+ * The promise will be rejected with a `RuntimeException` if it is cancelled.
+ *
+ * @param ReadableStreamInterface|WritableStreamInterface $stream
+ * @param string $event
+ * @return PromiseInterface<mixed,\RuntimeException>
+ */
+function first(EventEmitterInterface $stream, $event = 'data')
+{
+ if ($stream instanceof ReadableStreamInterface) {
+ // readable or duplex stream not readable => already closed
+ // a half-open duplex stream is considered closed if its readable side is closed
+ if (!$stream->isReadable()) {
+ return Promise\reject(new \RuntimeException('Stream already closed'));
+ }
+ } elseif ($stream instanceof WritableStreamInterface) {
+ // writable-only stream (not duplex) not writable => already closed
+ if (!$stream->isWritable()) {
+ return Promise\reject(new \RuntimeException('Stream already closed'));
+ }
+ }
+
+ return new Promise\Promise(function ($resolve, $reject) use ($stream, $event, &$listener) {
+ $listener = function ($data = null) use ($stream, $event, &$listener, $resolve) {
+ $stream->removeListener($event, $listener);
+ $resolve($data);
+ };
+ $stream->on($event, $listener);
+
+ if ($event !== 'error') {
+ $stream->on('error', function (\Exception $e) use ($stream, $event, $listener, $reject) {
+ $stream->removeListener($event, $listener);
+ $reject(new \RuntimeException(
+ 'An error occured on the underlying stream while waiting for event: ' . $e->getMessage(),
+ $e->getCode(),
+ $e
+ ));
+ });
+ }
+
+ $stream->on('close', function () use ($stream, $event, $listener, $reject) {
+ $stream->removeListener($event, $listener);
+ $reject(new \RuntimeException('Stream closed'));
+ });
+ }, function ($_, $reject) use ($stream, $event, &$listener) {
+ $stream->removeListener($event, $listener);
+ $reject(new \RuntimeException('Operation cancelled'));
+ });
+}
+
+/**
+ * Create a `Promise` which will be fulfilled with an array of all the event data.
+ *
+ * ```php
+ * $stream = accessSomeJsonStream();
+ *
+ * React\Promise\Stream\all($stream)->then(function (array $chunks) {
+ * echo 'The stream consists of ' . count($chunks) . ' chunk(s)';
+ * });
+ * ```
+ *
+ * The promise will be fulfilled with an `array` once the stream closes. The array
+ * will contain whatever all events emitted or `null` values if the events do not pass any data.
+ * If you do not pass a custom event name, then it will wait for all the "data"
+ * events.
+ * For common streams of type `ReadableStreamInterface<string>`, this means it will be
+ * fulfilled with a `string[]` array containing all the data chunk.
+ *
+ * The promise will be fulfilled with an empty `array` if the stream is already closed.
+ *
+ * The promise will be rejected with a `RuntimeException` if the stream emits an error.
+ *
+ * The promise will be rejected with a `RuntimeException` if it is cancelled.
+ *
+ * @param ReadableStreamInterface|WritableStreamInterface $stream
+ * @param string $event
+ * @return PromiseInterface<array,\RuntimeException>
+ */
+function all(EventEmitterInterface $stream, $event = 'data')
+{
+ // stream already ended => resolve with empty buffer
+ if ($stream instanceof ReadableStreamInterface) {
+ // readable or duplex stream not readable => already closed
+ // a half-open duplex stream is considered closed if its readable side is closed
+ if (!$stream->isReadable()) {
+ return Promise\resolve(array());
+ }
+ } elseif ($stream instanceof WritableStreamInterface) {
+ // writable-only stream (not duplex) not writable => already closed
+ if (!$stream->isWritable()) {
+ return Promise\resolve(array());
+ }
+ }
+
+ $buffer = array();
+ $bufferer = function ($data = null) use (&$buffer) {
+ $buffer []= $data;
+ };
+ $stream->on($event, $bufferer);
+
+ $promise = new Promise\Promise(function ($resolve, $reject) use ($stream, &$buffer) {
+ $stream->on('error', function (\Exception $e) use ($reject) {
+ $reject(new \RuntimeException(
+ 'An error occured on the underlying stream while buffering: ' . $e->getMessage(),
+ $e->getCode(),
+ $e
+ ));
+ });
+
+ $stream->on('close', function () use ($resolve, &$buffer) {
+ $resolve($buffer);
+ });
+ }, function ($_, $reject) {
+ $reject(new \RuntimeException('Cancelled buffering'));
+ });
+
+ return $promise->then(null, function ($error) use (&$buffer, $bufferer, $stream, $event) {
+ // promise rejected => clear buffer and buffering
+ $buffer = array();
+ $stream->removeListener($event, $bufferer);
+
+ throw $error;
+ });
+}
+
+/**
+ * Unwrap a `Promise` which will be fulfilled with a `ReadableStreamInterface<T>`.
+ *
+ * This function returns a readable stream instance (implementing `ReadableStreamInterface<T>`)
+ * right away which acts as a proxy for the future promise resolution.
+ * Once the given Promise will be fulfilled with a `ReadableStreamInterface<T>`, its
+ * data will be piped to the output stream.
+ *
+ * ```php
+ * //$promise = someFunctionWhichResolvesWithAStream();
+ * $promise = startDownloadStream($uri);
+ *
+ * $stream = React\Promise\Stream\unwrapReadable($promise);
+ *
+ * $stream->on('data', function (string $data) {
+ * echo $data;
+ * });
+ *
+ * $stream->on('end', function () {
+ * echo 'DONE';
+ * });
+ * ```
+ *
+ * If the given promise is either rejected or fulfilled with anything but an
+ * instance of `ReadableStreamInterface`, then the output stream will emit
+ * an `error` event and close:
+ *
+ * ```php
+ * $promise = startDownloadStream($invalidUri);
+ *
+ * $stream = React\Promise\Stream\unwrapReadable($promise);
+ *
+ * $stream->on('error', function (Exception $error) {
+ * echo 'Error: ' . $error->getMessage();
+ * });
+ * ```
+ *
+ * The given `$promise` SHOULD be pending, i.e. it SHOULD NOT be fulfilled or rejected
+ * at the time of invoking this function.
+ * If the given promise is already settled and does not fulfill with an instance of
+ * `ReadableStreamInterface`, then you will not be able to receive the `error` event.
+ *
+ * You can `close()` the resulting stream at any time, which will either try to
+ * `cancel()` the pending promise or try to `close()` the underlying stream.
+ *
+ * ```php
+ * $promise = startDownloadStream($uri);
+ *
+ * $stream = React\Promise\Stream\unwrapReadable($promise);
+ *
+ * $loop->addTimer(2.0, function () use ($stream) {
+ * $stream->close();
+ * });
+ * ```
+ *
+ * @param PromiseInterface<ReadableStreamInterface<T>,\Exception> $promise
+ * @return ReadableStreamInterface<T>
+ */
+function unwrapReadable(PromiseInterface $promise)
+{
+ return new UnwrapReadableStream($promise);
+}
+
+/**
+ * unwrap a `Promise` which will be fulfilled with a `WritableStreamInterface<T>`.
+ *
+ * This function returns a writable stream instance (implementing `WritableStreamInterface<T>`)
+ * right away which acts as a proxy for the future promise resolution.
+ * Any writes to this instance will be buffered in memory for when the promise will
+ * be fulfilled.
+ * Once the given Promise will be fulfilled with a `WritableStreamInterface<T>`, any
+ * data you have written to the proxy will be forwarded transparently to the inner
+ * stream.
+ *
+ * ```php
+ * //$promise = someFunctionWhichResolvesWithAStream();
+ * $promise = startUploadStream($uri);
+ *
+ * $stream = React\Promise\Stream\unwrapWritable($promise);
+ *
+ * $stream->write('hello');
+ * $stream->end('world');
+ *
+ * $stream->on('close', function () {
+ * echo 'DONE';
+ * });
+ * ```
+ *
+ * If the given promise is either rejected or fulfilled with anything but an
+ * instance of `WritableStreamInterface`, then the output stream will emit
+ * an `error` event and close:
+ *
+ * ```php
+ * $promise = startUploadStream($invalidUri);
+ *
+ * $stream = React\Promise\Stream\unwrapWritable($promise);
+ *
+ * $stream->on('error', function (Exception $error) {
+ * echo 'Error: ' . $error->getMessage();
+ * });
+ * ```
+ *
+ * The given `$promise` SHOULD be pending, i.e. it SHOULD NOT be fulfilled or rejected
+ * at the time of invoking this function.
+ * If the given promise is already settled and does not fulfill with an instance of
+ * `WritableStreamInterface`, then you will not be able to receive the `error` event.
+ *
+ * You can `close()` the resulting stream at any time, which will either try to
+ * `cancel()` the pending promise or try to `close()` the underlying stream.
+ *
+ * ```php
+ * $promise = startUploadStream($uri);
+ *
+ * $stream = React\Promise\Stream\unwrapWritable($promise);
+ *
+ * $loop->addTimer(2.0, function () use ($stream) {
+ * $stream->close();
+ * });
+ * ```
+ *
+ * @param PromiseInterface<WritableStreamInterface<T>,\Exception> $promise
+ * @return WritableStreamInterface<T>
+ */
+function unwrapWritable(PromiseInterface $promise)
+{
+ return new UnwrapWritableStream($promise);
+}
diff --git a/vendor/react/promise-stream/src/functions_include.php b/vendor/react/promise-stream/src/functions_include.php
new file mode 100644
index 0000000..768a4fd
--- /dev/null
+++ b/vendor/react/promise-stream/src/functions_include.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Promise\Stream;
+
+if (!function_exists('React\Promise\Stream\buffer')) {
+ require __DIR__ . '/functions.php';
+}
diff --git a/vendor/react/promise-timer/LICENSE b/vendor/react/promise-timer/LICENSE
new file mode 100644
index 0000000..56119ec
--- /dev/null
+++ b/vendor/react/promise-timer/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden
+
+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/react/promise-timer/composer.json b/vendor/react/promise-timer/composer.json
new file mode 100644
index 0000000..7335298
--- /dev/null
+++ b/vendor/react/promise-timer/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "react/promise-timer",
+ "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
+ "keywords": ["Promise", "timeout", "timer", "event-loop", "ReactPHP", "async"],
+ "homepage": "https://github.com/reactphp/promise-timer",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "React\\Promise\\Timer\\": "src/" },
+ "files": [ "src/functions_include.php" ]
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Promise\\Timer\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.0 || ^2.7.0 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/vendor/react/promise-timer/src/TimeoutException.php b/vendor/react/promise-timer/src/TimeoutException.php
new file mode 100644
index 0000000..7f03ba0
--- /dev/null
+++ b/vendor/react/promise-timer/src/TimeoutException.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace React\Promise\Timer;
+
+use RuntimeException;
+
+class TimeoutException extends RuntimeException
+{
+ /** @var float */
+ private $timeout;
+
+ /**
+ * @param float $timeout
+ * @param string|null $message
+ * @param int|null $code
+ * @param null|\Exception|\Throwable $previous
+ */
+ public function __construct($timeout, $message = '', $code = 0, $previous = null)
+ {
+ // Preserve compatibility with our former nullable signature, but avoid invalid arguments for the parent constructor:
+ parent::__construct((string) $message, (int) $code, $previous);
+
+ $this->timeout = (float) $timeout;
+ }
+
+ /**
+ * Get the timeout value in seconds.
+ *
+ * @return float
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+}
diff --git a/vendor/react/promise-timer/src/functions.php b/vendor/react/promise-timer/src/functions.php
new file mode 100644
index 0000000..b72bf36
--- /dev/null
+++ b/vendor/react/promise-timer/src/functions.php
@@ -0,0 +1,330 @@
+<?php
+
+namespace React\Promise\Timer;
+
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise\Promise;
+use React\Promise\PromiseInterface;
+
+/**
+ * Cancel operations that take *too long*.
+ *
+ * You need to pass in an input `$promise` that represents a pending operation
+ * and timeout parameters. It returns a new promise with the following
+ * resolution behavior:
+ *
+ * - If the input `$promise` resolves before `$time` seconds, resolve the
+ * resulting promise with its fulfillment value.
+ *
+ * - If the input `$promise` rejects before `$time` seconds, reject the
+ * resulting promise with its rejection value.
+ *
+ * - If the input `$promise` does not settle before `$time` seconds, *cancel*
+ * the operation and reject the resulting promise with a [`TimeoutException`](#timeoutexception).
+ *
+ * Internally, the given `$time` value will be used to start a timer that will
+ * *cancel* the pending operation once it 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.
+ *
+ * If the input `$promise` is already settled, then the resulting promise will
+ * resolve or reject immediately without starting a timer at all.
+ *
+ * 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.
+ *
+ * A common use case for handling only resolved values looks like this:
+ *
+ * ```php
+ * $promise = accessSomeRemoteResource();
+ * React\Promise\Timer\timeout($promise, 10.0)->then(function ($value) {
+ * // the operation finished within 10.0 seconds
+ * });
+ * ```
+ *
+ * A more complete example could look like this:
+ *
+ * ```php
+ * $promise = accessSomeRemoteResource();
+ * React\Promise\Timer\timeout($promise, 10.0)->then(
+ * function ($value) {
+ * // the operation finished within 10.0 seconds
+ * },
+ * function ($error) {
+ * if ($error instanceof React\Promise\Timer\TimeoutException) {
+ * // the operation has failed due to a timeout
+ * } else {
+ * // the input operation has failed due to some other error
+ * }
+ * }
+ * );
+ * ```
+ *
+ * Or if you're using [react/promise v3](https://github.com/reactphp/promise):
+ *
+ * ```php
+ * React\Promise\Timer\timeout($promise, 10.0)->then(function ($value) {
+ * // the operation finished within 10.0 seconds
+ * })->catch(function (React\Promise\Timer\TimeoutException $error) {
+ * // the operation has failed due to a timeout
+ * })->catch(function (Throwable $error) {
+ * // the input operation has failed due to some other error
+ * });
+ * ```
+ *
+ * As discussed above, the [`timeout()`](#timeout) function will take care of
+ * the underlying operation if it takes *too long*. In this case, you can be
+ * sure the resulting promise will always be rejected with a
+ * [`TimeoutException`](#timeoutexception). On top of this, the function will
+ * try to *cancel* the underlying operation. Responsibility for this
+ * cancellation logic is left up to the underlying operation.
+ *
+ * - A common use case involves cleaning up any resources like open network
+ * sockets or file handles or terminating external processes or timers.
+ *
+ * - If the given input `$promise` does not support cancellation, then this is a
+ * NO-OP. This means that while the resulting promise will still be rejected,
+ * the underlying input `$promise` may still be pending and can hence continue
+ * consuming resources
+ *
+ * On top of this, the returned promise is implemented in such a way that it can
+ * be cancelled when it is still pending. Cancelling a pending promise will
+ * cancel the underlying operation. As discussed above, responsibility for this
+ * cancellation logic is left up to the underlying operation.
+ *
+ * ```php
+ * $promise = accessSomeRemoteResource();
+ * $timeout = React\Promise\Timer\timeout($promise, 10.0);
+ *
+ * $timeout->cancel();
+ * ```
+ *
+ * For more details on the promise cancellation, please refer to the
+ * [Promise documentation](https://github.com/reactphp/promise#cancellablepromiseinterface).
+ *
+ * If you want to wait for multiple promises to resolve, you can use the normal
+ * promise primitives like this:
+ *
+ * ```php
+ * $promises = array(
+ * accessSomeRemoteResource(),
+ * accessSomeRemoteResource(),
+ * accessSomeRemoteResource()
+ * );
+ *
+ * $promise = React\Promise\all($promises);
+ *
+ * React\Promise\Timer\timeout($promise, 10)->then(function ($values) {
+ * // *all* promises resolved
+ * });
+ * ```
+ *
+ * The applies to all promise collection primitives alike, i.e. `all()`,
+ * `race()`, `any()`, `some()` etc.
+ *
+ * For more details on the promise primitives, please refer to the
+ * [Promise documentation](https://github.com/reactphp/promise#functions).
+ *
+ * @param PromiseInterface<mixed, \Throwable|mixed> $promise
+ * @param float $time
+ * @param ?LoopInterface $loop
+ * @return PromiseInterface<mixed, TimeoutException|\Throwable|mixed>
+ */
+function timeout(PromiseInterface $promise, $time, LoopInterface $loop = null)
+{
+ // cancelling this promise will only try to cancel the input promise,
+ // thus leaving responsibility to the input promise.
+ $canceller = null;
+ if (\method_exists($promise, 'cancel')) {
+ // pass promise by reference to clean reference after cancellation handler
+ // has been invoked once in order to avoid garbage references in call stack.
+ $canceller = function () use (&$promise) {
+ $promise->cancel();
+ $promise = null;
+ };
+ }
+
+ if ($loop === null) {
+ $loop = Loop::get();
+ }
+
+ return new Promise(function ($resolve, $reject) use ($loop, $time, $promise) {
+ $timer = null;
+ $promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $resolve($v);
+ }, function ($v) use (&$timer, $loop, $reject) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $reject($v);
+ });
+
+ // promise already resolved => no need to start timer
+ if ($timer === false) {
+ return;
+ }
+
+ // start timeout timer which will cancel the input promise
+ $timer = $loop->addTimer($time, function () use ($time, &$promise, $reject) {
+ $reject(new TimeoutException($time, 'Timed out after ' . $time . ' seconds'));
+
+ // try to invoke cancellation handler of input promise and then clean
+ // reference in order to avoid garbage references in call stack.
+ if (\method_exists($promise, 'cancel')) {
+ $promise->cancel();
+ }
+ $promise = null;
+ });
+ }, $canceller);
+}
+
+/**
+ * Create a new promise that resolves in `$time` seconds.
+ *
+ * ```php
+ * React\Promise\Timer\sleep(1.5)->then(function () {
+ * echo 'Thanks for waiting!' . PHP_EOL;
+ * });
+ * ```
+ *
+ * Internally, the given `$time` value will be used to start a timer that will
+ * resolve the promise once it 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.
+ *
+ * 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 a `RuntimeException` and clean up any pending timers.
+ *
+ * ```php
+ * $timer = React\Promise\Timer\sleep(2.0);
+ *
+ * $timer->cancel();
+ * ```
+ *
+ * @param float $time
+ * @param ?LoopInterface $loop
+ * @return PromiseInterface<void, \RuntimeException>
+ */
+function sleep($time, LoopInterface $loop = null)
+{
+ if ($loop === null) {
+ $loop = Loop::get();
+ }
+
+ $timer = null;
+ return new Promise(function ($resolve) use ($loop, $time, &$timer) {
+ // resolve the promise when the timer fires in $time seconds
+ $timer = $loop->addTimer($time, function () use ($resolve) {
+ $resolve(null);
+ });
+ }, function () use (&$timer, $loop) {
+ // cancelling this promise will cancel the timer, clean the reference
+ // in order to avoid garbage references in call stack and then reject.
+ $loop->cancelTimer($timer);
+ $timer = null;
+
+ throw new \RuntimeException('Timer cancelled');
+ });
+}
+
+/**
+ * [Deprecated] Create a new promise that resolves in `$time` seconds with the `$time` as the fulfillment value.
+ *
+ * ```php
+ * React\Promise\Timer\resolve(1.5)->then(function ($time) {
+ * echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL;
+ * });
+ * ```
+ *
+ * Internally, the given `$time` value will be used to start a timer that will
+ * resolve the promise once it 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.
+ *
+ * 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 a `RuntimeException` and clean up any pending timers.
+ *
+ * ```php
+ * $timer = React\Promise\Timer\resolve(2.0);
+ *
+ * $timer->cancel();
+ * ```
+ *
+ * @param float $time
+ * @param ?LoopInterface $loop
+ * @return PromiseInterface<float, \RuntimeException>
+ * @deprecated 1.8.0 See `sleep()` instead
+ * @see sleep()
+ */
+function resolve($time, LoopInterface $loop = null)
+{
+ return sleep($time, $loop)->then(function() use ($time) {
+ return $time;
+ });
+}
+
+/**
+ * [Deprecated] Create a new promise which rejects in `$time` seconds with a `TimeoutException`.
+ *
+ * ```php
+ * React\Promise\Timer\reject(2.0)->then(null, function (React\Promise\Timer\TimeoutException $e) {
+ * echo 'Rejected after ' . $e->getTimeout() . ' seconds ' . PHP_EOL;
+ * });
+ * ```
+ *
+ * Internally, the given `$time` value will be used to start a timer that will
+ * reject the promise once it 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.
+ *
+ * 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 a `RuntimeException` and clean up any pending timers.
+ *
+ * ```php
+ * $timer = React\Promise\Timer\reject(2.0);
+ *
+ * $timer->cancel();
+ * ```
+ *
+ * @param float $time
+ * @param LoopInterface $loop
+ * @return PromiseInterface<void, TimeoutException|\RuntimeException>
+ * @deprecated 1.8.0 See `sleep()` instead
+ * @see sleep()
+ */
+function reject($time, LoopInterface $loop = null)
+{
+ return sleep($time, $loop)->then(function () use ($time) {
+ throw new TimeoutException($time, 'Timer expired after ' . $time . ' seconds');
+ });
+}
diff --git a/vendor/react/promise-timer/src/functions_include.php b/vendor/react/promise-timer/src/functions_include.php
new file mode 100644
index 0000000..1d5673a
--- /dev/null
+++ b/vendor/react/promise-timer/src/functions_include.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Promise\Timer;
+
+if (!function_exists('React\\Promise\\Timer\\timeout')) {
+ require __DIR__ . '/functions.php';
+}
diff --git a/vendor/react/promise/LICENSE b/vendor/react/promise/LICENSE
new file mode 100644
index 0000000..21c1357
--- /dev/null
+++ b/vendor/react/promise/LICENSE
@@ -0,0 +1,24 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Jan Sorgalla, Christian Lück, Cees-Jan Kiewiet, Chris Boden
+
+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/react/promise/composer.json b/vendor/react/promise/composer.json
new file mode 100644
index 0000000..f933f15
--- /dev/null
+++ b/vendor/react/promise/composer.json
@@ -0,0 +1,48 @@
+{
+ "name": "react/promise",
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Promise\\": ["tests", "tests/fixtures"]
+ }
+ },
+ "keywords": [
+ "promise",
+ "promises"
+ ]
+}
diff --git a/vendor/react/promise/src/CancellablePromiseInterface.php b/vendor/react/promise/src/CancellablePromiseInterface.php
new file mode 100644
index 0000000..6b3a8c6
--- /dev/null
+++ b/vendor/react/promise/src/CancellablePromiseInterface.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace React\Promise;
+
+interface CancellablePromiseInterface extends PromiseInterface
+{
+ /**
+ * The `cancel()` method notifies the creator of the promise that there is no
+ * further interest in the results of the operation.
+ *
+ * Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
+ * a promise has no effect.
+ *
+ * @return void
+ */
+ public function cancel();
+}
diff --git a/vendor/react/promise/src/CancellationQueue.php b/vendor/react/promise/src/CancellationQueue.php
new file mode 100644
index 0000000..a381e97
--- /dev/null
+++ b/vendor/react/promise/src/CancellationQueue.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace React\Promise;
+
+class CancellationQueue
+{
+ private $started = false;
+ private $queue = [];
+
+ public function __invoke()
+ {
+ if ($this->started) {
+ return;
+ }
+
+ $this->started = true;
+ $this->drain();
+ }
+
+ public function enqueue($cancellable)
+ {
+ if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
+ return;
+ }
+
+ $length = \array_push($this->queue, $cancellable);
+
+ if ($this->started && 1 === $length) {
+ $this->drain();
+ }
+ }
+
+ private function drain()
+ {
+ for ($i = key($this->queue); isset($this->queue[$i]); $i++) {
+ $cancellable = $this->queue[$i];
+
+ $exception = null;
+
+ try {
+ $cancellable->cancel();
+ } catch (\Throwable $exception) {
+ } catch (\Exception $exception) {
+ }
+
+ unset($this->queue[$i]);
+
+ if ($exception) {
+ throw $exception;
+ }
+ }
+
+ $this->queue = [];
+ }
+}
diff --git a/vendor/react/promise/src/Deferred.php b/vendor/react/promise/src/Deferred.php
new file mode 100644
index 0000000..3ca034b
--- /dev/null
+++ b/vendor/react/promise/src/Deferred.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace React\Promise;
+
+class Deferred implements PromisorInterface
+{
+ private $promise;
+ private $resolveCallback;
+ private $rejectCallback;
+ private $notifyCallback;
+ private $canceller;
+
+ public function __construct(callable $canceller = null)
+ {
+ $this->canceller = $canceller;
+ }
+
+ public function promise()
+ {
+ if (null === $this->promise) {
+ $this->promise = new Promise(function ($resolve, $reject, $notify) {
+ $this->resolveCallback = $resolve;
+ $this->rejectCallback = $reject;
+ $this->notifyCallback = $notify;
+ }, $this->canceller);
+ $this->canceller = null;
+ }
+
+ return $this->promise;
+ }
+
+ public function resolve($value = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->resolveCallback, $value);
+ }
+
+ public function reject($reason = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->rejectCallback, $reason);
+ }
+
+ /**
+ * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
+ * @param mixed $update
+ */
+ public function notify($update = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->notifyCallback, $update);
+ }
+
+ /**
+ * @deprecated 2.2.0
+ * @see Deferred::notify()
+ */
+ public function progress($update = null)
+ {
+ $this->notify($update);
+ }
+}
diff --git a/vendor/react/promise/src/Exception/LengthException.php b/vendor/react/promise/src/Exception/LengthException.php
new file mode 100644
index 0000000..775c48d
--- /dev/null
+++ b/vendor/react/promise/src/Exception/LengthException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Promise\Exception;
+
+class LengthException extends \LengthException
+{
+}
diff --git a/vendor/react/promise/src/ExtendedPromiseInterface.php b/vendor/react/promise/src/ExtendedPromiseInterface.php
new file mode 100644
index 0000000..13b6369
--- /dev/null
+++ b/vendor/react/promise/src/ExtendedPromiseInterface.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace React\Promise;
+
+interface ExtendedPromiseInterface extends PromiseInterface
+{
+ /**
+ * Consumes the promise's ultimate value if the promise fulfills, or handles the
+ * ultimate error.
+ *
+ * It will cause a fatal error if either `$onFulfilled` or
+ * `$onRejected` throw or return a rejected promise.
+ *
+ * Since the purpose of `done()` is consumption rather than transformation,
+ * `done()` always returns `null`.
+ *
+ * @param callable|null $onFulfilled
+ * @param callable|null $onRejected
+ * @param callable|null $onProgress This argument is deprecated and should not be used anymore.
+ * @return void
+ */
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
+
+ /**
+ * Registers a rejection handler for promise. It is a shortcut for:
+ *
+ * ```php
+ * $promise->then(null, $onRejected);
+ * ```
+ *
+ * Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
+ * only specific errors.
+ *
+ * @param callable $onRejected
+ * @return ExtendedPromiseInterface
+ */
+ public function otherwise(callable $onRejected);
+
+ /**
+ * Allows you to execute "cleanup" type tasks in a promise chain.
+ *
+ * It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
+ * when the promise is either fulfilled or rejected.
+ *
+ * * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
+ * `$newPromise` will fulfill with the same value as `$promise`.
+ * * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
+ * rejected promise, `$newPromise` will reject with the thrown exception or
+ * rejected promise's reason.
+ * * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
+ * `$newPromise` will reject with the same reason as `$promise`.
+ * * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
+ * rejected promise, `$newPromise` will reject with the thrown exception or
+ * rejected promise's reason.
+ *
+ * `always()` behaves similarly to the synchronous finally statement. When combined
+ * with `otherwise()`, `always()` allows you to write code that is similar to the familiar
+ * synchronous catch/finally pair.
+ *
+ * Consider the following synchronous code:
+ *
+ * ```php
+ * try {
+ * return doSomething();
+ * } catch(\Exception $e) {
+ * return handleError($e);
+ * } finally {
+ * cleanup();
+ * }
+ * ```
+ *
+ * Similar asynchronous code (with `doSomething()` that returns a promise) can be
+ * written:
+ *
+ * ```php
+ * return doSomething()
+ * ->otherwise('handleError')
+ * ->always('cleanup');
+ * ```
+ *
+ * @param callable $onFulfilledOrRejected
+ * @return ExtendedPromiseInterface
+ */
+ public function always(callable $onFulfilledOrRejected);
+
+ /**
+ * Registers a handler for progress updates from promise. It is a shortcut for:
+ *
+ * ```php
+ * $promise->then(null, null, $onProgress);
+ * ```
+ *
+ * @param callable $onProgress
+ * @return ExtendedPromiseInterface
+ * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
+ */
+ public function progress(callable $onProgress);
+}
diff --git a/vendor/react/promise/src/FulfilledPromise.php b/vendor/react/promise/src/FulfilledPromise.php
new file mode 100644
index 0000000..1472752
--- /dev/null
+++ b/vendor/react/promise/src/FulfilledPromise.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace React\Promise;
+
+/**
+ * @deprecated 2.8.0 External usage of FulfilledPromise is deprecated, use `resolve()` instead.
+ */
+class FulfilledPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
+{
+ private $value;
+
+ public function __construct($value = null)
+ {
+ if ($value instanceof PromiseInterface) {
+ throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.');
+ }
+
+ $this->value = $value;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onFulfilled) {
+ return $this;
+ }
+
+ try {
+ return resolve($onFulfilled($this->value));
+ } catch (\Throwable $exception) {
+ return new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ return new RejectedPromise($exception);
+ }
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onFulfilled) {
+ return;
+ }
+
+ $result = $onFulfilled($this->value);
+
+ if ($result instanceof ExtendedPromiseInterface) {
+ $result->done();
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this;
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(function ($value) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+ return $value;
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this;
+ }
+
+ public function cancel()
+ {
+ }
+}
diff --git a/vendor/react/promise/src/LazyPromise.php b/vendor/react/promise/src/LazyPromise.php
new file mode 100644
index 0000000..bbe9293
--- /dev/null
+++ b/vendor/react/promise/src/LazyPromise.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace React\Promise;
+
+/**
+ * @deprecated 2.8.0 LazyPromise is deprecated and should not be used anymore.
+ */
+class LazyPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
+{
+ private $factory;
+ private $promise;
+
+ public function __construct(callable $factory)
+ {
+ $this->factory = $factory;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return $this->promise()->done($onFulfilled, $onRejected, $onProgress);
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->promise()->otherwise($onRejected);
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->promise()->always($onFulfilledOrRejected);
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this->promise()->progress($onProgress);
+ }
+
+ public function cancel()
+ {
+ return $this->promise()->cancel();
+ }
+
+ /**
+ * @internal
+ * @see Promise::settle()
+ */
+ public function promise()
+ {
+ if (null === $this->promise) {
+ try {
+ $this->promise = resolve(\call_user_func($this->factory));
+ } catch (\Throwable $exception) {
+ $this->promise = new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ $this->promise = new RejectedPromise($exception);
+ }
+ }
+
+ return $this->promise;
+ }
+}
diff --git a/vendor/react/promise/src/Promise.php b/vendor/react/promise/src/Promise.php
new file mode 100644
index 0000000..33759e6
--- /dev/null
+++ b/vendor/react/promise/src/Promise.php
@@ -0,0 +1,256 @@
+<?php
+
+namespace React\Promise;
+
+class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
+{
+ private $canceller;
+ private $result;
+
+ private $handlers = [];
+ private $progressHandlers = [];
+
+ private $requiredCancelRequests = 0;
+ private $cancelRequests = 0;
+
+ public function __construct(callable $resolver, callable $canceller = null)
+ {
+ $this->canceller = $canceller;
+
+ // Explicitly overwrite arguments with null values before invoking
+ // resolver function. This ensure that these arguments do not show up
+ // in the stack trace in PHP 7+ only.
+ $cb = $resolver;
+ $resolver = $canceller = null;
+ $this->call($cb);
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null !== $this->result) {
+ return $this->result->then($onFulfilled, $onRejected, $onProgress);
+ }
+
+ if (null === $this->canceller) {
+ return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
+ }
+
+ // This promise has a canceller, so we create a new child promise which
+ // has a canceller that invokes the parent canceller if all other
+ // followers are also cancelled. We keep a reference to this promise
+ // instance for the static canceller function and clear this to avoid
+ // keeping a cyclic reference between parent and follower.
+ $parent = $this;
+ ++$parent->requiredCancelRequests;
+
+ return new static(
+ $this->resolver($onFulfilled, $onRejected, $onProgress),
+ static function () use (&$parent) {
+ if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
+ $parent->cancel();
+ }
+
+ $parent = null;
+ }
+ );
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null !== $this->result) {
+ return $this->result->done($onFulfilled, $onRejected, $onProgress);
+ }
+
+ $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
+ $promise
+ ->done($onFulfilled, $onRejected);
+ };
+
+ if ($onProgress) {
+ $this->progressHandlers[] = $onProgress;
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, static function ($reason) use ($onRejected) {
+ if (!_checkTypehint($onRejected, $reason)) {
+ return new RejectedPromise($reason);
+ }
+
+ return $onRejected($reason);
+ });
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(static function ($value) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+ return $value;
+ });
+ }, static function ($reason) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
+ return new RejectedPromise($reason);
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this->then(null, null, $onProgress);
+ }
+
+ public function cancel()
+ {
+ if (null === $this->canceller || null !== $this->result) {
+ return;
+ }
+
+ $canceller = $this->canceller;
+ $this->canceller = null;
+
+ $this->call($canceller);
+ }
+
+ private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
+ if ($onProgress) {
+ $progressHandler = static function ($update) use ($notify, $onProgress) {
+ try {
+ $notify($onProgress($update));
+ } catch (\Throwable $e) {
+ $notify($e);
+ } catch (\Exception $e) {
+ $notify($e);
+ }
+ };
+ } else {
+ $progressHandler = $notify;
+ }
+
+ $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
+ $promise
+ ->then($onFulfilled, $onRejected)
+ ->done($resolve, $reject, $progressHandler);
+ };
+
+ $this->progressHandlers[] = $progressHandler;
+ };
+ }
+
+ private function reject($reason = null)
+ {
+ if (null !== $this->result) {
+ return;
+ }
+
+ $this->settle(reject($reason));
+ }
+
+ private function settle(ExtendedPromiseInterface $promise)
+ {
+ $promise = $this->unwrap($promise);
+
+ if ($promise === $this) {
+ $promise = new RejectedPromise(
+ new \LogicException('Cannot resolve a promise with itself.')
+ );
+ }
+
+ $handlers = $this->handlers;
+
+ $this->progressHandlers = $this->handlers = [];
+ $this->result = $promise;
+ $this->canceller = null;
+
+ foreach ($handlers as $handler) {
+ $handler($promise);
+ }
+ }
+
+ private function unwrap($promise)
+ {
+ $promise = $this->extract($promise);
+
+ while ($promise instanceof self && null !== $promise->result) {
+ $promise = $this->extract($promise->result);
+ }
+
+ return $promise;
+ }
+
+ private function extract($promise)
+ {
+ if ($promise instanceof LazyPromise) {
+ $promise = $promise->promise();
+ }
+
+ return $promise;
+ }
+
+ private function call(callable $cb)
+ {
+ // Explicitly overwrite argument with null value. This ensure that this
+ // argument does not show up in the stack trace in PHP 7+ only.
+ $callback = $cb;
+ $cb = null;
+
+ // Use reflection to inspect number of arguments expected by this callback.
+ // We did some careful benchmarking here: Using reflection to avoid unneeded
+ // function arguments is actually faster than blindly passing them.
+ // Also, this helps avoiding unnecessary function arguments in the call stack
+ // if the callback creates an Exception (creating garbage cycles).
+ if (\is_array($callback)) {
+ $ref = new \ReflectionMethod($callback[0], $callback[1]);
+ } elseif (\is_object($callback) && !$callback instanceof \Closure) {
+ $ref = new \ReflectionMethod($callback, '__invoke');
+ } else {
+ $ref = new \ReflectionFunction($callback);
+ }
+ $args = $ref->getNumberOfParameters();
+
+ try {
+ if ($args === 0) {
+ $callback();
+ } else {
+ // Keep references to this promise instance for the static resolve/reject functions.
+ // By using static callbacks that are not bound to this instance
+ // and passing the target promise instance by reference, we can
+ // still execute its resolving logic and still clear this
+ // reference when settling the promise. This helps avoiding
+ // garbage cycles if any callback creates an Exception.
+ // These assumptions are covered by the test suite, so if you ever feel like
+ // refactoring this, go ahead, any alternative suggestions are welcome!
+ $target =& $this;
+ $progressHandlers =& $this->progressHandlers;
+
+ $callback(
+ static function ($value = null) use (&$target) {
+ if ($target !== null) {
+ $target->settle(resolve($value));
+ $target = null;
+ }
+ },
+ static function ($reason = null) use (&$target) {
+ if ($target !== null) {
+ $target->reject($reason);
+ $target = null;
+ }
+ },
+ static function ($update = null) use (&$progressHandlers) {
+ foreach ($progressHandlers as $handler) {
+ $handler($update);
+ }
+ }
+ );
+ }
+ } catch (\Throwable $e) {
+ $target = null;
+ $this->reject($e);
+ } catch (\Exception $e) {
+ $target = null;
+ $this->reject($e);
+ }
+ }
+}
diff --git a/vendor/react/promise/src/PromiseInterface.php b/vendor/react/promise/src/PromiseInterface.php
new file mode 100644
index 0000000..edcb007
--- /dev/null
+++ b/vendor/react/promise/src/PromiseInterface.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace React\Promise;
+
+interface PromiseInterface
+{
+ /**
+ * Transforms a promise's value by applying a function to the promise's fulfillment
+ * or rejection value. Returns a new promise for the transformed result.
+ *
+ * The `then()` method registers new fulfilled and rejection handlers with a promise
+ * (all parameters are optional):
+ *
+ * * `$onFulfilled` will be invoked once the promise is fulfilled and passed
+ * the result as the first argument.
+ * * `$onRejected` will be invoked once the promise is rejected and passed the
+ * reason as the first argument.
+ * * `$onProgress` (deprecated) will be invoked whenever the producer of the promise
+ * triggers progress notifications and passed a single argument (whatever it
+ * wants) to indicate progress.
+ *
+ * It returns a new promise that will fulfill with the return value of either
+ * `$onFulfilled` or `$onRejected`, whichever is called, or will reject with
+ * the thrown exception if either throws.
+ *
+ * A promise makes the following guarantees about handlers registered in
+ * the same call to `then()`:
+ *
+ * 1. Only one of `$onFulfilled` or `$onRejected` will be called,
+ * never both.
+ * 2. `$onFulfilled` and `$onRejected` will never be called more
+ * than once.
+ * 3. `$onProgress` (deprecated) may be called multiple times.
+ *
+ * @param callable|null $onFulfilled
+ * @param callable|null $onRejected
+ * @param callable|null $onProgress This argument is deprecated and should not be used anymore.
+ * @return PromiseInterface
+ */
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
+}
diff --git a/vendor/react/promise/src/PromisorInterface.php b/vendor/react/promise/src/PromisorInterface.php
new file mode 100644
index 0000000..bd64400
--- /dev/null
+++ b/vendor/react/promise/src/PromisorInterface.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace React\Promise;
+
+interface PromisorInterface
+{
+ /**
+ * Returns the promise of the deferred.
+ *
+ * @return PromiseInterface
+ */
+ public function promise();
+}
diff --git a/vendor/react/promise/src/RejectedPromise.php b/vendor/react/promise/src/RejectedPromise.php
new file mode 100644
index 0000000..09cd4ab
--- /dev/null
+++ b/vendor/react/promise/src/RejectedPromise.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace React\Promise;
+
+/**
+ * @deprecated 2.8.0 External usage of RejectedPromise is deprecated, use `reject()` instead.
+ */
+class RejectedPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
+{
+ private $reason;
+
+ public function __construct($reason = null)
+ {
+ if ($reason instanceof PromiseInterface) {
+ throw new \InvalidArgumentException('You cannot create React\Promise\RejectedPromise with a promise. Use React\Promise\reject($promiseOrValue) instead.');
+ }
+
+ $this->reason = $reason;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onRejected) {
+ return $this;
+ }
+
+ try {
+ return resolve($onRejected($this->reason));
+ } catch (\Throwable $exception) {
+ return new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ return new RejectedPromise($exception);
+ }
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onRejected) {
+ throw UnhandledRejectionException::resolve($this->reason);
+ }
+
+ $result = $onRejected($this->reason);
+
+ if ($result instanceof self) {
+ throw UnhandledRejectionException::resolve($result->reason);
+ }
+
+ if ($result instanceof ExtendedPromiseInterface) {
+ $result->done();
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ if (!_checkTypehint($onRejected, $this->reason)) {
+ return $this;
+ }
+
+ return $this->then(null, $onRejected);
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(null, function ($reason) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
+ return new RejectedPromise($reason);
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this;
+ }
+
+ public function cancel()
+ {
+ }
+}
diff --git a/vendor/react/promise/src/UnhandledRejectionException.php b/vendor/react/promise/src/UnhandledRejectionException.php
new file mode 100644
index 0000000..e7fe2f7
--- /dev/null
+++ b/vendor/react/promise/src/UnhandledRejectionException.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace React\Promise;
+
+class UnhandledRejectionException extends \RuntimeException
+{
+ private $reason;
+
+ public static function resolve($reason)
+ {
+ if ($reason instanceof \Exception || $reason instanceof \Throwable) {
+ return $reason;
+ }
+
+ return new static($reason);
+ }
+
+ public function __construct($reason)
+ {
+ $this->reason = $reason;
+
+ $message = \sprintf('Unhandled Rejection: %s', \json_encode($reason));
+
+ parent::__construct($message, 0);
+ }
+
+ public function getReason()
+ {
+ return $this->reason;
+ }
+}
diff --git a/vendor/react/promise/src/functions.php b/vendor/react/promise/src/functions.php
new file mode 100644
index 0000000..429f0e7
--- /dev/null
+++ b/vendor/react/promise/src/functions.php
@@ -0,0 +1,407 @@
+<?php
+
+namespace React\Promise;
+
+/**
+ * Creates a promise for the supplied `$promiseOrValue`.
+ *
+ * If `$promiseOrValue` is a value, it will be the resolution value of the
+ * returned promise.
+ *
+ * If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
+ * a trusted promise that follows the state of the thenable is returned.
+ *
+ * If `$promiseOrValue` is a promise, it will be returned as is.
+ *
+ * @param mixed $promiseOrValue
+ * @return PromiseInterface
+ */
+function resolve($promiseOrValue = null)
+{
+ if ($promiseOrValue instanceof ExtendedPromiseInterface) {
+ return $promiseOrValue;
+ }
+
+ // Check is_object() first to avoid method_exists() triggering
+ // class autoloaders if $promiseOrValue is a string.
+ if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) {
+ $canceller = null;
+
+ if (\method_exists($promiseOrValue, 'cancel')) {
+ $canceller = [$promiseOrValue, 'cancel'];
+ }
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promiseOrValue) {
+ $promiseOrValue->then($resolve, $reject, $notify);
+ }, $canceller);
+ }
+
+ return new FulfilledPromise($promiseOrValue);
+}
+
+/**
+ * Creates a rejected promise for the supplied `$promiseOrValue`.
+ *
+ * If `$promiseOrValue` is a value, it will be the rejection value of the
+ * returned promise.
+ *
+ * If `$promiseOrValue` is a promise, its completion value will be the rejected
+ * value of the returned promise.
+ *
+ * This can be useful in situations where you need to reject a promise without
+ * throwing an exception. For example, it allows you to propagate a rejection with
+ * the value of another promise.
+ *
+ * @param mixed $promiseOrValue
+ * @return PromiseInterface
+ */
+function reject($promiseOrValue = null)
+{
+ if ($promiseOrValue instanceof PromiseInterface) {
+ return resolve($promiseOrValue)->then(function ($value) {
+ return new RejectedPromise($value);
+ });
+ }
+
+ return new RejectedPromise($promiseOrValue);
+}
+
+/**
+ * Returns a promise that will resolve only once all the items in
+ * `$promisesOrValues` have resolved. The resolution value of the returned promise
+ * will be an array containing the resolution values of each of the items in
+ * `$promisesOrValues`.
+ *
+ * @param array $promisesOrValues
+ * @return PromiseInterface
+ */
+function all($promisesOrValues)
+{
+ return map($promisesOrValues, function ($val) {
+ return $val;
+ });
+}
+
+/**
+ * Initiates a competitive race that allows one winner. Returns a promise which is
+ * resolved in the same way the first settled promise resolves.
+ *
+ * The returned promise will become **infinitely pending** if `$promisesOrValues`
+ * contains 0 items.
+ *
+ * @param array $promisesOrValues
+ * @return PromiseInterface
+ */
+function race($promisesOrValues)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($cancellationQueue, $resolve, $reject, $notify) {
+ if (!is_array($array) || !$array) {
+ $resolve();
+ return;
+ }
+
+ foreach ($array as $promiseOrValue) {
+ $cancellationQueue->enqueue($promiseOrValue);
+
+ resolve($promiseOrValue)
+ ->done($resolve, $reject, $notify);
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+/**
+ * Returns a promise that will resolve when any one of the items in
+ * `$promisesOrValues` resolves. The resolution value of the returned promise
+ * will be the resolution value of the triggering item.
+ *
+ * The returned promise will only reject if *all* items in `$promisesOrValues` are
+ * rejected. The rejection value will be an array of all rejection reasons.
+ *
+ * The returned promise will also reject with a `React\Promise\Exception\LengthException`
+ * if `$promisesOrValues` contains 0 items.
+ *
+ * @param array $promisesOrValues
+ * @return PromiseInterface
+ */
+function any($promisesOrValues)
+{
+ return some($promisesOrValues, 1)
+ ->then(function ($val) {
+ return \array_shift($val);
+ });
+}
+
+/**
+ * Returns a promise that will resolve when `$howMany` of the supplied items in
+ * `$promisesOrValues` resolve. The resolution value of the returned promise
+ * will be an array of length `$howMany` containing the resolution values of the
+ * triggering items.
+ *
+ * The returned promise will reject if it becomes impossible for `$howMany` items
+ * to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items
+ * reject). The rejection value will be an array of
+ * `(count($promisesOrValues) - $howMany) + 1` rejection reasons.
+ *
+ * The returned promise will also reject with a `React\Promise\Exception\LengthException`
+ * if `$promisesOrValues` contains less items than `$howMany`.
+ *
+ * @param array $promisesOrValues
+ * @param int $howMany
+ * @return PromiseInterface
+ */
+function some($promisesOrValues, $howMany)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $howMany, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array) || $howMany < 1) {
+ $resolve([]);
+ return;
+ }
+
+ $len = \count($array);
+
+ if ($len < $howMany) {
+ throw new Exception\LengthException(
+ \sprintf(
+ 'Input array must contain at least %d item%s but contains only %s item%s.',
+ $howMany,
+ 1 === $howMany ? '' : 's',
+ $len,
+ 1 === $len ? '' : 's'
+ )
+ );
+ }
+
+ $toResolve = $howMany;
+ $toReject = ($len - $toResolve) + 1;
+ $values = [];
+ $reasons = [];
+
+ foreach ($array as $i => $promiseOrValue) {
+ $fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) {
+ if ($toResolve < 1 || $toReject < 1) {
+ return;
+ }
+
+ $values[$i] = $val;
+
+ if (0 === --$toResolve) {
+ $resolve($values);
+ }
+ };
+
+ $rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) {
+ if ($toResolve < 1 || $toReject < 1) {
+ return;
+ }
+
+ $reasons[$i] = $reason;
+
+ if (0 === --$toReject) {
+ $reject($reasons);
+ }
+ };
+
+ $cancellationQueue->enqueue($promiseOrValue);
+
+ resolve($promiseOrValue)
+ ->done($fulfiller, $rejecter, $notify);
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+/**
+ * Traditional map function, similar to `array_map()`, but allows input to contain
+ * promises and/or values, and `$mapFunc` may return either a value or a promise.
+ *
+ * The map function receives each item as argument, where item is a fully resolved
+ * value of a promise or value in `$promisesOrValues`.
+ *
+ * @param array $promisesOrValues
+ * @param callable $mapFunc
+ * @return PromiseInterface
+ */
+function map($promisesOrValues, callable $mapFunc)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $mapFunc, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array) || !$array) {
+ $resolve([]);
+ return;
+ }
+
+ $toResolve = \count($array);
+ $values = [];
+
+ foreach ($array as $i => $promiseOrValue) {
+ $cancellationQueue->enqueue($promiseOrValue);
+ $values[$i] = null;
+
+ resolve($promiseOrValue)
+ ->then($mapFunc)
+ ->done(
+ function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
+ $values[$i] = $mapped;
+
+ if (0 === --$toResolve) {
+ $resolve($values);
+ }
+ },
+ $reject,
+ $notify
+ );
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+/**
+ * Traditional reduce function, similar to `array_reduce()`, but input may contain
+ * promises and/or values, and `$reduceFunc` may return either a value or a
+ * promise, *and* `$initialValue` may be a promise or a value for the starting
+ * value.
+ *
+ * @param array $promisesOrValues
+ * @param callable $reduceFunc
+ * @param mixed $initialValue
+ * @return PromiseInterface
+ */
+function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array)) {
+ $array = [];
+ }
+
+ $total = \count($array);
+ $i = 0;
+
+ // Wrap the supplied $reduceFunc with one that handles promises and then
+ // delegates to the supplied.
+ $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) {
+ $cancellationQueue->enqueue($val);
+
+ return $current
+ ->then(function ($c) use ($reduceFunc, $total, &$i, $val) {
+ return resolve($val)
+ ->then(function ($value) use ($reduceFunc, $total, &$i, $c) {
+ return $reduceFunc($c, $value, $i++, $total);
+ });
+ });
+ };
+
+ $cancellationQueue->enqueue($initialValue);
+
+ \array_reduce($array, $wrappedReduceFunc, resolve($initialValue))
+ ->done($resolve, $reject, $notify);
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+/**
+ * @internal
+ */
+function _checkTypehint(callable $callback, $object)
+{
+ if (!\is_object($object)) {
+ return true;
+ }
+
+ if (\is_array($callback)) {
+ $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
+ } elseif (\is_object($callback) && !$callback instanceof \Closure) {
+ $callbackReflection = new \ReflectionMethod($callback, '__invoke');
+ } else {
+ $callbackReflection = new \ReflectionFunction($callback);
+ }
+
+ $parameters = $callbackReflection->getParameters();
+
+ if (!isset($parameters[0])) {
+ return true;
+ }
+
+ $expectedException = $parameters[0];
+
+ // PHP before v8 used an easy API:
+ if (\PHP_VERSION_ID < 70100 || \defined('HHVM_VERSION')) {
+ if (!$expectedException->getClass()) {
+ return true;
+ }
+
+ return $expectedException->getClass()->isInstance($object);
+ }
+
+ // Extract the type of the argument and handle different possibilities
+ $type = $expectedException->getType();
+
+ $isTypeUnion = true;
+ $types = [];
+
+ switch (true) {
+ case $type === null:
+ break;
+ case $type instanceof \ReflectionNamedType:
+ $types = [$type];
+ break;
+ case $type instanceof \ReflectionIntersectionType:
+ $isTypeUnion = false;
+ case $type instanceof \ReflectionUnionType;
+ $types = $type->getTypes();
+ break;
+ default:
+ throw new \LogicException('Unexpected return value of ReflectionParameter::getType');
+ }
+
+ // If there is no type restriction, it matches
+ if (empty($types)) {
+ return true;
+ }
+
+ foreach ($types as $type) {
+ if (!$type instanceof \ReflectionNamedType) {
+ throw new \LogicException('This implementation does not support groups of intersection or union types');
+ }
+
+ // A named-type can be either a class-name or a built-in type like string, int, array, etc.
+ $matches = ($type->isBuiltin() && \gettype($object) === $type->getName())
+ || (new \ReflectionClass($type->getName()))->isInstance($object);
+
+
+ // If we look for a single match (union), we can return early on match
+ // If we look for a full match (intersection), we can return early on mismatch
+ if ($matches) {
+ if ($isTypeUnion) {
+ return true;
+ }
+ } else {
+ if (!$isTypeUnion) {
+ return false;
+ }
+ }
+ }
+
+ // If we look for a single match (union) and did not return early, we matched no type and are false
+ // If we look for a full match (intersection) and did not return early, we matched all types and are true
+ return $isTypeUnion ? false : true;
+}
diff --git a/vendor/react/promise/src/functions_include.php b/vendor/react/promise/src/functions_include.php
new file mode 100644
index 0000000..bd0c54f
--- /dev/null
+++ b/vendor/react/promise/src/functions_include.php
@@ -0,0 +1,5 @@
+<?php
+
+if (!\function_exists('React\Promise\resolve')) {
+ require __DIR__.'/functions.php';
+}
diff --git a/vendor/react/socket/LICENSE b/vendor/react/socket/LICENSE
new file mode 100644
index 0000000..d6f8901
--- /dev/null
+++ b/vendor/react/socket/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/socket/composer.json b/vendor/react/socket/composer.json
new file mode 100644
index 0000000..ec50942
--- /dev/null
+++ b/vendor/react/socket/composer.json
@@ -0,0 +1,52 @@
+{
+ "name": "react/socket",
+ "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+ "keywords": ["async", "socket", "stream", "connection", "ReactPHP"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "react/dns": "^1.8",
+ "react/event-loop": "^1.2",
+ "react/promise": "^2.6.0 || ^1.2.1",
+ "react/promise-timer": "^1.8",
+ "react/stream": "^1.2"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.5",
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
+ "react/promise-stream": "^1.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Socket\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Socket\\": "tests"
+ }
+ }
+}
diff --git a/vendor/react/socket/src/Connection.php b/vendor/react/socket/src/Connection.php
new file mode 100644
index 0000000..5e3b00d
--- /dev/null
+++ b/vendor/react/socket/src/Connection.php
@@ -0,0 +1,187 @@
+<?php
+
+namespace React\Socket;
+
+use Evenement\EventEmitter;
+use React\EventLoop\LoopInterface;
+use React\Stream\DuplexResourceStream;
+use React\Stream\Util;
+use React\Stream\WritableResourceStream;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * The actual connection implementation for ConnectionInterface
+ *
+ * This class should only be used internally, see ConnectionInterface instead.
+ *
+ * @see ConnectionInterface
+ * @internal
+ */
+class Connection extends EventEmitter implements ConnectionInterface
+{
+ /**
+ * Internal flag whether this is a Unix domain socket (UDS) connection
+ *
+ * @internal
+ */
+ public $unix = false;
+
+ /**
+ * Internal flag whether encryption has been enabled on this connection
+ *
+ * Mostly used by internal StreamEncryption so that connection returns
+ * `tls://` scheme for encrypted connections instead of `tcp://`.
+ *
+ * @internal
+ */
+ public $encryptionEnabled = false;
+
+ /** @internal */
+ public $stream;
+
+ private $input;
+
+ public function __construct($resource, LoopInterface $loop)
+ {
+ // PHP < 7.3.3 (and PHP < 7.2.15) suffers from a bug where feof() might
+ // block with 100% CPU usage on fragmented TLS records.
+ // We try to work around this by always consuming the complete receive
+ // buffer at once to avoid stale data in TLS buffers. This is known to
+ // work around high CPU usage for well-behaving peers, but this may
+ // cause very large data chunks for high throughput scenarios. The buggy
+ // behavior can still be triggered due to network I/O buffers or
+ // malicious peers on affected versions, upgrading is highly recommended.
+ // @link https://bugs.php.net/bug.php?id=77390
+ $clearCompleteBuffer = \PHP_VERSION_ID < 70215 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70303);
+
+ // PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big
+ // chunks of data over TLS streams at once.
+ // We try to work around this by limiting the write chunk size to 8192
+ // bytes for older PHP versions only.
+ // This is only a work-around and has a noticable performance penalty on
+ // affected versions. Please update your PHP version.
+ // This applies to all streams because TLS may be enabled later on.
+ // See https://github.com/reactphp/socket/issues/105
+ $limitWriteChunks = (\PHP_VERSION_ID < 70018 || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70104));
+
+ $this->input = new DuplexResourceStream(
+ $resource,
+ $loop,
+ $clearCompleteBuffer ? -1 : null,
+ new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null)
+ );
+
+ $this->stream = $resource;
+
+ Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain'));
+
+ $this->input->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->input->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->input->isWritable();
+ }
+
+ public function pause()
+ {
+ $this->input->pause();
+ }
+
+ public function resume()
+ {
+ $this->input->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return $this->input->pipe($dest, $options);
+ }
+
+ public function write($data)
+ {
+ return $this->input->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->input->end($data);
+ }
+
+ public function close()
+ {
+ $this->input->close();
+ $this->handleClose();
+ $this->removeAllListeners();
+ }
+
+ public function handleClose()
+ {
+ if (!\is_resource($this->stream)) {
+ return;
+ }
+
+ // Try to cleanly shut down socket and ignore any errors in case other
+ // side already closed. Shutting down may return to blocking mode on
+ // some legacy versions, so reset to non-blocking just in case before
+ // continuing to close the socket resource.
+ // Underlying Stream implementation will take care of closing file
+ // handle, so we otherwise keep this open here.
+ @\stream_socket_shutdown($this->stream, \STREAM_SHUT_RDWR);
+ \stream_set_blocking($this->stream, false);
+ }
+
+ public function getRemoteAddress()
+ {
+ if (!\is_resource($this->stream)) {
+ return null;
+ }
+
+ return $this->parseAddress(\stream_socket_get_name($this->stream, true));
+ }
+
+ public function getLocalAddress()
+ {
+ if (!\is_resource($this->stream)) {
+ return null;
+ }
+
+ return $this->parseAddress(\stream_socket_get_name($this->stream, false));
+ }
+
+ private function parseAddress($address)
+ {
+ if ($address === false) {
+ return null;
+ }
+
+ if ($this->unix) {
+ // remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo
+ // note that technically ":" is a valid address, so keep this in place otherwise
+ if (\substr($address, -1) === ':' && \defined('HHVM_VERSION_ID') && \HHVM_VERSION_ID < 31900) {
+ $address = (string)\substr($address, 0, -1); // @codeCoverageIgnore
+ }
+
+ // work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556
+ // PHP uses "\0" string and HHVM uses empty string (colon removed above)
+ if ($address === '' || $address[0] === "\x00" ) {
+ return null; // @codeCoverageIgnore
+ }
+
+ return 'unix://' . $address;
+ }
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = \strrpos($address, ':');
+ if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
+ $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
+ }
+
+ return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address;
+ }
+}
diff --git a/vendor/react/socket/src/ConnectionInterface.php b/vendor/react/socket/src/ConnectionInterface.php
new file mode 100644
index 0000000..64613b5
--- /dev/null
+++ b/vendor/react/socket/src/ConnectionInterface.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace React\Socket;
+
+use React\Stream\DuplexStreamInterface;
+
+/**
+ * Any incoming and outgoing connection is represented by this interface,
+ * such as a normal TCP/IP connection.
+ *
+ * An incoming or outgoing connection is a duplex stream (both readable and
+ * writable) that implements React's
+ * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+ * It contains additional properties for the local and remote address (client IP)
+ * where this connection has been established to/from.
+ *
+ * Most commonly, instances implementing this `ConnectionInterface` are emitted
+ * by all classes implementing the [`ServerInterface`](#serverinterface) and
+ * used by all classes implementing the [`ConnectorInterface`](#connectorinterface).
+ *
+ * Because the `ConnectionInterface` implements the underlying
+ * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
+ * you can use any of its events and methods as usual:
+ *
+ * ```php
+ * $connection->on('data', function ($chunk) {
+ * echo $chunk;
+ * });
+ *
+ * $connection->on('end', function () {
+ * echo 'ended';
+ * });
+ *
+ * $connection->on('error', function (Exception $e) {
+ * echo 'error: ' . $e->getMessage();
+ * });
+ *
+ * $connection->on('close', function () {
+ * echo 'closed';
+ * });
+ *
+ * $connection->write($data);
+ * $connection->end($data = null);
+ * $connection->close();
+ * // …
+ * ```
+ *
+ * For more details, see the
+ * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+ *
+ * @see DuplexStreamInterface
+ * @see ServerInterface
+ * @see ConnectorInterface
+ */
+interface ConnectionInterface extends DuplexStreamInterface
+{
+ /**
+ * Returns the full remote address (URI) where this connection has been established with
+ *
+ * ```php
+ * $address = $connection->getRemoteAddress();
+ * echo 'Connection with ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the remote address can not be determined or is unknown at this time (such as
+ * after the connection has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+ * `unix://example.sock` or `unix:///path/to/example.sock`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * If this is a TCP/IP based connection and you only want the remote IP, you may
+ * use something like this:
+ *
+ * ```php
+ * $address = $connection->getRemoteAddress();
+ * $ip = trim(parse_url($address, PHP_URL_HOST), '[]');
+ * echo 'Connection with ' . $ip . PHP_EOL;
+ * ```
+ *
+ * @return ?string remote address (URI) or null if unknown
+ */
+ public function getRemoteAddress();
+
+ /**
+ * Returns the full local address (full URI with scheme, IP and port) where this connection has been established with
+ *
+ * ```php
+ * $address = $connection->getLocalAddress();
+ * echo 'Connection with ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the local address can not be determined or is unknown at this time (such as
+ * after the connection has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+ * `unix://example.sock` or `unix:///path/to/example.sock`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
+ * so they should not be confused.
+ *
+ * If your `TcpServer` instance is listening on multiple interfaces (e.g. using
+ * the address `0.0.0.0`), you can use this method to find out which interface
+ * actually accepted this connection (such as a public or local interface).
+ *
+ * If your system has multiple interfaces (e.g. a WAN and a LAN interface),
+ * you can use this method to find out which interface was actually
+ * used for this connection.
+ *
+ * @return ?string local address (URI) or null if unknown
+ * @see self::getRemoteAddress()
+ */
+ public function getLocalAddress();
+}
diff --git a/vendor/react/socket/src/Connector.php b/vendor/react/socket/src/Connector.php
new file mode 100644
index 0000000..93477bd
--- /dev/null
+++ b/vendor/react/socket/src/Connector.php
@@ -0,0 +1,236 @@
+<?php
+
+namespace React\Socket;
+
+use React\Dns\Config\Config as DnsConfig;
+use React\Dns\Resolver\Factory as DnsFactory;
+use React\Dns\Resolver\ResolverInterface;
+use React\EventLoop\LoopInterface;
+
+/**
+ * The `Connector` class is the main class in this package that implements the
+ * `ConnectorInterface` and allows you to create streaming connections.
+ *
+ * You can use this connector to create any kind of streaming connections, such
+ * as plaintext TCP/IP, secure TLS or local Unix connection streams.
+ *
+ * Under the hood, the `Connector` is implemented as a *higher-level facade*
+ * for the lower-level connectors implemented in this package. This means it
+ * also shares all of their features and implementation details.
+ * If you want to typehint in your higher-level protocol implementation, you SHOULD
+ * use the generic [`ConnectorInterface`](#connectorinterface) instead.
+ *
+ * @see ConnectorInterface for the base interface
+ */
+final class Connector implements ConnectorInterface
+{
+ private $connectors = array();
+
+ /**
+ * Instantiate new `Connector`
+ *
+ * ```php
+ * $connector = new React\Socket\Connector();
+ * ```
+ *
+ * This class takes two optional arguments for more advanced usage:
+ *
+ * ```php
+ * // constructor signature as of v1.9.0
+ * $connector = new React\Socket\Connector(array $context = [], ?LoopInterface $loop = null);
+ *
+ * // legacy constructor signature before v1.9.0
+ * $connector = new React\Socket\Connector(?LoopInterface $loop = null, array $context = []);
+ * ```
+ *
+ * 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 array|LoopInterface|null $context
+ * @param null|LoopInterface|array $loop
+ * @throws \InvalidArgumentException for invalid arguments
+ */
+ public function __construct($context = array(), $loop = null)
+ {
+ // swap arguments for legacy constructor signature
+ if (($context instanceof LoopInterface || $context === null) && (\func_num_args() <= 1 || \is_array($loop))) {
+ $swap = $loop === null ? array(): $loop;
+ $loop = $context;
+ $context = $swap;
+ }
+
+ if (!\is_array($context) || ($loop !== null && !$loop instanceof LoopInterface)) {
+ throw new \InvalidArgumentException('Expected "array $context" and "?LoopInterface $loop" arguments');
+ }
+
+ // apply default options if not explicitly given
+ $context += array(
+ 'tcp' => true,
+ 'tls' => true,
+ 'unix' => true,
+
+ 'dns' => true,
+ 'timeout' => true,
+ 'happy_eyeballs' => true,
+ );
+
+ if ($context['timeout'] === true) {
+ $context['timeout'] = (float)\ini_get("default_socket_timeout");
+ }
+
+ if ($context['tcp'] instanceof ConnectorInterface) {
+ $tcp = $context['tcp'];
+ } else {
+ $tcp = new TcpConnector(
+ $loop,
+ \is_array($context['tcp']) ? $context['tcp'] : array()
+ );
+ }
+
+ if ($context['dns'] !== false) {
+ if ($context['dns'] instanceof ResolverInterface) {
+ $resolver = $context['dns'];
+ } else {
+ if ($context['dns'] !== true) {
+ $config = $context['dns'];
+ } else {
+ // try to load nameservers from system config or default to Google's public DNS
+ $config = DnsConfig::loadSystemConfigBlocking();
+ if (!$config->nameservers) {
+ $config->nameservers[] = '8.8.8.8'; // @codeCoverageIgnore
+ }
+ }
+
+ $factory = new DnsFactory();
+ $resolver = $factory->createCached(
+ $config,
+ $loop
+ );
+ }
+
+ if ($context['happy_eyeballs'] === true) {
+ $tcp = new HappyEyeBallsConnector($loop, $tcp, $resolver);
+ } else {
+ $tcp = new DnsConnector($tcp, $resolver);
+ }
+ }
+
+ if ($context['tcp'] !== false) {
+ $context['tcp'] = $tcp;
+
+ if ($context['timeout'] !== false) {
+ $context['tcp'] = new TimeoutConnector(
+ $context['tcp'],
+ $context['timeout'],
+ $loop
+ );
+ }
+
+ $this->connectors['tcp'] = $context['tcp'];
+ }
+
+ if ($context['tls'] !== false) {
+ if (!$context['tls'] instanceof ConnectorInterface) {
+ $context['tls'] = new SecureConnector(
+ $tcp,
+ $loop,
+ \is_array($context['tls']) ? $context['tls'] : array()
+ );
+ }
+
+ if ($context['timeout'] !== false) {
+ $context['tls'] = new TimeoutConnector(
+ $context['tls'],
+ $context['timeout'],
+ $loop
+ );
+ }
+
+ $this->connectors['tls'] = $context['tls'];
+ }
+
+ if ($context['unix'] !== false) {
+ if (!$context['unix'] instanceof ConnectorInterface) {
+ $context['unix'] = new UnixConnector($loop);
+ }
+ $this->connectors['unix'] = $context['unix'];
+ }
+ }
+
+ public function connect($uri)
+ {
+ $scheme = 'tcp';
+ if (\strpos($uri, '://') !== false) {
+ $scheme = (string)\substr($uri, 0, \strpos($uri, '://'));
+ }
+
+ if (!isset($this->connectors[$scheme])) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'No connector available for URI scheme "' . $scheme . '" (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ ));
+ }
+
+ return $this->connectors[$scheme]->connect($uri);
+ }
+
+
+ /**
+ * [internal] Builds on URI from the given URI parts and ip address with original hostname as query
+ *
+ * @param array $parts
+ * @param string $host
+ * @param string $ip
+ * @return string
+ * @internal
+ */
+ public static function uri(array $parts, $host, $ip)
+ {
+ $uri = '';
+
+ // prepend original scheme if known
+ if (isset($parts['scheme'])) {
+ $uri .= $parts['scheme'] . '://';
+ }
+
+ if (\strpos($ip, ':') !== false) {
+ // enclose IPv6 addresses in square brackets before appending port
+ $uri .= '[' . $ip . ']';
+ } else {
+ $uri .= $ip;
+ }
+
+ // append original port if known
+ if (isset($parts['port'])) {
+ $uri .= ':' . $parts['port'];
+ }
+
+ // append orignal path if known
+ if (isset($parts['path'])) {
+ $uri .= $parts['path'];
+ }
+
+ // append original query if known
+ if (isset($parts['query'])) {
+ $uri .= '?' . $parts['query'];
+ }
+
+ // append original hostname as query if resolved via DNS and if
+ // destination URI does not contain "hostname" query param already
+ $args = array();
+ \parse_str(isset($parts['query']) ? $parts['query'] : '', $args);
+ if ($host !== $ip && !isset($args['hostname'])) {
+ $uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . \rawurlencode($host);
+ }
+
+ // append original fragment if known
+ if (isset($parts['fragment'])) {
+ $uri .= '#' . $parts['fragment'];
+ }
+
+ return $uri;
+ }
+}
diff --git a/vendor/react/socket/src/ConnectorInterface.php b/vendor/react/socket/src/ConnectorInterface.php
new file mode 100644
index 0000000..3dd78f1
--- /dev/null
+++ b/vendor/react/socket/src/ConnectorInterface.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace React\Socket;
+
+/**
+ * The `ConnectorInterface` is responsible for providing an interface for
+ * establishing streaming connections, such as a normal TCP/IP connection.
+ *
+ * This is the main interface defined in this package and it is used throughout
+ * React's vast ecosystem.
+ *
+ * Most higher-level components (such as HTTP, database or other networking
+ * service clients) accept an instance implementing this interface to create their
+ * TCP/IP connection to the underlying networking service.
+ * This is usually done via dependency injection, so it's fairly simple to actually
+ * swap this implementation against any other implementation of this interface.
+ *
+ * The interface only offers a single `connect()` method.
+ *
+ * @see ConnectionInterface
+ */
+interface ConnectorInterface
+{
+ /**
+ * Creates a streaming connection to the given remote address
+ *
+ * If returns a Promise which either fulfills with a stream implementing
+ * `ConnectionInterface` on success or rejects with an `Exception` if the
+ * connection is not successful.
+ *
+ * ```php
+ * $connector->connect('google.com:443')->then(
+ * function (React\Socket\ConnectionInterface $connection) {
+ * // connection successfully established
+ * },
+ * function (Exception $error) {
+ * // failed to connect due to $error
+ * }
+ * );
+ * ```
+ *
+ * The returned Promise MUST be implemented in such a way that it can be
+ * cancelled when it is still pending. Cancelling a pending promise MUST
+ * reject its value with an Exception. It SHOULD clean up any underlying
+ * resources and references as applicable.
+ *
+ * ```php
+ * $promise = $connector->connect($uri);
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $uri
+ * @return \React\Promise\PromiseInterface resolves with a stream implementing ConnectionInterface on success or rejects with an Exception on error
+ * @see ConnectionInterface
+ */
+ public function connect($uri);
+}
diff --git a/vendor/react/socket/src/DnsConnector.php b/vendor/react/socket/src/DnsConnector.php
new file mode 100644
index 0000000..27fc8f8
--- /dev/null
+++ b/vendor/react/socket/src/DnsConnector.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace React\Socket;
+
+use React\Dns\Resolver\ResolverInterface;
+use React\Promise;
+use React\Promise\CancellablePromiseInterface;
+
+final class DnsConnector implements ConnectorInterface
+{
+ private $connector;
+ private $resolver;
+
+ public function __construct(ConnectorInterface $connector, ResolverInterface $resolver)
+ {
+ $this->connector = $connector;
+ $this->resolver = $resolver;
+ }
+
+ public function connect($uri)
+ {
+ $original = $uri;
+ if (\strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ $parts = \parse_url($uri);
+ if (isset($parts['scheme'])) {
+ unset($parts['scheme']);
+ }
+ } else {
+ $parts = \parse_url($uri);
+ }
+
+ if (!$parts || !isset($parts['host'])) {
+ return Promise\reject(new \InvalidArgumentException(
+ 'Given URI "' . $original . '" is invalid (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ ));
+ }
+
+ $host = \trim($parts['host'], '[]');
+ $connector = $this->connector;
+
+ // skip DNS lookup / URI manipulation if this URI already contains an IP
+ if (@\inet_pton($host) !== false) {
+ return $connector->connect($original);
+ }
+
+ $promise = $this->resolver->resolve($host);
+ $resolved = null;
+
+ return new Promise\Promise(
+ function ($resolve, $reject) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
+ // resolve/reject with result of DNS lookup
+ $promise->then(function ($ip) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
+ $resolved = $ip;
+
+ return $promise = $connector->connect(
+ Connector::uri($parts, $host, $ip)
+ )->then(null, function (\Exception $e) use ($uri) {
+ if ($e instanceof \RuntimeException) {
+ $message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage());
+ $e = new \RuntimeException(
+ 'Connection to ' . $uri . ' failed: ' . $message,
+ $e->getCode(),
+ $e
+ );
+
+ // avoid garbage references by replacing all closures in call stack.
+ // what a lovely piece of code!
+ $r = new \ReflectionProperty('Exception', 'trace');
+ $r->setAccessible(true);
+ $trace = $r->getValue($e);
+
+ // Exception trace arguments are not available on some PHP 7.4 installs
+ // @codeCoverageIgnoreStart
+ foreach ($trace as &$one) {
+ if (isset($one['args'])) {
+ foreach ($one['args'] as &$arg) {
+ if ($arg instanceof \Closure) {
+ $arg = 'Object(' . \get_class($arg) . ')';
+ }
+ }
+ }
+ }
+ // @codeCoverageIgnoreEnd
+ $r->setValue($e, $trace);
+ }
+
+ throw $e;
+ });
+ }, function ($e) use ($uri, $reject) {
+ $reject(new \RuntimeException('Connection to ' . $uri .' failed during DNS lookup: ' . $e->getMessage(), 0, $e));
+ })->then($resolve, $reject);
+ },
+ function ($_, $reject) use (&$promise, &$resolved, $uri) {
+ // cancellation should reject connection attempt
+ // reject DNS resolution with custom reason, otherwise rely on connection cancellation below
+ if ($resolved === null) {
+ $reject(new \RuntimeException(
+ 'Connection to ' . $uri . ' cancelled during DNS lookup (ECONNABORTED)',
+ \defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
+ ));
+ }
+
+ // (try to) cancel pending DNS lookup / connection attempt
+ if ($promise instanceof CancellablePromiseInterface) {
+ // overwrite callback arguments for PHP7+ only, so they do not show
+ // up in the Exception trace and do not cause a possible cyclic reference.
+ $_ = $reject = null;
+
+ $promise->cancel();
+ $promise = null;
+ }
+ }
+ );
+ }
+}
diff --git a/vendor/react/socket/src/FdServer.php b/vendor/react/socket/src/FdServer.php
new file mode 100644
index 0000000..2c7a6c4
--- /dev/null
+++ b/vendor/react/socket/src/FdServer.php
@@ -0,0 +1,212 @@
+<?php
+
+namespace React\Socket;
+
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+
+/**
+ * [Internal] The `FdServer` class implements the `ServerInterface` and
+ * is responsible for accepting connections from an existing file descriptor.
+ *
+ * ```php
+ * $socket = new React\Socket\FdServer(3);
+ * ```
+ *
+ * Whenever a client connects, it will emit a `connection` event with a connection
+ * instance implementing `ConnectionInterface`:
+ *
+ * ```php
+ * $socket->on('connection', function (ConnectionInterface $connection) {
+ * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ * @internal
+ */
+final class FdServer extends EventEmitter implements ServerInterface
+{
+ private $master;
+ private $loop;
+ private $unix = false;
+ private $listening = false;
+
+ /**
+ * Creates a socket server and starts listening on the given file descriptor
+ *
+ * This starts accepting new incoming connections on the given file descriptor.
+ * See also the `connection event` documented in the `ServerInterface`
+ * for more details.
+ *
+ * ```php
+ * $socket = new React\Socket\FdServer(3);
+ * ```
+ *
+ * If the given FD is invalid or out of range, it will throw an `InvalidArgumentException`:
+ *
+ * ```php
+ * // throws InvalidArgumentException
+ * $socket = new React\Socket\FdServer(-1);
+ * ```
+ *
+ * If the given FD appears to be valid, but listening on it fails (such as
+ * if the FD does not exist or does not refer to a socket server), it will
+ * throw a `RuntimeException`:
+ *
+ * ```php
+ * // throws RuntimeException because FD does not reference a socket server
+ * $socket = new React\Socket\FdServer(0, $loop);
+ * ```
+ *
+ * Note that these error conditions may vary depending on your system and/or
+ * configuration.
+ * See the exception message and code for more details about the actual error
+ * condition.
+ *
+ * @param int|string $fd FD number such as `3` or as URL in the form of `php://fd/3`
+ * @param ?LoopInterface $loop
+ * @throws \InvalidArgumentException if the listening address is invalid
+ * @throws \RuntimeException if listening on this address fails (already in use etc.)
+ */
+ public function __construct($fd, LoopInterface $loop = null)
+ {
+ if (\preg_match('#^php://fd/(\d+)$#', $fd, $m)) {
+ $fd = (int) $m[1];
+ }
+ if (!\is_int($fd) || $fd < 0 || $fd >= \PHP_INT_MAX) {
+ throw new \InvalidArgumentException(
+ 'Invalid FD number given (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ );
+ }
+
+ $this->loop = $loop ?: Loop::get();
+
+ $this->master = @\fopen('php://fd/' . $fd, 'r+');
+ if (false === $this->master) {
+ // Match errstr from PHP's warning message.
+ // fopen(php://fd/3): Failed to open stream: Error duping file descriptor 3; possibly it doesn't exist: [9]: Bad file descriptor
+ $error = \error_get_last();
+ \preg_match('/\[(\d+)\]: (.*)/', $error['message'], $m);
+ $errno = isset($m[1]) ? (int) $m[1] : 0;
+ $errstr = isset($m[2]) ? $m[2] : $error['message'];
+
+ throw new \RuntimeException(
+ 'Failed to listen on FD ' . $fd . ': ' . $errstr . SocketServer::errconst($errno),
+ $errno
+ );
+ }
+
+ $meta = \stream_get_meta_data($this->master);
+ if (!isset($meta['stream_type']) || $meta['stream_type'] !== 'tcp_socket') {
+ \fclose($this->master);
+
+ $errno = \defined('SOCKET_ENOTSOCK') ? \SOCKET_ENOTSOCK : 88;
+ $errstr = \function_exists('socket_strerror') ? \socket_strerror($errno) : 'Not a socket';
+
+ throw new \RuntimeException(
+ 'Failed to listen on FD ' . $fd . ': ' . $errstr . ' (ENOTSOCK)',
+ $errno
+ );
+ }
+
+ // Socket should not have a peer address if this is a listening socket.
+ // Looks like this work-around is the closest we can get because PHP doesn't expose SO_ACCEPTCONN even with ext-sockets.
+ if (\stream_socket_get_name($this->master, true) !== false) {
+ \fclose($this->master);
+
+ $errno = \defined('SOCKET_EISCONN') ? \SOCKET_EISCONN : 106;
+ $errstr = \function_exists('socket_strerror') ? \socket_strerror($errno) : 'Socket is connected';
+
+ throw new \RuntimeException(
+ 'Failed to listen on FD ' . $fd . ': ' . $errstr . ' (EISCONN)',
+ $errno
+ );
+ }
+
+ // Assume this is a Unix domain socket (UDS) when its listening address doesn't parse as a valid URL with a port.
+ // Looks like this work-around is the closest we can get because PHP doesn't expose SO_DOMAIN even with ext-sockets.
+ $this->unix = \parse_url($this->getAddress(), \PHP_URL_PORT) === false;
+
+ \stream_set_blocking($this->master, false);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!\is_resource($this->master)) {
+ return null;
+ }
+
+ $address = \stream_socket_get_name($this->master, false);
+
+ if ($this->unix === true) {
+ return 'unix://' . $address;
+ }
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = \strrpos($address, ':');
+ if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
+ $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
+ }
+
+ return 'tcp://' . $address;
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !\is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ try {
+ $newSocket = SocketServer::accept($master);
+ } catch (\RuntimeException $e) {
+ $that->emit('error', array($e));
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!\is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ \fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $connection = new Connection($socket, $this->loop);
+ $connection->unix = $this->unix;
+
+ $this->emit('connection', array($connection));
+ }
+}
diff --git a/vendor/react/socket/src/FixedUriConnector.php b/vendor/react/socket/src/FixedUriConnector.php
new file mode 100644
index 0000000..f83241d
--- /dev/null
+++ b/vendor/react/socket/src/FixedUriConnector.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace React\Socket;
+
+/**
+ * Decorates an existing Connector to always use a fixed, preconfigured URI
+ *
+ * This can be useful for consumers that do not support certain URIs, such as
+ * when you want to explicitly connect to a Unix domain socket (UDS) path
+ * instead of connecting to a default address assumed by an higher-level API:
+ *
+ * ```php
+ * $connector = new React\Socket\FixedUriConnector(
+ * 'unix:///var/run/docker.sock',
+ * new React\Socket\UnixConnector()
+ * );
+ *
+ * // destination will be ignored, actually connects to Unix domain socket
+ * $promise = $connector->connect('localhost:80');
+ * ```
+ */
+class FixedUriConnector implements ConnectorInterface
+{
+ private $uri;
+ private $connector;
+
+ /**
+ * @param string $uri
+ * @param ConnectorInterface $connector
+ */
+ public function __construct($uri, ConnectorInterface $connector)
+ {
+ $this->uri = $uri;
+ $this->connector = $connector;
+ }
+
+ public function connect($_)
+ {
+ return $this->connector->connect($this->uri);
+ }
+}
diff --git a/vendor/react/socket/src/HappyEyeBallsConnectionBuilder.php b/vendor/react/socket/src/HappyEyeBallsConnectionBuilder.php
new file mode 100644
index 0000000..6bd0716
--- /dev/null
+++ b/vendor/react/socket/src/HappyEyeBallsConnectionBuilder.php
@@ -0,0 +1,333 @@
+<?php
+
+namespace React\Socket;
+
+use React\Dns\Model\Message;
+use React\Dns\Resolver\ResolverInterface;
+use React\EventLoop\LoopInterface;
+use React\EventLoop\TimerInterface;
+use React\Promise;
+use React\Promise\CancellablePromiseInterface;
+
+/**
+ * @internal
+ */
+final class HappyEyeBallsConnectionBuilder
+{
+ /**
+ * As long as we haven't connected yet keep popping an IP address of the connect queue until one of them
+ * succeeds or they all fail. We will wait 100ms between connection attempts as per RFC.
+ *
+ * @link https://tools.ietf.org/html/rfc8305#section-5
+ */
+ const CONNECTION_ATTEMPT_DELAY = 0.1;
+
+ /**
+ * Delay `A` lookup by 50ms sending out connection to IPv4 addresses when IPv6 records haven't
+ * resolved yet as per RFC.
+ *
+ * @link https://tools.ietf.org/html/rfc8305#section-3
+ */
+ const RESOLUTION_DELAY = 0.05;
+
+ public $loop;
+ public $connector;
+ public $resolver;
+ public $uri;
+ public $host;
+ public $resolved = array(
+ Message::TYPE_A => false,
+ Message::TYPE_AAAA => false,
+ );
+ public $resolverPromises = array();
+ public $connectionPromises = array();
+ public $connectQueue = array();
+ public $nextAttemptTimer;
+ public $parts;
+ public $ipsCount = 0;
+ public $failureCount = 0;
+ public $resolve;
+ public $reject;
+
+ public $lastErrorFamily;
+ public $lastError6;
+ public $lastError4;
+
+ public function __construct(LoopInterface $loop, ConnectorInterface $connector, ResolverInterface $resolver, $uri, $host, $parts)
+ {
+ $this->loop = $loop;
+ $this->connector = $connector;
+ $this->resolver = $resolver;
+ $this->uri = $uri;
+ $this->host = $host;
+ $this->parts = $parts;
+ }
+
+ public function connect()
+ {
+ $timer = null;
+ $that = $this;
+ return new Promise\Promise(function ($resolve, $reject) use ($that, &$timer) {
+ $lookupResolve = function ($type) use ($that, $resolve, $reject) {
+ return function (array $ips) use ($that, $type, $resolve, $reject) {
+ unset($that->resolverPromises[$type]);
+ $that->resolved[$type] = true;
+
+ $that->mixIpsIntoConnectQueue($ips);
+
+ // start next connection attempt if not already awaiting next
+ if ($that->nextAttemptTimer === null && $that->connectQueue) {
+ $that->check($resolve, $reject);
+ }
+ };
+ };
+
+ $that->resolverPromises[Message::TYPE_AAAA] = $that->resolve(Message::TYPE_AAAA, $reject)->then($lookupResolve(Message::TYPE_AAAA));
+ $that->resolverPromises[Message::TYPE_A] = $that->resolve(Message::TYPE_A, $reject)->then(function (array $ips) use ($that, &$timer) {
+ // happy path: IPv6 has resolved already (or could not resolve), continue with IPv4 addresses
+ if ($that->resolved[Message::TYPE_AAAA] === true || !$ips) {
+ return $ips;
+ }
+
+ // Otherwise delay processing IPv4 lookup until short timer passes or IPv6 resolves in the meantime
+ $deferred = new Promise\Deferred();
+ $timer = $that->loop->addTimer($that::RESOLUTION_DELAY, function () use ($deferred, $ips) {
+ $deferred->resolve($ips);
+ });
+
+ $that->resolverPromises[Message::TYPE_AAAA]->then(function () use ($that, $timer, $deferred, $ips) {
+ $that->loop->cancelTimer($timer);
+ $deferred->resolve($ips);
+ });
+
+ return $deferred->promise();
+ })->then($lookupResolve(Message::TYPE_A));
+ }, function ($_, $reject) use ($that, &$timer) {
+ $reject(new \RuntimeException(
+ 'Connection to ' . $that->uri . ' cancelled' . (!$that->connectionPromises ? ' during DNS lookup' : '') . ' (ECONNABORTED)',
+ \defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
+ ));
+ $_ = $reject = null;
+
+ $that->cleanUp();
+ if ($timer instanceof TimerInterface) {
+ $that->loop->cancelTimer($timer);
+ }
+ });
+ }
+
+ /**
+ * @internal
+ * @param int $type DNS query type
+ * @param callable $reject
+ * @return \React\Promise\PromiseInterface<string[]> Returns a promise that
+ * always resolves with a list of IP addresses on success or an empty
+ * list on error.
+ */
+ public function resolve($type, $reject)
+ {
+ $that = $this;
+ return $that->resolver->resolveAll($that->host, $type)->then(null, function (\Exception $e) use ($type, $reject, $that) {
+ unset($that->resolverPromises[$type]);
+ $that->resolved[$type] = true;
+
+ if ($type === Message::TYPE_A) {
+ $that->lastError4 = $e->getMessage();
+ $that->lastErrorFamily = 4;
+ } else {
+ $that->lastError6 = $e->getMessage();
+ $that->lastErrorFamily = 6;
+ }
+
+ // cancel next attempt timer when there are no more IPs to connect to anymore
+ if ($that->nextAttemptTimer !== null && !$that->connectQueue) {
+ $that->loop->cancelTimer($that->nextAttemptTimer);
+ $that->nextAttemptTimer = null;
+ }
+
+ if ($that->hasBeenResolved() && $that->ipsCount === 0) {
+ $reject(new \RuntimeException(
+ $that->error(),
+ 0,
+ $e
+ ));
+ }
+
+ // Exception already handled above, so don't throw an unhandled rejection here
+ return array();
+ });
+ }
+
+ /**
+ * @internal
+ */
+ public function check($resolve, $reject)
+ {
+ $ip = \array_shift($this->connectQueue);
+
+ // start connection attempt and remember array position to later unset again
+ $this->connectionPromises[] = $this->attemptConnection($ip);
+ \end($this->connectionPromises);
+ $index = \key($this->connectionPromises);
+
+ $that = $this;
+ $that->connectionPromises[$index]->then(function ($connection) use ($that, $index, $resolve) {
+ unset($that->connectionPromises[$index]);
+
+ $that->cleanUp();
+
+ $resolve($connection);
+ }, function (\Exception $e) use ($that, $index, $ip, $resolve, $reject) {
+ unset($that->connectionPromises[$index]);
+
+ $that->failureCount++;
+
+ $message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage());
+ if (\strpos($ip, ':') === false) {
+ $that->lastError4 = $message;
+ $that->lastErrorFamily = 4;
+ } else {
+ $that->lastError6 = $message;
+ $that->lastErrorFamily = 6;
+ }
+
+ // start next connection attempt immediately on error
+ if ($that->connectQueue) {
+ if ($that->nextAttemptTimer !== null) {
+ $that->loop->cancelTimer($that->nextAttemptTimer);
+ $that->nextAttemptTimer = null;
+ }
+
+ $that->check($resolve, $reject);
+ }
+
+ if ($that->hasBeenResolved() === false) {
+ return;
+ }
+
+ if ($that->ipsCount === $that->failureCount) {
+ $that->cleanUp();
+
+ $reject(new \RuntimeException(
+ $that->error(),
+ $e->getCode(),
+ $e
+ ));
+ }
+ });
+
+ // Allow next connection attempt in 100ms: https://tools.ietf.org/html/rfc8305#section-5
+ // Only start timer when more IPs are queued or when DNS query is still pending (might add more IPs)
+ if ($this->nextAttemptTimer === null && (\count($this->connectQueue) > 0 || $this->resolved[Message::TYPE_A] === false || $this->resolved[Message::TYPE_AAAA] === false)) {
+ $this->nextAttemptTimer = $this->loop->addTimer(self::CONNECTION_ATTEMPT_DELAY, function () use ($that, $resolve, $reject) {
+ $that->nextAttemptTimer = null;
+
+ if ($that->connectQueue) {
+ $that->check($resolve, $reject);
+ }
+ });
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public function attemptConnection($ip)
+ {
+ $uri = Connector::uri($this->parts, $this->host, $ip);
+
+ return $this->connector->connect($uri);
+ }
+
+ /**
+ * @internal
+ */
+ public function cleanUp()
+ {
+ // clear list of outstanding IPs to avoid creating new connections
+ $this->connectQueue = array();
+
+ foreach ($this->connectionPromises as $connectionPromise) {
+ if ($connectionPromise instanceof CancellablePromiseInterface) {
+ $connectionPromise->cancel();
+ }
+ }
+
+ foreach ($this->resolverPromises as $resolverPromise) {
+ if ($resolverPromise instanceof CancellablePromiseInterface) {
+ $resolverPromise->cancel();
+ }
+ }
+
+ if ($this->nextAttemptTimer instanceof TimerInterface) {
+ $this->loop->cancelTimer($this->nextAttemptTimer);
+ $this->nextAttemptTimer = null;
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public function hasBeenResolved()
+ {
+ foreach ($this->resolved as $typeHasBeenResolved) {
+ if ($typeHasBeenResolved === false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Mixes an array of IP addresses into the connect queue in such a way they alternate when attempting to connect.
+ * The goal behind it is first attempt to connect to IPv6, then to IPv4, then to IPv6 again until one of those
+ * attempts succeeds.
+ *
+ * @link https://tools.ietf.org/html/rfc8305#section-4
+ *
+ * @internal
+ */
+ public function mixIpsIntoConnectQueue(array $ips)
+ {
+ \shuffle($ips);
+ $this->ipsCount += \count($ips);
+ $connectQueueStash = $this->connectQueue;
+ $this->connectQueue = array();
+ while (\count($connectQueueStash) > 0 || \count($ips) > 0) {
+ if (\count($ips) > 0) {
+ $this->connectQueue[] = \array_shift($ips);
+ }
+ if (\count($connectQueueStash) > 0) {
+ $this->connectQueue[] = \array_shift($connectQueueStash);
+ }
+ }
+ }
+
+ /**
+ * @internal
+ * @return string
+ */
+ public function error()
+ {
+ if ($this->lastError4 === $this->lastError6) {
+ $message = $this->lastError6;
+ } elseif ($this->lastErrorFamily === 6) {
+ $message = 'Last error for IPv6: ' . $this->lastError6 . '. Previous error for IPv4: ' . $this->lastError4;
+ } else {
+ $message = 'Last error for IPv4: ' . $this->lastError4 . '. Previous error for IPv6: ' . $this->lastError6;
+ }
+
+ if ($this->hasBeenResolved() && $this->ipsCount === 0) {
+ if ($this->lastError6 === $this->lastError4) {
+ $message = ' during DNS lookup: ' . $this->lastError6;
+ } else {
+ $message = ' during DNS lookup. ' . $message;
+ }
+ } else {
+ $message = ': ' . $message;
+ }
+
+ return 'Connection to ' . $this->uri . ' failed' . $message;
+ }
+}
diff --git a/vendor/react/socket/src/HappyEyeBallsConnector.php b/vendor/react/socket/src/HappyEyeBallsConnector.php
new file mode 100644
index 0000000..4b04f77
--- /dev/null
+++ b/vendor/react/socket/src/HappyEyeBallsConnector.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace React\Socket;
+
+use React\Dns\Resolver\ResolverInterface;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise;
+
+final class HappyEyeBallsConnector implements ConnectorInterface
+{
+ private $loop;
+ private $connector;
+ private $resolver;
+
+ public function __construct(LoopInterface $loop = null, ConnectorInterface $connector = null, ResolverInterface $resolver = null)
+ {
+ // $connector and $resolver arguments are actually required, marked
+ // optional for technical reasons only. Nullable $loop without default
+ // requires PHP 7.1, null default is also supported in legacy PHP
+ // versions, but required parameters are not allowed after arguments
+ // with null default. Mark all parameters optional and check accordingly.
+ if ($connector === null || $resolver === null) {
+ throw new \InvalidArgumentException('Missing required $connector or $resolver argument');
+ }
+
+ $this->loop = $loop ?: Loop::get();
+ $this->connector = $connector;
+ $this->resolver = $resolver;
+ }
+
+ public function connect($uri)
+ {
+ $original = $uri;
+ if (\strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ $parts = \parse_url($uri);
+ if (isset($parts['scheme'])) {
+ unset($parts['scheme']);
+ }
+ } else {
+ $parts = \parse_url($uri);
+ }
+
+ if (!$parts || !isset($parts['host'])) {
+ return Promise\reject(new \InvalidArgumentException(
+ 'Given URI "' . $original . '" is invalid (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ ));
+ }
+
+ $host = \trim($parts['host'], '[]');
+
+ // skip DNS lookup / URI manipulation if this URI already contains an IP
+ if (@\inet_pton($host) !== false) {
+ return $this->connector->connect($original);
+ }
+
+ $builder = new HappyEyeBallsConnectionBuilder(
+ $this->loop,
+ $this->connector,
+ $this->resolver,
+ $uri,
+ $host,
+ $parts
+ );
+ return $builder->connect();
+ }
+}
diff --git a/vendor/react/socket/src/LimitingServer.php b/vendor/react/socket/src/LimitingServer.php
new file mode 100644
index 0000000..d19000b
--- /dev/null
+++ b/vendor/react/socket/src/LimitingServer.php
@@ -0,0 +1,203 @@
+<?php
+
+namespace React\Socket;
+
+use Evenement\EventEmitter;
+use Exception;
+use OverflowException;
+
+/**
+ * The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible
+ * for limiting and keeping track of open connections to this server instance.
+ *
+ * Whenever the underlying server emits a `connection` event, it will check its
+ * limits and then either
+ * - keep track of this connection by adding it to the list of
+ * open connections and then forward the `connection` event
+ * - or reject (close) the connection when its limits are exceeded and will
+ * forward an `error` event instead.
+ *
+ * Whenever a connection closes, it will remove this connection from the list of
+ * open connections.
+ *
+ * ```php
+ * $server = new React\Socket\LimitingServer($server, 100);
+ * $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+class LimitingServer extends EventEmitter implements ServerInterface
+{
+ private $connections = array();
+ private $server;
+ private $limit;
+
+ private $pauseOnLimit = false;
+ private $autoPaused = false;
+ private $manuPaused = false;
+
+ /**
+ * Instantiates a new LimitingServer.
+ *
+ * You have to pass a maximum number of open connections to ensure
+ * the server will automatically reject (close) connections once this limit
+ * is exceeded. In this case, it will emit an `error` event to inform about
+ * this and no `connection` event will be emitted.
+ *
+ * ```php
+ * $server = new React\Socket\LimitingServer($server, 100);
+ * $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * You MAY pass a `null` limit in order to put no limit on the number of
+ * open connections and keep accepting new connection until you run out of
+ * operating system resources (such as open file handles). This may be
+ * useful if you do not want to take care of applying a limit but still want
+ * to use the `getConnections()` method.
+ *
+ * You can optionally configure the server to pause accepting new
+ * connections once the connection limit is reached. In this case, it will
+ * pause the underlying server and no longer process any new connections at
+ * all, thus also no longer closing any excessive connections.
+ * The underlying operating system is responsible for keeping a backlog of
+ * pending connections until its limit is reached, at which point it will
+ * start rejecting further connections.
+ * Once the server is below the connection limit, it will continue consuming
+ * connections from the backlog and will process any outstanding data on
+ * each connection.
+ * This mode may be useful for some protocols that are designed to wait for
+ * a response message (such as HTTP), but may be less useful for other
+ * protocols that demand immediate responses (such as a "welcome" message in
+ * an interactive chat).
+ *
+ * ```php
+ * $server = new React\Socket\LimitingServer($server, 100, true);
+ * $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * @param ServerInterface $server
+ * @param int|null $connectionLimit
+ * @param bool $pauseOnLimit
+ */
+ public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false)
+ {
+ $this->server = $server;
+ $this->limit = $connectionLimit;
+ if ($connectionLimit !== null) {
+ $this->pauseOnLimit = $pauseOnLimit;
+ }
+
+ $this->server->on('connection', array($this, 'handleConnection'));
+ $this->server->on('error', array($this, 'handleError'));
+ }
+
+ /**
+ * Returns an array with all currently active connections
+ *
+ * ```php
+ * foreach ($server->getConnection() as $connection) {
+ * $connection->write('Hi!');
+ * }
+ * ```
+ *
+ * @return ConnectionInterface[]
+ */
+ public function getConnections()
+ {
+ return $this->connections;
+ }
+
+ public function getAddress()
+ {
+ return $this->server->getAddress();
+ }
+
+ public function pause()
+ {
+ if (!$this->manuPaused) {
+ $this->manuPaused = true;
+
+ if (!$this->autoPaused) {
+ $this->server->pause();
+ }
+ }
+ }
+
+ public function resume()
+ {
+ if ($this->manuPaused) {
+ $this->manuPaused = false;
+
+ if (!$this->autoPaused) {
+ $this->server->resume();
+ }
+ }
+ }
+
+ public function close()
+ {
+ $this->server->close();
+ }
+
+ /** @internal */
+ public function handleConnection(ConnectionInterface $connection)
+ {
+ // close connection if limit exceeded
+ if ($this->limit !== null && \count($this->connections) >= $this->limit) {
+ $this->handleError(new \OverflowException('Connection closed because server reached connection limit'));
+ $connection->close();
+ return;
+ }
+
+ $this->connections[] = $connection;
+ $that = $this;
+ $connection->on('close', function () use ($that, $connection) {
+ $that->handleDisconnection($connection);
+ });
+
+ // pause accepting new connections if limit exceeded
+ if ($this->pauseOnLimit && !$this->autoPaused && \count($this->connections) >= $this->limit) {
+ $this->autoPaused = true;
+
+ if (!$this->manuPaused) {
+ $this->server->pause();
+ }
+ }
+
+ $this->emit('connection', array($connection));
+ }
+
+ /** @internal */
+ public function handleDisconnection(ConnectionInterface $connection)
+ {
+ unset($this->connections[\array_search($connection, $this->connections)]);
+
+ // continue accepting new connection if below limit
+ if ($this->autoPaused && \count($this->connections) < $this->limit) {
+ $this->autoPaused = false;
+
+ if (!$this->manuPaused) {
+ $this->server->resume();
+ }
+ }
+ }
+
+ /** @internal */
+ public function handleError(\Exception $error)
+ {
+ $this->emit('error', array($error));
+ }
+}
diff --git a/vendor/react/socket/src/SecureConnector.php b/vendor/react/socket/src/SecureConnector.php
new file mode 100644
index 0000000..03c6e36
--- /dev/null
+++ b/vendor/react/socket/src/SecureConnector.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace React\Socket;
+
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise;
+use BadMethodCallException;
+use InvalidArgumentException;
+use UnexpectedValueException;
+
+final class SecureConnector implements ConnectorInterface
+{
+ private $connector;
+ private $streamEncryption;
+ private $context;
+
+ public function __construct(ConnectorInterface $connector, LoopInterface $loop = null, array $context = array())
+ {
+ $this->connector = $connector;
+ $this->streamEncryption = new StreamEncryption($loop ?: Loop::get(), false);
+ $this->context = $context;
+ }
+
+ public function connect($uri)
+ {
+ if (!\function_exists('stream_socket_enable_crypto')) {
+ return Promise\reject(new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); // @codeCoverageIgnore
+ }
+
+ if (\strpos($uri, '://') === false) {
+ $uri = 'tls://' . $uri;
+ }
+
+ $parts = \parse_url($uri);
+ if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') {
+ return Promise\reject(new \InvalidArgumentException(
+ 'Given URI "' . $uri . '" is invalid (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ ));
+ }
+
+ $context = $this->context;
+ $encryption = $this->streamEncryption;
+ $connected = false;
+ $promise = $this->connector->connect(
+ \str_replace('tls://', '', $uri)
+ )->then(function (ConnectionInterface $connection) use ($context, $encryption, $uri, &$promise, &$connected) {
+ // (unencrypted) TCP/IP connection succeeded
+ $connected = true;
+
+ if (!$connection instanceof Connection) {
+ $connection->close();
+ throw new \UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource');
+ }
+
+ // set required SSL/TLS context options
+ foreach ($context as $name => $value) {
+ \stream_context_set_option($connection->stream, 'ssl', $name, $value);
+ }
+
+ // try to enable encryption
+ return $promise = $encryption->enable($connection)->then(null, function ($error) use ($connection, $uri) {
+ // establishing encryption failed => close invalid connection and return error
+ $connection->close();
+
+ throw new \RuntimeException(
+ 'Connection to ' . $uri . ' failed during TLS handshake: ' . $error->getMessage(),
+ $error->getCode()
+ );
+ });
+ }, function (\Exception $e) use ($uri) {
+ if ($e instanceof \RuntimeException) {
+ $message = \preg_replace('/^Connection to [^ ]+/', '', $e->getMessage());
+ $e = new \RuntimeException(
+ 'Connection to ' . $uri . $message,
+ $e->getCode(),
+ $e
+ );
+
+ // avoid garbage references by replacing all closures in call stack.
+ // what a lovely piece of code!
+ $r = new \ReflectionProperty('Exception', 'trace');
+ $r->setAccessible(true);
+ $trace = $r->getValue($e);
+
+ // Exception trace arguments are not available on some PHP 7.4 installs
+ // @codeCoverageIgnoreStart
+ foreach ($trace as &$one) {
+ if (isset($one['args'])) {
+ foreach ($one['args'] as &$arg) {
+ if ($arg instanceof \Closure) {
+ $arg = 'Object(' . \get_class($arg) . ')';
+ }
+ }
+ }
+ }
+ // @codeCoverageIgnoreEnd
+ $r->setValue($e, $trace);
+ }
+
+ throw $e;
+ });
+
+ return new \React\Promise\Promise(
+ function ($resolve, $reject) use ($promise) {
+ $promise->then($resolve, $reject);
+ },
+ function ($_, $reject) use (&$promise, $uri, &$connected) {
+ if ($connected) {
+ $reject(new \RuntimeException(
+ 'Connection to ' . $uri . ' cancelled during TLS handshake (ECONNABORTED)',
+ \defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
+ ));
+ }
+
+ $promise->cancel();
+ $promise = null;
+ }
+ );
+ }
+}
diff --git a/vendor/react/socket/src/SecureServer.php b/vendor/react/socket/src/SecureServer.php
new file mode 100644
index 0000000..d0525c9
--- /dev/null
+++ b/vendor/react/socket/src/SecureServer.php
@@ -0,0 +1,206 @@
+<?php
+
+namespace React\Socket;
+
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use BadMethodCallException;
+use UnexpectedValueException;
+
+/**
+ * The `SecureServer` class implements the `ServerInterface` and is responsible
+ * for providing a secure TLS (formerly known as SSL) server.
+ *
+ * It does so by wrapping a `TcpServer` instance which waits for plaintext
+ * TCP/IP connections and then performs a TLS handshake for each connection.
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer(8000);
+ * $server = new React\Socket\SecureServer($server, null, array(
+ * // tls context options here…
+ * ));
+ * ```
+ *
+ * Whenever a client completes the TLS handshake, it will emit a `connection` event
+ * with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
+ *
+ * ```php
+ * $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
+ * echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
+ *
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * Whenever a client fails to perform a successful TLS handshake, it will emit an
+ * `error` event and then close the underlying TCP/IP connection:
+ *
+ * ```php
+ * $server->on('error', function (Exception $e) {
+ * echo 'Error' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * Note that the `SecureServer` class is a concrete implementation for TLS sockets.
+ * If you want to typehint in your higher-level protocol implementation, you SHOULD
+ * use the generic `ServerInterface` instead.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class SecureServer extends EventEmitter implements ServerInterface
+{
+ private $tcp;
+ private $encryption;
+ private $context;
+
+ /**
+ * Creates a secure TLS server and starts waiting for incoming connections
+ *
+ * It does so by wrapping a `TcpServer` instance which waits for plaintext
+ * TCP/IP connections and then performs a TLS handshake for each connection.
+ * It thus requires valid [TLS context options],
+ * which in its most basic form may look something like this if you're using a
+ * PEM encoded certificate file:
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer(8000);
+ * $server = new React\Socket\SecureServer($server, null, array(
+ * 'local_cert' => 'server.pem'
+ * ));
+ * ```
+ *
+ * Note that the certificate file will not be loaded on instantiation but when an
+ * incoming connection initializes its TLS context.
+ * This implies that any invalid certificate file paths or contents will only cause
+ * an `error` event at a later time.
+ *
+ * If your private key is encrypted with a passphrase, you have to specify it
+ * like this:
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer(8000);
+ * $server = new React\Socket\SecureServer($server, null, array(
+ * 'local_cert' => 'server.pem',
+ * 'passphrase' => 'secret'
+ * ));
+ * ```
+ *
+ * Note that available [TLS context options],
+ * their defaults and effects of changing these may vary depending on your system
+ * and/or PHP version.
+ * Passing unknown context options has no effect.
+ *
+ * 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.
+ *
+ * Advanced usage: Despite allowing any `ServerInterface` as first parameter,
+ * you SHOULD pass a `TcpServer` instance as first parameter, unless you
+ * know what you're doing.
+ * Internally, the `SecureServer` has to set the required TLS context options on
+ * the underlying stream resources.
+ * These resources are not exposed through any of the interfaces defined in this
+ * package, but only through the internal `Connection` class.
+ * The `TcpServer` class is guaranteed to emit connections that implement
+ * the `ConnectionInterface` and uses the internal `Connection` class in order to
+ * expose these underlying resources.
+ * If you use a custom `ServerInterface` and its `connection` event does not
+ * meet this requirement, the `SecureServer` will emit an `error` event and
+ * then close the underlying connection.
+ *
+ * @param ServerInterface|TcpServer $tcp
+ * @param ?LoopInterface $loop
+ * @param array $context
+ * @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support
+ * @see TcpServer
+ * @link https://www.php.net/manual/en/context.ssl.php for TLS context options
+ */
+ public function __construct(ServerInterface $tcp, LoopInterface $loop = null, array $context = array())
+ {
+ if (!\function_exists('stream_socket_enable_crypto')) {
+ throw new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore
+ }
+
+ // default to empty passphrase to suppress blocking passphrase prompt
+ $context += array(
+ 'passphrase' => ''
+ );
+
+ $this->tcp = $tcp;
+ $this->encryption = new StreamEncryption($loop ?: Loop::get());
+ $this->context = $context;
+
+ $that = $this;
+ $this->tcp->on('connection', function ($connection) use ($that) {
+ $that->handleConnection($connection);
+ });
+ $this->tcp->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ public function getAddress()
+ {
+ $address = $this->tcp->getAddress();
+ if ($address === null) {
+ return null;
+ }
+
+ return \str_replace('tcp://' , 'tls://', $address);
+ }
+
+ public function pause()
+ {
+ $this->tcp->pause();
+ }
+
+ public function resume()
+ {
+ $this->tcp->resume();
+ }
+
+ public function close()
+ {
+ return $this->tcp->close();
+ }
+
+ /** @internal */
+ public function handleConnection(ConnectionInterface $connection)
+ {
+ if (!$connection instanceof Connection) {
+ $this->emit('error', array(new \UnexpectedValueException('Base server does not use internal Connection class exposing stream resource')));
+ $connection->close();
+ return;
+ }
+
+ foreach ($this->context as $name => $value) {
+ \stream_context_set_option($connection->stream, 'ssl', $name, $value);
+ }
+
+ // get remote address before starting TLS handshake in case connection closes during handshake
+ $remote = $connection->getRemoteAddress();
+ $that = $this;
+
+ $this->encryption->enable($connection)->then(
+ function ($conn) use ($that) {
+ $that->emit('connection', array($conn));
+ },
+ function ($error) use ($that, $connection, $remote) {
+ $error = new \RuntimeException(
+ 'Connection from ' . $remote . ' failed during TLS handshake: ' . $error->getMessage(),
+ $error->getCode()
+ );
+
+ $that->emit('error', array($error));
+ $connection->close();
+ }
+ );
+ }
+}
diff --git a/vendor/react/socket/src/Server.php b/vendor/react/socket/src/Server.php
new file mode 100644
index 0000000..7d4111e
--- /dev/null
+++ b/vendor/react/socket/src/Server.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace React\Socket;
+
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use Exception;
+
+/**
+ * @deprecated 1.9.0 See `SocketServer` instead
+ * @see SocketServer
+ */
+final class Server extends EventEmitter implements ServerInterface
+{
+ private $server;
+
+ /**
+ * [Deprecated] `Server`
+ *
+ * This class exists for BC reasons only and should not be used anymore.
+ *
+ * ```php
+ * // deprecated
+ * $socket = new React\Socket\Server(0);
+ * $socket = new React\Socket\Server('127.0.0.1:8000');
+ * $socket = new React\Socket\Server('127.0.0.1:8000', null, $context);
+ * $socket = new React\Socket\Server('127.0.0.1:8000', $loop, $context);
+ *
+ * // new
+ * $socket = new React\Socket\SocketServer('127.0.0.1:0');
+ * $socket = new React\Socket\SocketServer('127.0.0.1:8000');
+ * $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context);
+ * $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context, $loop);
+ * ```
+ *
+ * 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.
+ *
+ * For BC reasons, you can also pass the TCP socket context options as a simple
+ * array without wrapping this in another array under the `tcp` key.
+ *
+ * @param string|int $uri
+ * @param LoopInterface $loop
+ * @param array $context
+ * @deprecated 1.9.0 See `SocketServer` instead
+ * @see SocketServer
+ */
+ public function __construct($uri, LoopInterface $loop = null, array $context = array())
+ {
+ $loop = $loop ?: Loop::get();
+
+ // sanitize TCP context options if not properly wrapped
+ if ($context && (!isset($context['tcp']) && !isset($context['tls']) && !isset($context['unix']))) {
+ $context = array('tcp' => $context);
+ }
+
+ // apply default options if not explicitly given
+ $context += array(
+ 'tcp' => array(),
+ 'tls' => array(),
+ 'unix' => array()
+ );
+
+ $scheme = 'tcp';
+ $pos = \strpos($uri, '://');
+ if ($pos !== false) {
+ $scheme = \substr($uri, 0, $pos);
+ }
+
+ if ($scheme === 'unix') {
+ $server = new UnixServer($uri, $loop, $context['unix']);
+ } else {
+ $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
+
+ if ($scheme === 'tls') {
+ $server = new SecureServer($server, $loop, $context['tls']);
+ }
+ }
+
+ $this->server = $server;
+
+ $that = $this;
+ $server->on('connection', function (ConnectionInterface $conn) use ($that) {
+ $that->emit('connection', array($conn));
+ });
+ $server->on('error', function (Exception $error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ public function getAddress()
+ {
+ return $this->server->getAddress();
+ }
+
+ public function pause()
+ {
+ $this->server->pause();
+ }
+
+ public function resume()
+ {
+ $this->server->resume();
+ }
+
+ public function close()
+ {
+ $this->server->close();
+ }
+}
diff --git a/vendor/react/socket/src/ServerInterface.php b/vendor/react/socket/src/ServerInterface.php
new file mode 100644
index 0000000..aa79fa1
--- /dev/null
+++ b/vendor/react/socket/src/ServerInterface.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace React\Socket;
+
+use Evenement\EventEmitterInterface;
+
+/**
+ * The `ServerInterface` is responsible for providing an interface for accepting
+ * incoming streaming connections, such as a normal TCP/IP connection.
+ *
+ * Most higher-level components (such as a HTTP server) accept an instance
+ * implementing this interface to accept incoming streaming connections.
+ * This is usually done via dependency injection, so it's fairly simple to actually
+ * swap this implementation against any other implementation of this interface.
+ * This means that you SHOULD typehint against this interface instead of a concrete
+ * implementation of this interface.
+ *
+ * Besides defining a few methods, this interface also implements the
+ * `EventEmitterInterface` which allows you to react to certain events:
+ *
+ * connection event:
+ * The `connection` event will be emitted whenever a new connection has been
+ * established, i.e. a new client connects to this server socket:
+ *
+ * ```php
+ * $socket->on('connection', function (React\Socket\ConnectionInterface $connection) {
+ * echo 'new connection' . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also the `ConnectionInterface` for more details about handling the
+ * incoming connection.
+ *
+ * error event:
+ * The `error` event will be emitted whenever there's an error accepting a new
+ * connection from a client.
+ *
+ * ```php
+ * $socket->on('error', function (Exception $e) {
+ * echo 'error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * Note that this is not a fatal error event, i.e. the server keeps listening for
+ * new connections even after this event.
+ *
+ * @see ConnectionInterface
+ */
+interface ServerInterface extends EventEmitterInterface
+{
+ /**
+ * Returns the full address (URI) this server is currently listening on
+ *
+ * ```php
+ * $address = $socket->getAddress();
+ * echo 'Server listening on ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the address can not be determined or is unknown at this time (such as
+ * after the socket has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80` or `tls://127.0.0.1:443`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * If this is a TCP/IP based server and you only want the local port, you may
+ * use something like this:
+ *
+ * ```php
+ * $address = $socket->getAddress();
+ * $port = parse_url($address, PHP_URL_PORT);
+ * echo 'Server listening on port ' . $port . PHP_EOL;
+ * ```
+ *
+ * @return ?string the full listening address (URI) or NULL if it is unknown (not applicable to this server socket or already closed)
+ */
+ public function getAddress();
+
+ /**
+ * Pauses accepting new incoming connections.
+ *
+ * Removes the socket resource from the EventLoop and thus stop accepting
+ * new connections. Note that the listening socket stays active and is not
+ * closed.
+ *
+ * This means that new incoming connections will stay pending in the
+ * operating system backlog until its configurable backlog is filled.
+ * Once the backlog is filled, the operating system may reject further
+ * incoming connections until the backlog is drained again by resuming
+ * to accept new connections.
+ *
+ * Once the server is paused, no futher `connection` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $socket->pause();
+ *
+ * $socket->on('connection', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * server MAY continue emitting `connection` events.
+ *
+ * Unless otherwise noted, a successfully opened server SHOULD NOT start
+ * in paused state.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ * Similarly, calling this after `close()` is a NO-OP.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes accepting new incoming connections.
+ *
+ * Re-attach the socket resource to the EventLoop after a previous `pause()`.
+ *
+ * ```php
+ * $socket->pause();
+ *
+ * Loop::addTimer(1.0, function () use ($socket) {
+ * $socket->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ * Similarly, calling this after `close()` is a NO-OP.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Shuts down this listening socket
+ *
+ * This will stop listening for new incoming connections on this socket.
+ *
+ * Calling this method more than once on the same instance is a NO-OP.
+ *
+ * @return void
+ */
+ public function close();
+}
diff --git a/vendor/react/socket/src/SocketServer.php b/vendor/react/socket/src/SocketServer.php
new file mode 100644
index 0000000..2ea03ba
--- /dev/null
+++ b/vendor/react/socket/src/SocketServer.php
@@ -0,0 +1,187 @@
+<?php
+
+namespace React\Socket;
+
+use Evenement\EventEmitter;
+use React\EventLoop\LoopInterface;
+
+final class SocketServer extends EventEmitter implements ServerInterface
+{
+ private $server;
+
+ /**
+ * The `SocketServer` class is the main class in this package that implements the `ServerInterface` and
+ * allows you to accept incoming streaming connections, such as plaintext TCP/IP or secure TLS connection streams.
+ *
+ * ```php
+ * $socket = new React\Socket\SocketServer('127.0.0.1:0');
+ * $socket = new React\Socket\SocketServer('127.0.0.1:8000');
+ * $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context);
+ * ```
+ *
+ * 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 string $uri
+ * @param array $context
+ * @param ?LoopInterface $loop
+ * @throws \InvalidArgumentException if the listening address is invalid
+ * @throws \RuntimeException if listening on this address fails (already in use etc.)
+ */
+ public function __construct($uri, array $context = array(), LoopInterface $loop = null)
+ {
+ // apply default options if not explicitly given
+ $context += array(
+ 'tcp' => array(),
+ 'tls' => array(),
+ 'unix' => array()
+ );
+
+ $scheme = 'tcp';
+ $pos = \strpos($uri, '://');
+ if ($pos !== false) {
+ $scheme = \substr($uri, 0, $pos);
+ }
+
+ if ($scheme === 'unix') {
+ $server = new UnixServer($uri, $loop, $context['unix']);
+ } elseif ($scheme === 'php') {
+ $server = new FdServer($uri, $loop);
+ } else {
+ if (preg_match('#^(?:\w+://)?\d+$#', $uri)) {
+ throw new \InvalidArgumentException(
+ 'Invalid URI given (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ );
+ }
+
+ $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
+
+ if ($scheme === 'tls') {
+ $server = new SecureServer($server, $loop, $context['tls']);
+ }
+ }
+
+ $this->server = $server;
+
+ $that = $this;
+ $server->on('connection', function (ConnectionInterface $conn) use ($that) {
+ $that->emit('connection', array($conn));
+ });
+ $server->on('error', function (\Exception $error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ public function getAddress()
+ {
+ return $this->server->getAddress();
+ }
+
+ public function pause()
+ {
+ $this->server->pause();
+ }
+
+ public function resume()
+ {
+ $this->server->resume();
+ }
+
+ public function close()
+ {
+ $this->server->close();
+ }
+
+ /**
+ * [internal] Internal helper method to accept new connection from given server socket
+ *
+ * @param resource $socket server socket to accept connection from
+ * @return resource new client socket if any
+ * @throws \RuntimeException if accepting fails
+ * @internal
+ */
+ public static function accept($socket)
+ {
+ $newSocket = @\stream_socket_accept($socket, 0);
+
+ if (false === $newSocket) {
+ // Match errstr from PHP's warning message.
+ // stream_socket_accept(): accept failed: Connection timed out
+ $error = \error_get_last();
+ $errstr = \preg_replace('#.*: #', '', $error['message']);
+ $errno = self::errno($errstr);
+
+ throw new \RuntimeException(
+ 'Unable to accept new connection: ' . $errstr . self::errconst($errno),
+ $errno
+ );
+ }
+
+ return $newSocket;
+ }
+
+ /**
+ * [Internal] Returns errno value for given errstr
+ *
+ * The errno and errstr values describes the type of error that has been
+ * encountered. This method tries to look up the given errstr and find a
+ * matching errno value which can be useful to provide more context to error
+ * messages. It goes through the list of known errno constants when
+ * ext-sockets is available to find an errno matching the given errstr.
+ *
+ * @param string $errstr
+ * @return int errno value (e.g. value of `SOCKET_ECONNREFUSED`) or 0 if not found
+ * @internal
+ * @copyright Copyright (c) 2018 Christian Lück, taken from https://github.com/clue/errno with permission
+ * @codeCoverageIgnore
+ */
+ public static function errno($errstr)
+ {
+ if (\function_exists('socket_strerror')) {
+ foreach (\get_defined_constants(false) as $name => $value) {
+ if (\strpos($name, 'SOCKET_E') === 0 && \socket_strerror($value) === $errstr) {
+ return $value;
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * [Internal] Returns errno constant name for given errno value
+ *
+ * The errno value describes the type of error that has been encountered.
+ * This method tries to look up the given errno value and find a matching
+ * errno constant name which can be useful to provide more context and more
+ * descriptive error messages. It goes through the list of known errno
+ * constants when ext-sockets is available to find the matching errno
+ * constant name.
+ *
+ * Because this method is used to append more context to error messages, the
+ * constant name will be prefixed with a space and put between parenthesis
+ * when found.
+ *
+ * @param int $errno
+ * @return string e.g. ` (ECONNREFUSED)` or empty string if no matching const for the given errno could be found
+ * @internal
+ * @copyright Copyright (c) 2018 Christian Lück, taken from https://github.com/clue/errno with permission
+ * @codeCoverageIgnore
+ */
+ public static function errconst($errno)
+ {
+ if (\function_exists('socket_strerror')) {
+ foreach (\get_defined_constants(false) as $name => $value) {
+ if ($value === $errno && \strpos($name, 'SOCKET_E') === 0) {
+ return ' (' . \substr($name, 7) . ')';
+ }
+ }
+ }
+
+ return '';
+ }
+}
diff --git a/vendor/react/socket/src/StreamEncryption.php b/vendor/react/socket/src/StreamEncryption.php
new file mode 100644
index 0000000..4aa7fca
--- /dev/null
+++ b/vendor/react/socket/src/StreamEncryption.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace React\Socket;
+
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+use RuntimeException;
+use UnexpectedValueException;
+
+/**
+ * This class is considered internal and its API should not be relied upon
+ * outside of Socket.
+ *
+ * @internal
+ */
+class StreamEncryption
+{
+ private $loop;
+ private $method;
+ private $server;
+
+ public function __construct(LoopInterface $loop, $server = true)
+ {
+ $this->loop = $loop;
+ $this->server = $server;
+
+ // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3.
+ // As of PHP 7.2+ the main crypto method constant includes all TLS versions.
+ // As of PHP 5.6+ the crypto method is a bitmask, so we explicitly include all TLS versions.
+ // For legacy PHP < 5.6 the crypto method is a single value only and this constant includes all TLS versions.
+ // @link https://3v4l.org/9PSST
+ if ($server) {
+ $this->method = \STREAM_CRYPTO_METHOD_TLS_SERVER;
+
+ if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) {
+ $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; // @codeCoverageIgnore
+ }
+ } else {
+ $this->method = \STREAM_CRYPTO_METHOD_TLS_CLIENT;
+
+ if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) {
+ $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; // @codeCoverageIgnore
+ }
+ }
+ }
+
+ public function enable(Connection $stream)
+ {
+ return $this->toggle($stream, true);
+ }
+
+ public function toggle(Connection $stream, $toggle)
+ {
+ // pause actual stream instance to continue operation on raw stream socket
+ $stream->pause();
+
+ // TODO: add write() event to make sure we're not sending any excessive data
+
+ // cancelling this leaves this stream in an inconsistent state…
+ $deferred = new Deferred(function () {
+ throw new \RuntimeException();
+ });
+
+ // get actual stream socket from stream instance
+ $socket = $stream->stream;
+
+ // get crypto method from context options or use global setting from constructor
+ $method = $this->method;
+ $context = \stream_context_get_options($socket);
+ if (isset($context['ssl']['crypto_method'])) {
+ $method = $context['ssl']['crypto_method'];
+ }
+
+ $that = $this;
+ $toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) {
+ $that->toggleCrypto($socket, $deferred, $toggle, $method);
+ };
+
+ $this->loop->addReadStream($socket, $toggleCrypto);
+
+ if (!$this->server) {
+ $toggleCrypto();
+ }
+
+ $loop = $this->loop;
+
+ return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) {
+ $loop->removeReadStream($socket);
+
+ $stream->encryptionEnabled = $toggle;
+ $stream->resume();
+
+ return $stream;
+ }, function($error) use ($stream, $socket, $loop) {
+ $loop->removeReadStream($socket);
+ $stream->resume();
+ throw $error;
+ });
+ }
+
+ public function toggleCrypto($socket, Deferred $deferred, $toggle, $method)
+ {
+ $error = null;
+ \set_error_handler(function ($_, $errstr) use (&$error) {
+ $error = \str_replace(array("\r", "\n"), ' ', $errstr);
+
+ // remove useless function name from error message
+ if (($pos = \strpos($error, "): ")) !== false) {
+ $error = \substr($error, $pos + 3);
+ }
+ });
+
+ $result = \stream_socket_enable_crypto($socket, $toggle, $method);
+
+ \restore_error_handler();
+
+ if (true === $result) {
+ $deferred->resolve();
+ } else if (false === $result) {
+ // overwrite callback arguments for PHP7+ only, so they do not show
+ // up in the Exception trace and do not cause a possible cyclic reference.
+ $d = $deferred;
+ $deferred = null;
+
+ if (\feof($socket) || $error === null) {
+ // EOF or failed without error => connection closed during handshake
+ $d->reject(new \UnexpectedValueException(
+ 'Connection lost during TLS handshake (ECONNRESET)',
+ \defined('SOCKET_ECONNRESET') ? \SOCKET_ECONNRESET : 104
+ ));
+ } else {
+ // handshake failed with error message
+ $d->reject(new \UnexpectedValueException(
+ $error
+ ));
+ }
+ } else {
+ // need more data, will retry
+ }
+ }
+}
diff --git a/vendor/react/socket/src/TcpConnector.php b/vendor/react/socket/src/TcpConnector.php
new file mode 100644
index 0000000..a4d3b5b
--- /dev/null
+++ b/vendor/react/socket/src/TcpConnector.php
@@ -0,0 +1,159 @@
+<?php
+
+namespace React\Socket;
+
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise;
+use InvalidArgumentException;
+use RuntimeException;
+
+final class TcpConnector implements ConnectorInterface
+{
+ private $loop;
+ private $context;
+
+ public function __construct(LoopInterface $loop = null, array $context = array())
+ {
+ $this->loop = $loop ?: Loop::get();
+ $this->context = $context;
+ }
+
+ 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(
+ 'Given URI "' . $uri . '" is invalid (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ ));
+ }
+
+ $ip = \trim($parts['host'], '[]');
+ if (@\inet_pton($ip) === false) {
+ return Promise\reject(new \InvalidArgumentException(
+ 'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ ));
+ }
+
+ // use context given in constructor
+ $context = array(
+ 'socket' => $this->context
+ );
+
+ // parse arguments from query component of URI
+ $args = array();
+ if (isset($parts['query'])) {
+ \parse_str($parts['query'], $args);
+ }
+
+ // If an original hostname has been given, use this for TLS setup.
+ // This can happen due to layers of nested connectors, such as a
+ // DnsConnector reporting its original hostname.
+ // These context options are here in case TLS is enabled later on this stream.
+ // If TLS is not enabled later, this doesn't hurt either.
+ if (isset($args['hostname'])) {
+ $context['ssl'] = array(
+ 'SNI_enabled' => true,
+ 'peer_name' => $args['hostname']
+ );
+
+ // Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead.
+ // The SNI_server_name context option has to be set here during construction,
+ // as legacy PHP ignores any values set later.
+ // @codeCoverageIgnoreStart
+ if (\PHP_VERSION_ID < 50600) {
+ $context['ssl'] += array(
+ 'SNI_server_name' => $args['hostname'],
+ 'CN_match' => $args['hostname']
+ );
+ }
+ // @codeCoverageIgnoreEnd
+ }
+
+ // latest versions of PHP no longer accept any other URI components and
+ // HHVM fails to parse URIs with a query but no path, so let's simplify our URI here
+ $remote = 'tcp://' . $parts['host'] . ':' . $parts['port'];
+
+ $stream = @\stream_socket_client(
+ $remote,
+ $errno,
+ $errstr,
+ 0,
+ \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT,
+ \stream_context_create($context)
+ );
+
+ if (false === $stream) {
+ return Promise\reject(new \RuntimeException(
+ 'Connection to ' . $uri . ' failed: ' . $errstr . SocketServer::errconst($errno),
+ $errno
+ ));
+ }
+
+ // wait for connection
+ $loop = $this->loop;
+ return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream, $uri) {
+ $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject, $uri) {
+ $loop->removeWriteStream($stream);
+
+ // The following hack looks like the only way to
+ // detect connection refused errors with PHP's stream sockets.
+ if (false === \stream_socket_get_name($stream, true)) {
+ // If we reach this point, we know the connection is dead, but we don't know the underlying error condition.
+ // @codeCoverageIgnoreStart
+ if (\function_exists('socket_import_stream')) {
+ // actual socket errno and errstr can be retrieved with ext-sockets on PHP 5.4+
+ $socket = \socket_import_stream($stream);
+ $errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
+ $errstr = \socket_strerror($errno);
+ } elseif (\PHP_OS === 'Linux') {
+ // Linux reports socket errno and errstr again when trying to write to the dead socket.
+ // Suppress error reporting to get error message below and close dead socket before rejecting.
+ // This is only known to work on Linux, Mac and Windows are known to not support this.
+ @\fwrite($stream, \PHP_EOL);
+ $error = \error_get_last();
+
+ // fwrite(): send of 2 bytes failed with errno=111 Connection refused
+ \preg_match('/errno=(\d+) (.+)/', $error['message'], $m);
+ $errno = isset($m[1]) ? (int) $m[1] : 0;
+ $errstr = isset($m[2]) ? $m[2] : $error['message'];
+ } else {
+ // Not on Linux and ext-sockets not available? Too bad.
+ $errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;
+ $errstr = 'Connection refused?';
+ }
+ // @codeCoverageIgnoreEnd
+
+ \fclose($stream);
+ $reject(new \RuntimeException(
+ 'Connection to ' . $uri . ' failed: ' . $errstr . SocketServer::errconst($errno),
+ $errno
+ ));
+ } else {
+ $resolve(new Connection($stream, $loop));
+ }
+ });
+ }, function () use ($loop, $stream, $uri) {
+ $loop->removeWriteStream($stream);
+ \fclose($stream);
+
+ // @codeCoverageIgnoreStart
+ // legacy PHP 5.3 sometimes requires a second close call (see tests)
+ if (\PHP_VERSION_ID < 50400 && \is_resource($stream)) {
+ \fclose($stream);
+ }
+ // @codeCoverageIgnoreEnd
+
+ throw new \RuntimeException(
+ 'Connection to ' . $uri . ' cancelled during TCP/IP handshake (ECONNABORTED)',
+ \defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
+ );
+ });
+ }
+}
diff --git a/vendor/react/socket/src/TcpServer.php b/vendor/react/socket/src/TcpServer.php
new file mode 100644
index 0000000..442af70
--- /dev/null
+++ b/vendor/react/socket/src/TcpServer.php
@@ -0,0 +1,258 @@
+<?php
+
+namespace React\Socket;
+
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use InvalidArgumentException;
+use RuntimeException;
+
+/**
+ * The `TcpServer` class implements the `ServerInterface` and
+ * is responsible for accepting plaintext TCP/IP connections.
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer(8080);
+ * ```
+ *
+ * Whenever a client connects, it will emit a `connection` event with a connection
+ * instance implementing `ConnectionInterface`:
+ *
+ * ```php
+ * $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
+ * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class TcpServer extends EventEmitter implements ServerInterface
+{
+ private $master;
+ private $loop;
+ private $listening = false;
+
+ /**
+ * Creates a plaintext TCP/IP socket server and starts listening on the given address
+ *
+ * This starts accepting new incoming connections on the given address.
+ * See also the `connection event` documented in the `ServerInterface`
+ * for more details.
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer(8080);
+ * ```
+ *
+ * As above, the `$uri` parameter can consist of only a port, in which case the
+ * server will default to listening on the localhost address `127.0.0.1`,
+ * which means it will not be reachable from outside of this system.
+ *
+ * In order to use a random port assignment, you can use the port `0`:
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer(0);
+ * $address = $server->getAddress();
+ * ```
+ *
+ * In order to change the host the socket is listening on, you can provide an IP
+ * address through the first parameter provided to the constructor, optionally
+ * preceded by the `tcp://` scheme:
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer('192.168.0.1:8080');
+ * ```
+ *
+ * If you want to listen on an IPv6 address, you MUST enclose the host in square
+ * brackets:
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer('[::1]:8080');
+ * ```
+ *
+ * If the given URI is invalid, does not contain a port, any other scheme or if it
+ * contains a hostname, it will throw an `InvalidArgumentException`:
+ *
+ * ```php
+ * // throws InvalidArgumentException due to missing port
+ * $server = new React\Socket\TcpServer('127.0.0.1');
+ * ```
+ *
+ * If the given URI appears to be valid, but listening on it fails (such as if port
+ * is already in use or port below 1024 may require root access etc.), it will
+ * throw a `RuntimeException`:
+ *
+ * ```php
+ * $first = new React\Socket\TcpServer(8080);
+ *
+ * // throws RuntimeException because port is already in use
+ * $second = new React\Socket\TcpServer(8080);
+ * ```
+ *
+ * Note that these error conditions may vary depending on your system and/or
+ * configuration.
+ * See the exception message and code for more details about the actual error
+ * condition.
+ *
+ * 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.
+ *
+ * Optionally, you can specify [socket context options](https://www.php.net/manual/en/context.socket.php)
+ * for the underlying stream socket resource like this:
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer('[::1]:8080', null, array(
+ * 'backlog' => 200,
+ * 'so_reuseport' => true,
+ * 'ipv6_v6only' => true
+ * ));
+ * ```
+ *
+ * Note that available [socket context options](https://www.php.net/manual/en/context.socket.php),
+ * their defaults and effects of changing these may vary depending on your system
+ * and/or PHP version.
+ * Passing unknown context options has no effect.
+ * The `backlog` context option defaults to `511` unless given explicitly.
+ *
+ * @param string|int $uri
+ * @param ?LoopInterface $loop
+ * @param array $context
+ * @throws InvalidArgumentException if the listening address is invalid
+ * @throws RuntimeException if listening on this address fails (already in use etc.)
+ */
+ public function __construct($uri, LoopInterface $loop = null, array $context = array())
+ {
+ $this->loop = $loop ?: Loop::get();
+
+ // a single port has been given => assume localhost
+ if ((string)(int)$uri === (string)$uri) {
+ $uri = '127.0.0.1:' . $uri;
+ }
+
+ // assume default scheme if none has been given
+ if (\strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ // parse_url() does not accept null ports (random port assignment) => manually remove
+ if (\substr($uri, -2) === ':0') {
+ $parts = \parse_url(\substr($uri, 0, -2));
+ if ($parts) {
+ $parts['port'] = 0;
+ }
+ } else {
+ $parts = \parse_url($uri);
+ }
+
+ // ensure URI contains TCP scheme, host and port
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ throw new \InvalidArgumentException(
+ 'Invalid URI "' . $uri . '" given (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ );
+ }
+
+ if (@\inet_pton(\trim($parts['host'], '[]')) === false) {
+ throw new \InvalidArgumentException(
+ 'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ );
+ }
+
+ $this->master = @\stream_socket_server(
+ $uri,
+ $errno,
+ $errstr,
+ \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
+ \stream_context_create(array('socket' => $context + array('backlog' => 511)))
+ );
+ if (false === $this->master) {
+ if ($errno === 0) {
+ // PHP does not seem to report errno, so match errno from errstr
+ // @link https://3v4l.org/3qOBl
+ $errno = SocketServer::errno($errstr);
+ }
+
+ throw new \RuntimeException(
+ 'Failed to listen on "' . $uri . '": ' . $errstr . SocketServer::errconst($errno),
+ $errno
+ );
+ }
+ \stream_set_blocking($this->master, false);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!\is_resource($this->master)) {
+ return null;
+ }
+
+ $address = \stream_socket_get_name($this->master, false);
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = \strrpos($address, ':');
+ if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
+ $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
+ }
+
+ return 'tcp://' . $address;
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !\is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ try {
+ $newSocket = SocketServer::accept($master);
+ } catch (\RuntimeException $e) {
+ $that->emit('error', array($e));
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!\is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ \fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $this->emit('connection', array(
+ new Connection($socket, $this->loop)
+ ));
+ }
+}
diff --git a/vendor/react/socket/src/TimeoutConnector.php b/vendor/react/socket/src/TimeoutConnector.php
new file mode 100644
index 0000000..332369f
--- /dev/null
+++ b/vendor/react/socket/src/TimeoutConnector.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace React\Socket;
+
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise\Timer;
+use React\Promise\Timer\TimeoutException;
+
+final class TimeoutConnector implements ConnectorInterface
+{
+ private $connector;
+ private $timeout;
+ private $loop;
+
+ public function __construct(ConnectorInterface $connector, $timeout, LoopInterface $loop = null)
+ {
+ $this->connector = $connector;
+ $this->timeout = $timeout;
+ $this->loop = $loop ?: Loop::get();
+ }
+
+ public function connect($uri)
+ {
+ return Timer\timeout($this->connector->connect($uri), $this->timeout, $this->loop)->then(null, self::handler($uri));
+ }
+
+ /**
+ * Creates a static rejection handler that reports a proper error message in case of a timeout.
+ *
+ * This uses a private static helper method to ensure this closure is not
+ * bound to this instance and the exception trace does not include a
+ * reference to this instance and its connector stack as a result.
+ *
+ * @param string $uri
+ * @return callable
+ */
+ private static function handler($uri)
+ {
+ return function (\Exception $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;
+ };
+ }
+}
diff --git a/vendor/react/socket/src/UnixConnector.php b/vendor/react/socket/src/UnixConnector.php
new file mode 100644
index 0000000..513fb51
--- /dev/null
+++ b/vendor/react/socket/src/UnixConnector.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace React\Socket;
+
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise;
+use InvalidArgumentException;
+use RuntimeException;
+
+/**
+ * Unix domain socket connector
+ *
+ * Unix domain sockets use atomic operations, so we can as well emulate
+ * async behavior.
+ */
+final class UnixConnector implements ConnectorInterface
+{
+ private $loop;
+
+ public function __construct(LoopInterface $loop = null)
+ {
+ $this->loop = $loop ?: Loop::get();
+ }
+
+ public function connect($path)
+ {
+ if (\strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (\substr($path, 0, 7) !== 'unix://') {
+ return Promise\reject(new \InvalidArgumentException(
+ 'Given URI "' . $path . '" is invalid (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ ));
+ }
+
+ $resource = @\stream_socket_client($path, $errno, $errstr, 1.0);
+
+ if (!$resource) {
+ return Promise\reject(new \RuntimeException(
+ 'Unable to connect to unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno),
+ $errno
+ ));
+ }
+
+ $connection = new Connection($resource, $this->loop);
+ $connection->unix = true;
+
+ return Promise\resolve($connection);
+ }
+}
diff --git a/vendor/react/socket/src/UnixServer.php b/vendor/react/socket/src/UnixServer.php
new file mode 100644
index 0000000..668e8cb
--- /dev/null
+++ b/vendor/react/socket/src/UnixServer.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace React\Socket;
+
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use InvalidArgumentException;
+use RuntimeException;
+
+/**
+ * The `UnixServer` class implements the `ServerInterface` and
+ * is responsible for accepting plaintext connections on unix domain sockets.
+ *
+ * ```php
+ * $server = new React\Socket\UnixServer('unix:///tmp/app.sock');
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class UnixServer extends EventEmitter implements ServerInterface
+{
+ private $master;
+ private $loop;
+ private $listening = false;
+
+ /**
+ * Creates a plaintext socket server and starts listening on the given unix socket
+ *
+ * This starts accepting new incoming connections on the given address.
+ * See also the `connection event` documented in the `ServerInterface`
+ * for more details.
+ *
+ * ```php
+ * $server = new React\Socket\UnixServer('unix:///tmp/app.sock');
+ * ```
+ *
+ * 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 string $path
+ * @param ?LoopInterface $loop
+ * @param array $context
+ * @throws InvalidArgumentException if the listening address is invalid
+ * @throws RuntimeException if listening on this address fails (already in use etc.)
+ */
+ public function __construct($path, LoopInterface $loop = null, array $context = array())
+ {
+ $this->loop = $loop ?: Loop::get();
+
+ if (\strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (\substr($path, 0, 7) !== 'unix://') {
+ throw new \InvalidArgumentException(
+ 'Given URI "' . $path . '" is invalid (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
+ );
+ }
+
+ $this->master = @\stream_socket_server(
+ $path,
+ $errno,
+ $errstr,
+ \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
+ \stream_context_create(array('socket' => $context))
+ );
+ if (false === $this->master) {
+ // PHP does not seem to report errno/errstr for Unix domain sockets (UDS) right now.
+ // This only applies to UDS server sockets, see also https://3v4l.org/NAhpr.
+ // Parse PHP warning message containing unknown error, HHVM reports proper info at least.
+ if ($errno === 0 && $errstr === '') {
+ $error = \error_get_last();
+ if (\preg_match('/\(([^\)]+)\)|\[(\d+)\]: (.*)/', $error['message'], $match)) {
+ $errstr = isset($match[3]) ? $match['3'] : $match[1];
+ $errno = isset($match[2]) ? (int)$match[2] : 0;
+ }
+ }
+
+ throw new \RuntimeException(
+ 'Failed to listen on Unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno),
+ $errno
+ );
+ }
+ \stream_set_blocking($this->master, 0);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!\is_resource($this->master)) {
+ return null;
+ }
+
+ return 'unix://' . \stream_socket_get_name($this->master, false);
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ try {
+ $newSocket = SocketServer::accept($master);
+ } catch (\RuntimeException $e) {
+ $that->emit('error', array($e));
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!\is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ \fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $connection = new Connection($socket, $this->loop);
+ $connection->unix = true;
+
+ $this->emit('connection', array(
+ $connection
+ ));
+ }
+}
diff --git a/vendor/react/stream/LICENSE b/vendor/react/stream/LICENSE
new file mode 100644
index 0000000..d6f8901
--- /dev/null
+++ b/vendor/react/stream/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/stream/composer.json b/vendor/react/stream/composer.json
new file mode 100644
index 0000000..b235f5a
--- /dev/null
+++ b/vendor/react/stream/composer.json
@@ -0,0 +1,47 @@
+{
+ "name": "react/stream",
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.2",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
+ "clue/stream-filter": "~1.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Stream\\": "tests"
+ }
+ }
+}
diff --git a/vendor/react/stream/src/CompositeStream.php b/vendor/react/stream/src/CompositeStream.php
new file mode 100644
index 0000000..dde091d
--- /dev/null
+++ b/vendor/react/stream/src/CompositeStream.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitter;
+
+final class CompositeStream extends EventEmitter implements DuplexStreamInterface
+{
+ private $readable;
+ private $writable;
+ private $closed = false;
+
+ public function __construct(ReadableStreamInterface $readable, WritableStreamInterface $writable)
+ {
+ $this->readable = $readable;
+ $this->writable = $writable;
+
+ if (!$readable->isReadable() || !$writable->isWritable()) {
+ $this->close();
+ return;
+ }
+
+ Util::forwardEvents($this->readable, $this, array('data', 'end', 'error'));
+ Util::forwardEvents($this->writable, $this, array('drain', 'error', 'pipe'));
+
+ $this->readable->on('close', array($this, 'close'));
+ $this->writable->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->readable->isReadable();
+ }
+
+ public function pause()
+ {
+ $this->readable->pause();
+ }
+
+ public function resume()
+ {
+ if (!$this->writable->isWritable()) {
+ return;
+ }
+
+ $this->readable->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isWritable()
+ {
+ return $this->writable->isWritable();
+ }
+
+ public function write($data)
+ {
+ return $this->writable->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->readable->pause();
+ $this->writable->end($data);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+ $this->readable->close();
+ $this->writable->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/vendor/react/stream/src/DuplexResourceStream.php b/vendor/react/stream/src/DuplexResourceStream.php
new file mode 100644
index 0000000..c3163c6
--- /dev/null
+++ b/vendor/react/stream/src/DuplexResourceStream.php
@@ -0,0 +1,227 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use InvalidArgumentException;
+
+final class DuplexResourceStream extends EventEmitter implements DuplexStreamInterface
+{
+ private $stream;
+
+ /** @var LoopInterface */
+ private $loop;
+
+ /**
+ * Controls the maximum buffer size in bytes to read at once from the stream.
+ *
+ * This can be a positive number which means that up to X bytes will be read
+ * at once from the underlying stream resource. Note that the actual number
+ * of bytes read may be lower if the stream resource has less than X bytes
+ * currently available.
+ *
+ * This can be `-1` which means read everything available from the
+ * underlying stream resource.
+ * This should read until the stream resource is not readable anymore
+ * (i.e. underlying buffer drained), note that this does not neccessarily
+ * mean it reached EOF.
+ *
+ * @var int
+ */
+ private $bufferSize;
+ private $buffer;
+
+ private $readable = true;
+ private $writable = true;
+ private $closing = false;
+ private $listening = false;
+
+ public function __construct($stream, LoopInterface $loop = null, $readChunkSize = null, WritableStreamInterface $buffer = null)
+ {
+ if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") {
+ throw new InvalidArgumentException('First parameter must be a valid stream resource');
+ }
+
+ // ensure resource is opened for reading and wrting (fopen mode must contain "+")
+ $meta = \stream_get_meta_data($stream);
+ if (isset($meta['mode']) && $meta['mode'] !== '' && \strpos($meta['mode'], '+') === false) {
+ throw new InvalidArgumentException('Given stream resource is not opened in read and write mode');
+ }
+
+ // this class relies on non-blocking I/O in order to not interrupt the event loop
+ // e.g. pipes on Windows do not support this: https://bugs.php.net/bug.php?id=47918
+ if (\stream_set_blocking($stream, false) !== true) {
+ throw new \RuntimeException('Unable to set stream resource to non-blocking mode');
+ }
+
+ // Use unbuffered read operations on the underlying stream resource.
+ // Reading chunks from the stream may otherwise leave unread bytes in
+ // PHP's stream buffers which some event loop implementations do not
+ // trigger events on (edge triggered).
+ // This does not affect the default event loop implementation (level
+ // triggered), so we can ignore platforms not supporting this (HHVM).
+ // Pipe streams (such as STDIN) do not seem to require this and legacy
+ // PHP versions cause SEGFAULTs on unbuffered pipe streams, so skip this.
+ if (\function_exists('stream_set_read_buffer') && !$this->isLegacyPipe($stream)) {
+ \stream_set_read_buffer($stream, 0);
+ }
+
+ if ($buffer === null) {
+ $buffer = new WritableResourceStream($stream, $loop);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop ?: Loop::get();
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+ $this->buffer = $buffer;
+
+ $that = $this;
+
+ $this->buffer->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+
+ $this->buffer->on('close', array($this, 'close'));
+
+ $this->buffer->on('drain', function () use ($that) {
+ $that->emit('drain');
+ });
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && $this->readable) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ return $this->buffer->write($data);
+ }
+
+ public function close()
+ {
+ if (!$this->writable && !$this->closing) {
+ return;
+ }
+
+ $this->closing = false;
+
+ $this->readable = false;
+ $this->writable = false;
+
+ $this->emit('close');
+ $this->pause();
+ $this->buffer->close();
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ $this->closing = true;
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->pause();
+
+ $this->buffer->end($data);
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ /** @internal */
+ public function handleData($stream)
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = \stream_get_contents($stream, $this->bufferSize);
+
+ \restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } elseif (\feof($this->stream)) {
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
+ $meta = \stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/vendor/react/stream/src/DuplexStreamInterface.php b/vendor/react/stream/src/DuplexStreamInterface.php
new file mode 100644
index 0000000..631ce31
--- /dev/null
+++ b/vendor/react/stream/src/DuplexStreamInterface.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace React\Stream;
+
+/**
+ * The `DuplexStreamInterface` is responsible for providing an interface for
+ * duplex streams (both readable and writable).
+ *
+ * It builds on top of the existing interfaces for readable and writable streams
+ * and follows the exact same method and event semantics.
+ * If you're new to this concept, you should look into the
+ * `ReadableStreamInterface` and `WritableStreamInterface` first.
+ *
+ * Besides defining a few methods, this interface also implements the
+ * `EventEmitterInterface` which allows you to react to the same events defined
+ * on the `ReadbleStreamInterface` and `WritableStreamInterface`.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see ReadableStreamInterface
+ * @see WritableStreamInterface
+ */
+interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface
+{
+}
diff --git a/vendor/react/stream/src/ReadableResourceStream.php b/vendor/react/stream/src/ReadableResourceStream.php
new file mode 100644
index 0000000..1b0b08c
--- /dev/null
+++ b/vendor/react/stream/src/ReadableResourceStream.php
@@ -0,0 +1,179 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use InvalidArgumentException;
+
+final class ReadableResourceStream extends EventEmitter implements ReadableStreamInterface
+{
+ /**
+ * @var resource
+ */
+ private $stream;
+
+ /** @var LoopInterface */
+ private $loop;
+
+ /**
+ * Controls the maximum buffer size in bytes to read at once from the stream.
+ *
+ * This value SHOULD NOT be changed unless you know what you're doing.
+ *
+ * This can be a positive number which means that up to X bytes will be read
+ * at once from the underlying stream resource. Note that the actual number
+ * of bytes read may be lower if the stream resource has less than X bytes
+ * currently available.
+ *
+ * This can be `-1` which means read everything available from the
+ * underlying stream resource.
+ * This should read until the stream resource is not readable anymore
+ * (i.e. underlying buffer drained), note that this does not neccessarily
+ * mean it reached EOF.
+ *
+ * @var int
+ */
+ private $bufferSize;
+
+ private $closed = false;
+ private $listening = false;
+
+ public function __construct($stream, LoopInterface $loop = null, $readChunkSize = null)
+ {
+ if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") {
+ throw new InvalidArgumentException('First parameter must be a valid stream resource');
+ }
+
+ // ensure resource is opened for reading (fopen mode must contain "r" or "+")
+ $meta = \stream_get_meta_data($stream);
+ if (isset($meta['mode']) && $meta['mode'] !== '' && \strpos($meta['mode'], 'r') === \strpos($meta['mode'], '+')) {
+ throw new InvalidArgumentException('Given stream resource is not opened in read mode');
+ }
+
+ // this class relies on non-blocking I/O in order to not interrupt the event loop
+ // e.g. pipes on Windows do not support this: https://bugs.php.net/bug.php?id=47918
+ if (\stream_set_blocking($stream, false) !== true) {
+ throw new \RuntimeException('Unable to set stream resource to non-blocking mode');
+ }
+
+ // Use unbuffered read operations on the underlying stream resource.
+ // Reading chunks from the stream may otherwise leave unread bytes in
+ // PHP's stream buffers which some event loop implementations do not
+ // trigger events on (edge triggered).
+ // This does not affect the default event loop implementation (level
+ // triggered), so we can ignore platforms not supporting this (HHVM).
+ // Pipe streams (such as STDIN) do not seem to require this and legacy
+ // PHP versions cause SEGFAULTs on unbuffered pipe streams, so skip this.
+ if (\function_exists('stream_set_read_buffer') && !$this->isLegacyPipe($stream)) {
+ \stream_set_read_buffer($stream, 0);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop ?: Loop::get();
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && !$this->closed) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+
+ $this->emit('close');
+ $this->pause();
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleData()
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = \stream_get_contents($this->stream, $this->bufferSize);
+
+ \restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } elseif (\feof($this->stream)) {
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
+ $meta = \stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/vendor/react/stream/src/ReadableStreamInterface.php b/vendor/react/stream/src/ReadableStreamInterface.php
new file mode 100644
index 0000000..fa3d59c
--- /dev/null
+++ b/vendor/react/stream/src/ReadableStreamInterface.php
@@ -0,0 +1,362 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitterInterface;
+
+/**
+ * The `ReadableStreamInterface` is responsible for providing an interface for
+ * read-only streams and the readable side of duplex streams.
+ *
+ * Besides defining a few methods, this interface also implements the
+ * `EventEmitterInterface` which allows you to react to certain events:
+ *
+ * data event:
+ * The `data` event will be emitted whenever some data was read/received
+ * from this source stream.
+ * The event receives a single mixed argument for incoming data.
+ *
+ * ```php
+ * $stream->on('data', function ($data) {
+ * echo $data;
+ * });
+ * ```
+ *
+ * This event MAY be emitted any number of times, which may be zero times if
+ * this stream does not send any data at all.
+ * It SHOULD not be emitted after an `end` or `close` event.
+ *
+ * The given `$data` argument may be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit the raw (binary) payload data that is received over the wire as
+ * chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * end event:
+ * The `end` event will be emitted once the source stream has successfully
+ * reached the end of the stream (EOF).
+ *
+ * ```php
+ * $stream->on('end', function () {
+ * echo 'END';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * a successful end was detected.
+ * It SHOULD NOT be emitted after a previous `end` or `close` event.
+ * It MUST NOT be emitted if the stream closes due to a non-successful
+ * end, such as after a previous `error` event.
+ *
+ * After the stream is ended, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * This event will only be emitted if the *end* was reached successfully,
+ * not if the stream was interrupted by an unrecoverable error or explicitly
+ * closed. Not all streams know this concept of a "successful end".
+ * Many use-cases involve detecting when the stream closes (terminates)
+ * instead, in this case you should use the `close` event.
+ * After the stream emits an `end` event, it SHOULD usually be followed by a
+ * `close` event.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit this event if either the remote side closes the connection or
+ * a file handle was successfully read until reaching its end (EOF).
+ *
+ * Note that this event should not be confused with the `end()` method.
+ * This event defines a successful end *reading* from a source stream, while
+ * the `end()` method defines *writing* a successful end to a destination
+ * stream.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to read from this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error or after an unexpected `data` or premature
+ * `end` event.
+ * It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-readable mode, see
+ * also `close()` and `isReadable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and do not make assumption about data
+ * boundaries (such as unexpected `data` or premature `end` events).
+ * In other words, many lower-level protocols (such as TCP/IP) may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * Unlike the `end` event, this event SHOULD be emitted whenever the stream
+ * closes, irrespective of whether this happens implicitly due to an
+ * unrecoverable error or explicitly when either side closes the stream.
+ * If you only want to detect a *successful* end, you should use the `end`
+ * event instead.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after reading a *successful* `end`
+ * event or after a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ */
+interface ReadableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a readable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts incoming
+ * data events or if it is ended or closed already.
+ * Once the stream is non-readable, no further `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * A successfully opened stream always MUST start in readable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-readable mode.
+ * This can happen any time, explicitly through `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-readable mode, it MUST NOT transition
+ * back to readable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `isWritable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isReadable();
+
+ /**
+ * Pauses reading incoming data events.
+ *
+ * Removes the data source file descriptor from the event loop. This
+ * allows you to throttle incoming data.
+ *
+ * Unless otherwise noted, a successfully opened stream SHOULD NOT start
+ * in paused state.
+ *
+ * Once the stream is paused, no futher `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $stream->on('data', assertShouldNeverCalled());
+ * $stream->on('end', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * stream MAY continue emitting `data` events.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes reading incoming data events.
+ *
+ * Re-attach the data source after a previous `pause()`.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * Loop::addTimer(1.0, function () use ($stream) {
+ * $stream->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Pipes all the data from this readable source into the given writable destination.
+ *
+ * Automatically sends all incoming data to the destination.
+ * Automatically throttles the source based on what the destination can handle.
+ *
+ * ```php
+ * $source->pipe($dest);
+ * ```
+ *
+ * Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+ * into itself in order to write back all the data that is received.
+ * This may be a useful feature for a TCP/IP echo service:
+ *
+ * ```php
+ * $connection->pipe($connection);
+ * ```
+ *
+ * This method returns the destination stream as-is, which can be used to
+ * set up chains of piped streams:
+ *
+ * ```php
+ * $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+ * ```
+ *
+ * By default, this will call `end()` on the destination stream once the
+ * source stream emits an `end` event. This can be disabled like this:
+ *
+ * ```php
+ * $source->pipe($dest, array('end' => false));
+ * ```
+ *
+ * Note that this only applies to the `end` event.
+ * If an `error` or explicit `close` event happens on the source stream,
+ * you'll have to manually close the destination stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $source->on('close', function () use ($dest) {
+ * $dest->end('BYE!');
+ * });
+ * ```
+ *
+ * If the source stream is not readable (closed state), then this is a NO-OP.
+ *
+ * ```php
+ * $source->close();
+ * $source->pipe($dest); // NO-OP
+ * ```
+ *
+ * If the destinantion stream is not writable (closed state), then this will simply
+ * throttle (pause) the source stream:
+ *
+ * ```php
+ * $dest->close();
+ * $source->pipe($dest); // calls $source->pause()
+ * ```
+ *
+ * Similarly, if the destination stream is closed while the pipe is still
+ * active, it will also throttle (pause) the source stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $dest->close(); // calls $source->pause()
+ * ```
+ *
+ * Once the pipe is set up successfully, the destination stream MUST emit
+ * a `pipe` event with this source stream an event argument.
+ *
+ * @param WritableStreamInterface $dest
+ * @param array $options
+ * @return WritableStreamInterface $dest stream as-is
+ */
+ public function pipe(WritableStreamInterface $dest, array $options = array());
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to (forcefully) close the stream.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-readable
+ * mode, see also `isReadable()`.
+ * This means that no further `data` or `end` events SHOULD be emitted.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this method should not be confused with the `end()` method.
+ *
+ * @return void
+ * @see WritableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/vendor/react/stream/src/ThroughStream.php b/vendor/react/stream/src/ThroughStream.php
new file mode 100644
index 0000000..6f73fb8
--- /dev/null
+++ b/vendor/react/stream/src/ThroughStream.php
@@ -0,0 +1,190 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitter;
+use InvalidArgumentException;
+
+/**
+ * The `ThroughStream` implements the
+ * [`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data
+ * you write to it through to its readable end.
+ *
+ * ```php
+ * $through = new ThroughStream();
+ * $through->on('data', $this->expectCallableOnceWith('hello'));
+ *
+ * $through->write('hello');
+ * ```
+ *
+ * Similarly, the [`end()` method](#end) will end the stream and emit an
+ * [`end` event](#end-event) and then [`close()`](#close-1) the stream.
+ * The [`close()` method](#close-1) will close the stream and emit a
+ * [`close` event](#close-event).
+ * Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+ *
+ * ```php
+ * $through = new ThroughStream();
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Optionally, its constructor accepts any callable function which will then be
+ * used to *filter* any data written to it. This function receives a single data
+ * argument as passed to the writable side and must return the data as it will be
+ * passed to its readable end:
+ *
+ * ```php
+ * $through = new ThroughStream('strtoupper');
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Note that this class makes no assumptions about any data types. This can be
+ * used to convert data, for example for transforming any structured data into
+ * a newline-delimited JSON (NDJSON) stream like this:
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * return json_encode($data) . PHP_EOL;
+ * });
+ * $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+ *
+ * $through->write(array(2, true));
+ * ```
+ *
+ * The callback function is allowed to throw an `Exception`. In this case,
+ * the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * if (!is_string($data)) {
+ * throw new \UnexpectedValueException('Only strings allowed');
+ * }
+ * return $data;
+ * });
+ * $through->on('error', $this->expectCallableOnce()));
+ * $through->on('close', $this->expectCallableOnce()));
+ * $through->on('data', $this->expectCallableNever()));
+ *
+ * $through->write(2);
+ * ```
+ *
+ * @see WritableStreamInterface::write()
+ * @see WritableStreamInterface::end()
+ * @see DuplexStreamInterface::close()
+ * @see WritableStreamInterface::pipe()
+ */
+final class ThroughStream extends EventEmitter implements DuplexStreamInterface
+{
+ private $readable = true;
+ private $writable = true;
+ private $closed = false;
+ private $paused = false;
+ private $drain = false;
+ private $callback;
+
+ public function __construct($callback = null)
+ {
+ if ($callback !== null && !\is_callable($callback)) {
+ throw new InvalidArgumentException('Invalid transformation callback given');
+ }
+
+ $this->callback = $callback;
+ }
+
+ public function pause()
+ {
+ $this->paused = true;
+ }
+
+ public function resume()
+ {
+ if ($this->drain) {
+ $this->drain = false;
+ $this->emit('drain');
+ }
+ $this->paused = false;
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ if ($this->callback !== null) {
+ try {
+ $data = \call_user_func($this->callback, $data);
+ } catch (\Exception $e) {
+ $this->emit('error', array($e));
+ $this->close();
+
+ return false;
+ }
+ }
+
+ $this->emit('data', array($data));
+
+ if ($this->paused) {
+ $this->drain = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ if (null !== $data) {
+ $this->write($data);
+
+ // return if write() already caused the stream to close
+ if (!$this->writable) {
+ return;
+ }
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->paused = true;
+ $this->drain = false;
+
+ $this->emit('end');
+ $this->close();
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->closed = true;
+ $this->paused = true;
+ $this->drain = false;
+ $this->callback = null;
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/vendor/react/stream/src/Util.php b/vendor/react/stream/src/Util.php
new file mode 100644
index 0000000..056b037
--- /dev/null
+++ b/vendor/react/stream/src/Util.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace React\Stream;
+
+final class Util
+{
+ /**
+ * Pipes all the data from the given $source into the $dest
+ *
+ * @param ReadableStreamInterface $source
+ * @param WritableStreamInterface $dest
+ * @param array $options
+ * @return WritableStreamInterface $dest stream as-is
+ * @see ReadableStreamInterface::pipe() for more details
+ */
+ public static function pipe(ReadableStreamInterface $source, WritableStreamInterface $dest, array $options = array())
+ {
+ // source not readable => NO-OP
+ if (!$source->isReadable()) {
+ return $dest;
+ }
+
+ // destination not writable => just pause() source
+ if (!$dest->isWritable()) {
+ $source->pause();
+
+ return $dest;
+ }
+
+ $dest->emit('pipe', array($source));
+
+ // forward all source data events as $dest->write()
+ $source->on('data', $dataer = function ($data) use ($source, $dest) {
+ $feedMore = $dest->write($data);
+
+ if (false === $feedMore) {
+ $source->pause();
+ }
+ });
+ $dest->on('close', function () use ($source, $dataer) {
+ $source->removeListener('data', $dataer);
+ $source->pause();
+ });
+
+ // forward destination drain as $source->resume()
+ $dest->on('drain', $drainer = function () use ($source) {
+ $source->resume();
+ });
+ $source->on('close', function () use ($dest, $drainer) {
+ $dest->removeListener('drain', $drainer);
+ });
+
+ // forward end event from source as $dest->end()
+ $end = isset($options['end']) ? $options['end'] : true;
+ if ($end) {
+ $source->on('end', $ender = function () use ($dest) {
+ $dest->end();
+ });
+ $dest->on('close', function () use ($source, $ender) {
+ $source->removeListener('end', $ender);
+ });
+ }
+
+ return $dest;
+ }
+
+ public static function forwardEvents($source, $target, array $events)
+ {
+ foreach ($events as $event) {
+ $source->on($event, function () use ($event, $target) {
+ $target->emit($event, \func_get_args());
+ });
+ }
+ }
+}
diff --git a/vendor/react/stream/src/WritableResourceStream.php b/vendor/react/stream/src/WritableResourceStream.php
new file mode 100644
index 0000000..1af16b1
--- /dev/null
+++ b/vendor/react/stream/src/WritableResourceStream.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+
+final class WritableResourceStream extends EventEmitter implements WritableStreamInterface
+{
+ private $stream;
+
+ /** @var LoopInterface */
+ private $loop;
+
+ /**
+ * @var int
+ */
+ private $softLimit;
+
+ /**
+ * @var int
+ */
+ private $writeChunkSize;
+
+ private $listening = false;
+ private $writable = true;
+ private $closed = false;
+ private $data = '';
+
+ public function __construct($stream, LoopInterface $loop = null, $writeBufferSoftLimit = null, $writeChunkSize = null)
+ {
+ if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") {
+ throw new \InvalidArgumentException('First parameter must be a valid stream resource');
+ }
+
+ // ensure resource is opened for writing (fopen mode must contain either of "waxc+")
+ $meta = \stream_get_meta_data($stream);
+ if (isset($meta['mode']) && $meta['mode'] !== '' && \strtr($meta['mode'], 'waxc+', '.....') === $meta['mode']) {
+ throw new \InvalidArgumentException('Given stream resource is not opened in write mode');
+ }
+
+ // this class relies on non-blocking I/O in order to not interrupt the event loop
+ // e.g. pipes on Windows do not support this: https://bugs.php.net/bug.php?id=47918
+ if (\stream_set_blocking($stream, false) !== true) {
+ throw new \RuntimeException('Unable to set stream resource to non-blocking mode');
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop ?: Loop::get();
+ $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit;
+ $this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ $this->data .= $data;
+
+ if (!$this->listening && $this->data !== '') {
+ $this->listening = true;
+
+ $this->loop->addWriteStream($this->stream, array($this, 'handleWrite'));
+ }
+
+ return !isset($this->data[$this->softLimit - 1]);
+ }
+
+ public function end($data = null)
+ {
+ if (null !== $data) {
+ $this->write($data);
+ }
+
+ $this->writable = false;
+
+ // close immediately if buffer is already empty
+ // otherwise wait for buffer to flush first
+ if ($this->data === '') {
+ $this->close();
+ }
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ if ($this->listening) {
+ $this->listening = false;
+ $this->loop->removeWriteStream($this->stream);
+ }
+
+ $this->closed = true;
+ $this->writable = false;
+ $this->data = '';
+
+ $this->emit('close');
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleWrite()
+ {
+ $error = null;
+ \set_error_handler(function ($_, $errstr) use (&$error) {
+ $error = $errstr;
+ });
+
+ if ($this->writeChunkSize === -1) {
+ $sent = \fwrite($this->stream, $this->data);
+ } else {
+ $sent = \fwrite($this->stream, $this->data, $this->writeChunkSize);
+ }
+
+ \restore_error_handler();
+
+ // Only report errors if *nothing* could be sent and an error has been raised.
+ // Ignore non-fatal warnings if *some* data could be sent.
+ // Any hard (permanent) error will fail to send any data at all.
+ // Sending excessive amounts of data will only flush *some* data and then
+ // report a temporary error (EAGAIN) which we do not raise here in order
+ // to keep the stream open for further tries to write.
+ // Should this turn out to be a permanent error later, it will eventually
+ // send *nothing* and we can detect this.
+ if (($sent === 0 || $sent === false) && $error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . $error)));
+ $this->close();
+
+ return;
+ }
+
+ $exceeded = isset($this->data[$this->softLimit - 1]);
+ $this->data = (string) \substr($this->data, $sent);
+
+ // buffer has been above limit and is now below limit
+ if ($exceeded && !isset($this->data[$this->softLimit - 1])) {
+ $this->emit('drain');
+ }
+
+ // buffer is now completely empty => stop trying to write
+ if ($this->data === '') {
+ // stop waiting for resource to be writable
+ if ($this->listening) {
+ $this->loop->removeWriteStream($this->stream);
+ $this->listening = false;
+ }
+
+ // buffer is end()ing and now completely empty => close buffer
+ if (!$this->writable) {
+ $this->close();
+ }
+ }
+ }
+}
diff --git a/vendor/react/stream/src/WritableStreamInterface.php b/vendor/react/stream/src/WritableStreamInterface.php
new file mode 100644
index 0000000..9b54680
--- /dev/null
+++ b/vendor/react/stream/src/WritableStreamInterface.php
@@ -0,0 +1,347 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitterInterface;
+
+/**
+ * The `WritableStreamInterface` is responsible for providing an interface for
+ * write-only streams and the writable side of duplex streams.
+ *
+ * Besides defining a few methods, this interface also implements the
+ * `EventEmitterInterface` which allows you to react to certain events:
+ *
+ * drain event:
+ * The `drain` event will be emitted whenever the write buffer became full
+ * previously and is now ready to accept more data.
+ *
+ * ```php
+ * $stream->on('drain', function () use ($stream) {
+ * echo 'Stream is now ready to accept more data';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once every time the buffer became full
+ * previously and is now ready to accept more data.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if the buffer never became full in the first place.
+ * This event SHOULD NOT be emitted if the buffer has not become full
+ * previously.
+ *
+ * This event is mostly used internally, see also `write()` for more details.
+ *
+ * pipe event:
+ * The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+ * into this stream.
+ * The event receives a single `ReadableStreamInterface` argument for the
+ * source stream.
+ *
+ * ```php
+ * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ * echo 'Now receiving piped data';
+ *
+ * // explicitly close target if source emits an error
+ * $source->on('error', function () use ($stream) {
+ * $stream->close();
+ * });
+ * });
+ *
+ * $source->pipe($stream);
+ * ```
+ *
+ * This event MUST be emitted once for each readable stream that is
+ * successfully piped into this destination stream.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if no stream is ever piped into this stream.
+ * This event MUST NOT be emitted if either the source is not readable
+ * (closed already) or this destination is not writable (closed already).
+ *
+ * This event is mostly used internally, see also `pipe()` for more details.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to write to this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error.
+ * It SHOULD NOT be emitted after a previous `error` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-writable mode, see
+ * also `close()` and `isWritable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-writable mode,
+ * see also `isWritable()`.
+ *
+ * This event SHOULD be emitted whenever the stream closes, irrespective of
+ * whether this happens implicitly due to an unrecoverable error or
+ * explicitly when either side closes the stream.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after flushing the buffer from
+ * the `end()` method, after receiving a *successful* `end` event or after
+ * a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ * @see DuplexStreamInterface
+ */
+interface WritableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a writable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts writing
+ * any data or if it is ended or closed already.
+ * Writing any data to a non-writable stream is a NO-OP:
+ *
+ * ```php
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('end'); // NO-OP
+ * $stream->end('end'); // NO-OP
+ * ```
+ *
+ * A successfully opened stream always MUST start in writable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-writable mode.
+ * This can happen any time, explicitly through `end()` or `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-writable mode, it MUST NOT transition
+ * back to writable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `isReadable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isWritable();
+
+ /**
+ * Write some data into the stream.
+ *
+ * A successful write MUST be confirmed with a boolean `true`, which means
+ * that either the data was written (flushed) immediately or is buffered and
+ * scheduled for a future write. Note that this interface gives you no
+ * control over explicitly flushing the buffered data, as finding the
+ * appropriate time for this is beyond the scope of this interface and left
+ * up to the implementation of this interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+ * an `error` event and MAY `close()` the stream if it can not recover from
+ * this error.
+ *
+ * If the internal buffer is full after adding `$data`, then `write()`
+ * SHOULD return `false`, indicating that the caller should stop sending
+ * data until the buffer drains.
+ * The stream SHOULD send a `drain` event once the buffer is ready to accept
+ * more data.
+ *
+ * Similarly, if the the stream is not writable (already in a closed state)
+ * it MUST NOT process the given `$data` and SHOULD return `false`,
+ * indicating that the caller should stop sending data.
+ *
+ * The given `$data` argument MAY be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will only accept the raw (binary) payload data that is transferred over
+ * the wire as chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * @param mixed|string $data
+ * @return bool
+ */
+ public function write($data);
+
+ /**
+ * Successfully ends the stream (after optionally sending some final data).
+ *
+ * This method can be used to successfully end the stream, i.e. close
+ * the stream after sending out all data that is currently buffered.
+ *
+ * ```php
+ * $stream->write('hello');
+ * $stream->write('world');
+ * $stream->end();
+ * ```
+ *
+ * If there's no data currently buffered and nothing to be flushed, then
+ * this method MAY `close()` the stream immediately.
+ *
+ * If there's still data in the buffer that needs to be flushed first, then
+ * this method SHOULD try to write out this data and only then `close()`
+ * the stream.
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ *
+ * Note that this interface gives you no control over explicitly flushing
+ * the buffered data, as finding the appropriate time for this is beyond the
+ * scope of this interface and left up to the implementation of this
+ * interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * You can optionally pass some final data that is written to the stream
+ * before ending the stream. If a non-`null` value is given as `$data`, then
+ * this method will behave just like calling `write($data)` before ending
+ * with no data.
+ *
+ * ```php
+ * // shorter version
+ * $stream->end('bye');
+ *
+ * // same as longer version
+ * $stream->write('bye');
+ * $stream->end();
+ * ```
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->end();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+ * also end its readable side, unless the stream supports half-open mode.
+ * In other words, after calling this method, these streams SHOULD switch
+ * into non-writable AND non-readable mode, see also `isReadable()`.
+ * This implies that in this case, the stream SHOULD NOT emit any `data`
+ * or `end` events anymore.
+ * Streams MAY choose to use the `pause()` method logic for this, but
+ * special care may have to be taken to ensure a following call to the
+ * `resume()` method SHOULD NOT continue emitting readable events.
+ *
+ * Note that this method should not be confused with the `close()` method.
+ *
+ * @param mixed|string|null $data
+ * @return void
+ */
+ public function end($data = null);
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to forcefully close the stream, i.e. close
+ * the stream without waiting for any buffered data to be flushed.
+ * If there's still data in the buffer, this data SHOULD be discarded.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * Note that this method should not be confused with the `end()` method.
+ * Unlike the `end()` method, this method does not take care of any existing
+ * buffers and simply discards any buffer contents.
+ * Likewise, this method may also be called after calling `end()` on a
+ * stream in order to stop waiting for the stream to flush its final data.
+ *
+ * ```php
+ * $stream->end();
+ * Loop::addTimer(1.0, function () use ($stream) {
+ * $stream->close();
+ * });
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ *
+ * @return void
+ * @see ReadableStreamInterface::close()
+ */
+ public function close();
+}