mirror of
https://github.com/Icinga/icinga-php-thirdparty.git
synced 2025-08-16 07:08:21 +02:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e41d6866f4 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -1,3 +0,0 @@
|
||||
.git* export-ignore
|
||||
bin/ export-ignore
|
||||
RELEASE.md export-ignore
|
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@ -1,11 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
open-pull-requests-limit: 20 # Roughly half the number of dependencies
|
||||
groups:
|
||||
composer:
|
||||
update-types:
|
||||
- "patch"
|
||||
schedule:
|
||||
interval: "daily"
|
4
.github/workflows/create-snapshot.yml
vendored
4
.github/workflows/create-snapshot.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code base
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
php-version: '5.6'
|
||||
|
||||
- name: Create snapshot/nightly
|
||||
if: success()
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
/vendor/
|
||||
/.idea/
|
||||
.*.sw[op]
|
||||
|
@ -6,7 +6,7 @@ which can be integrated as library into Icinga Web 2.
|
||||
## Requirements
|
||||
|
||||
* [Icinga Web 2](https://github.com/Icinga/icingaweb2) (>= 2.9)
|
||||
* PHP (>= 8.2)
|
||||
* PHP (>= 7.2)
|
||||
|
||||
## Installation
|
||||
|
||||
@ -16,7 +16,7 @@ path for Icinga Web 2 installations is: `/usr/share/icinga-php`
|
||||
Download or clone this repository there (e.g. `/usr/share/icinga-php/vendor`) and you're done.
|
||||
|
||||
> **Note**: Do NOT install the GIT master, it will not work! Checking out a
|
||||
> branch like `stable/0.13.0` or a tag like `v0.13.0` is fine.
|
||||
> branch like `stable/0.10.0` or a tag like `v0.10.0` is fine.
|
||||
|
||||
### Examples
|
||||
|
||||
@ -24,7 +24,7 @@ Download or clone this repository there (e.g. `/usr/share/icinga-php/vendor`) an
|
||||
|
||||
```sh
|
||||
INSTALL_PATH="/usr/share/icinga-php/vendor"
|
||||
INSTALL_VERSION="v0.13.0"
|
||||
INSTALL_VERSION="v0.10.0"
|
||||
mkdir "$INSTALL_PATH" \
|
||||
&& wget -q "https://github.com/Icinga/icinga-php-thirdparty/archive/$INSTALL_VERSION.tar.gz" -O - \
|
||||
| tar xfz - -C "$INSTALL_PATH" --strip-components 1
|
||||
@ -34,6 +34,6 @@ mkdir "$INSTALL_PATH" \
|
||||
|
||||
```
|
||||
INSTALL_PATH="/usr/share/icinga-php/vendor"
|
||||
INSTALL_VERSION="stable/0.13.0"
|
||||
INSTALL_VERSION="stable/0.10.0"
|
||||
git clone https://github.com/Icinga/icinga-php-thirdparty.git "$INSTALL_PATH" --branch "$INSTALL_VERSION"
|
||||
```
|
||||
|
@ -8,4 +8,4 @@ e.g.
|
||||
|
||||
## Docker Example
|
||||
|
||||
docker run -it -v $(pwd):/tmp/pwd -w /tmp/pwd -v $(realpath ~/.gitconfig):/tmp/user/.gitconfig -e "HOME=/tmp/user" -u $(id -u):$(id -g) dev-docker_web82 bin/make-release.sh 1.0.0 --no-tag
|
||||
docker run -it -v $(pwd):/tmp/pwd -w /tmp/pwd -v $(realpath ~/.gitconfig):/tmp/user/.gitconfig -e "HOME=/tmp/user" -u $(id -u):$(id -g) dev-docker_web72 bin/make-release.sh 1.0.0 --no-tag
|
||||
|
10881
asset/js/jquery/jquery.js
vendored
Normal file
10881
asset/js/jquery/jquery.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
asset/js/jquery/jquery.min.js
vendored
Normal file
2
asset/js/jquery/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,13 +0,0 @@
|
||||
Copyright 2010-2023 Mike Bostock
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
20625
asset/js/mbostock/d3.js
vendored
20625
asset/js/mbostock/d3.js
vendored
File diff suppressed because it is too large
Load Diff
2
asset/js/mbostock/d3.min.js
vendored
2
asset/js/mbostock/d3.min.js
vendored
File diff suppressed because one or more lines are too long
@ -32,13 +32,24 @@ fi
|
||||
|
||||
git rm -rf vendor
|
||||
rm -rf vendor
|
||||
rm -f composer.lock
|
||||
composer install || fail "composer install failed"
|
||||
git add vendor
|
||||
find vendor/ -type f -name "*.php" \
|
||||
| grep -v '/examples/' \
|
||||
| grep -v '/example/' \
|
||||
| grep -v '/tests/' \
|
||||
| grep -v '/test/' \
|
||||
| xargs -L1 git add -f
|
||||
find vendor/ -type f -name LICENSE | xargs -L1 git add -f
|
||||
find vendor/ -type f -name '*.json' | xargs -L1 git add -f
|
||||
find asset/ -type f | xargs -L1 git add -f
|
||||
echo "v$VERSION" > VERSION
|
||||
git add VERSION
|
||||
git add composer.lock -f
|
||||
git commit -m "Version v$VERSION"
|
||||
|
||||
rm -rf vendor
|
||||
git checkout vendor
|
||||
composer validate --no-check-all --strict || fail "Composer validate failed"
|
||||
|
||||
if [ -z "$NO_OPT" ]; then
|
||||
|
@ -18,11 +18,7 @@ if [[ -n $(git branch | grep $BRANCH) ]]; then
|
||||
fi
|
||||
|
||||
git checkout -b $BRANCH
|
||||
git merge --no-ff -m "Merge latest tag, to make it reachable for git-describe" $LATEST_TAG
|
||||
|
||||
git mv composer.lock composer.lock.bak
|
||||
git commit -am "Backup composer.lock"
|
||||
git merge --no-ff -m "Merge latest tag, package pipelines require it" $LATEST_TAG || (git checkout --ours composer.lock.bak && git add composer.lock.bak && git commit --no-edit)
|
||||
git mv -f composer.lock.bak composer.lock
|
||||
git commit -am "Restore composer.lock" || true # in case composer.lock was not modified
|
||||
|
||||
git commit -a -m "Require dev-master everywhere"
|
||||
bin/make-release.sh "$NEXT_VERSION-dev" --no-checkout
|
||||
|
@ -6,6 +6,9 @@
|
||||
"license": "MIT",
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"platform": {
|
||||
"php": "7.2"
|
||||
},
|
||||
"allow-plugins": {
|
||||
"cweagans/composer-patches": true
|
||||
}
|
||||
@ -14,45 +17,36 @@
|
||||
"issues": "https://github.com/Icinga/icinga-php-thirdparty/issues"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"php": ">=7.2 <=8.1",
|
||||
"ext-curl": "*",
|
||||
"clue/block-react": "^1.5",
|
||||
"clue/connection-manager-extra": "^1.3",
|
||||
"clue/http-proxy-react": "^1.8",
|
||||
"clue/mq-react": "^1.5",
|
||||
"clue/redis-react": "^2.6",
|
||||
"clue/soap-react": "^2",
|
||||
"clue/socket-raw": "^1.6",
|
||||
"clue/socks-react": "^1.4",
|
||||
"clue/stdio-react": "^2.6",
|
||||
"components/jquery": "3.7.*",
|
||||
"clue/block-react": "^1",
|
||||
"clue/connection-manager-extra": "^1",
|
||||
"clue/http-proxy-react": "^1",
|
||||
"clue/mq-react": "^1",
|
||||
"clue/redis-react": "^2",
|
||||
"clue/soap-react": "^1",
|
||||
"clue/socket-raw": "^1",
|
||||
"clue/socks-react": "^1",
|
||||
"clue/stdio-react": "^2",
|
||||
"cweagans/composer-patches": "~1.0",
|
||||
"dompdf/dompdf": "^3.1.0",
|
||||
"erusev/parsedown": "^1.7.4",
|
||||
"evenement/evenement": "^3.0.1",
|
||||
"ezyang/htmlpurifier": "^4.16",
|
||||
"guzzlehttp/guzzle": "^7.7",
|
||||
"guzzlehttp/psr7": "^2.5",
|
||||
"jfcherng/php-diff": "^6.10.14",
|
||||
"predis/predis": "^3.0",
|
||||
"psr/http-message": "^1.1",
|
||||
"ramsey/uuid": "^4.2.3",
|
||||
"react/child-process": "^0.6.5",
|
||||
"react/datagram": "^1.9",
|
||||
"react/dns": "^1.11",
|
||||
"react/event-loop": "^1.4",
|
||||
"react/http": "^1.9",
|
||||
"react/promise": "^2.10",
|
||||
"react/promise-stream": "^1.6",
|
||||
"react/promise-timer": "^1.9",
|
||||
"react/socket": "^1.13",
|
||||
"react/stream": "^1.3",
|
||||
"shardj/zf1-future": "^1.23.5",
|
||||
"tedivm/jshrink": "^1.6.8",
|
||||
"wikimedia/less.php": "^3.2.1",
|
||||
"simshaun/recurr": "^5",
|
||||
"dragonmantank/cron-expression": "^3",
|
||||
"psr/log": "^1"
|
||||
"evenement/evenement": "^3",
|
||||
"predis/predis": "^1",
|
||||
"psr/http-message": "^1",
|
||||
"ramsey/uuid": "^3",
|
||||
"react/child-process": "^0.6",
|
||||
"react/datagram": "^1",
|
||||
"react/dns": "^1",
|
||||
"react/event-loop": "^1",
|
||||
"react/http": "^1",
|
||||
"react/promise": "^2",
|
||||
"react/promise-stream": "^1",
|
||||
"react/promise-timer": "^1",
|
||||
"react/socket": "^1",
|
||||
"react/stream": "^1",
|
||||
"guzzlehttp/psr7": "^1",
|
||||
"guzzlehttp/guzzle": "^6.5",
|
||||
"jfcherng/php-diff": "^6.10",
|
||||
"components/jquery": "3.6.*"
|
||||
},
|
||||
"require-dev": {
|
||||
},
|
||||
@ -60,9 +54,6 @@
|
||||
"psr-0": { "AssetLoader": "" }
|
||||
},
|
||||
"scripts": {
|
||||
"post-install-cmd": [
|
||||
"AssetLoader::update"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"AssetLoader::update"
|
||||
]
|
||||
@ -70,8 +61,8 @@
|
||||
"extra": {
|
||||
"composer-exit-on-patch-failure": true,
|
||||
"patches": {
|
||||
"shardj/zf1-future": {
|
||||
"ZF1-Future: ZF backward compatibility": "patches/shardj-zf1-future.patch"
|
||||
"ramsey/uuid": {
|
||||
"Uuid: Add PHP 8.1 support": "patches/ramsey-uuid.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2775
composer.lock
generated
2775
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,37 +0,0 @@
|
||||
--- a/vendor/ramsey/collection/src/AbstractArray.php
|
||||
+++ b/vendor/ramsey/collection/src/AbstractArray.php
|
||||
@@ -84,6 +84,7 @@ abstract class AbstractArray implements ArrayInterface
|
||||
* @return T|null the value stored at the offset, or null if the offset
|
||||
* does not exist.
|
||||
*/
|
||||
+ #[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->data[$offset] ?? null;
|
||||
@@ -132,6 +133,11 @@ abstract class AbstractArray implements ArrayInterface
|
||||
return serialize($this->data);
|
||||
}
|
||||
|
||||
+ public function __serialize()
|
||||
+ {
|
||||
+ return $this->serialize();
|
||||
+ }
|
||||
+
|
||||
/**
|
||||
* Converts a serialized string representation into an instance object.
|
||||
*
|
||||
@@ -149,6 +155,11 @@ abstract class AbstractArray implements ArrayInterface
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
+ public function __unserialize(array $data)
|
||||
+ {
|
||||
+ $this->unserialize($data);
|
||||
+ }
|
||||
+
|
||||
/**
|
||||
* Returns the number of items in this array.
|
||||
*
|
||||
--
|
||||
2.41.0
|
||||
|
10
patches/ramsey-uuid.patch
Normal file
10
patches/ramsey-uuid.patch
Normal file
@ -0,0 +1,10 @@
|
||||
--- a/vendor/ramsey/uuid/src/Uuid.php
|
||||
+++ b/vendor/ramsey/uuid/src/Uuid.php
|
||||
@@ -212,6 +212,7 @@ class Uuid implements UuidInterface
|
||||
* @return string
|
||||
* @link http://php.net/manual/en/class.jsonserializable.php
|
||||
*/
|
||||
+ #[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toString();
|
@ -1,22 +0,0 @@
|
||||
--- a/vendor/shardj/zf1-future/library/Zend/Form/Element.php
|
||||
+++ b/vendor/shardj/zf1-future/library/Zend/Form/Element.php
|
||||
@@ -595,7 +595,7 @@ class Zend_Form_Element implements Zend_Validate_Interface
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
- protected function _filterValue(&$value, $key)
|
||||
+ protected function _filterValue(&$value, &$key)
|
||||
{
|
||||
foreach ($this->getFilters() as $filter) {
|
||||
$value = $filter->filter($value);
|
||||
@@ -612,7 +612,9 @@ class Zend_Form_Element implements Zend_Validate_Interface
|
||||
$valueFiltered = $this->_value;
|
||||
|
||||
if ($this->isArray() && is_array($valueFiltered)) {
|
||||
- array_walk_recursive($valueFiltered, [$this, '_filterValue']);
|
||||
+ array_walk_recursive($valueFiltered, function (&$val, $key) {
|
||||
+ $this->_filterValue($val, $key);
|
||||
+ });
|
||||
} else {
|
||||
$this->_filterValue($valueFiltered, $valueFiltered);
|
||||
}
|
12
vendor/autoload.php
vendored
Normal file
12
vendor/autoload.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitd325ec05aa7f058e77e450fa9a6801af::getLoader();
|
21
vendor/clue/block-react/LICENSE
vendored
Normal file
21
vendor/clue/block-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
29
vendor/clue/block-react/composer.json
vendored
Normal file
29
vendor/clue/block-react/composer.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "clue/block-react",
|
||||
"description": "Lightweight library that eases integrating async components built for ReactPHP in a traditional, blocking environment.",
|
||||
"keywords": ["blocking", "await", "sleep", "Event Loop", "synchronous", "Promise", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-block",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"files": [ "src/functions_include.php" ]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Block\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.0 || ^2.7 || ^1.2.1",
|
||||
"react/promise-timer": "^1.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
|
||||
"react/http": "^1.4"
|
||||
}
|
||||
}
|
357
vendor/clue/block-react/src/functions.php
vendored
Normal file
357
vendor/clue/block-react/src/functions.php
vendored
Normal file
@ -0,0 +1,357 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Block;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Timer;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
use Exception;
|
||||
use UnderflowException;
|
||||
|
||||
/**
|
||||
* Wait/sleep for `$time` seconds.
|
||||
*
|
||||
* ```php
|
||||
* Clue\React\Block\sleep(1.5, $loop);
|
||||
* ```
|
||||
*
|
||||
* This function will only return after the given `$time` has elapsed. In the
|
||||
* meantime, the event loop will run any other events attached to the same loop
|
||||
* until the timer fires. If there are no other events attached to this loop,
|
||||
* it will behave similar to the built-in [`sleep()`](https://www.php.net/manual/en/function.sleep.php).
|
||||
*
|
||||
* Internally, the `$time` argument will be used as a timer for the loop so that
|
||||
* it keeps running until this timer triggers. This implies that if you pass a
|
||||
* really small (or negative) value, it will still start a timer and will thus
|
||||
* trigger at the earliest possible time in the future.
|
||||
*
|
||||
* This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use. You can use a `null` value here in order to
|
||||
* use the [default loop](https://github.com/reactphp/event-loop#loop). This value
|
||||
* SHOULD NOT be given unless you're sure you want to explicitly use a given event
|
||||
* loop instance.
|
||||
*
|
||||
* Note that this function will assume control over the event loop. Internally, it
|
||||
* will actually `run()` the loop until the timer fires and then calls `stop()` to
|
||||
* terminate execution of the loop. This means this function is more suited for
|
||||
* short-lived program executions when using async APIs is not feasible. For
|
||||
* long-running applications, using event-driven APIs by leveraging timers
|
||||
* is usually preferable.
|
||||
*
|
||||
* @param float $time
|
||||
* @param ?LoopInterface $loop
|
||||
* @return void
|
||||
*/
|
||||
function sleep($time, LoopInterface $loop = null)
|
||||
{
|
||||
await(Timer\resolve($time, $loop), $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Block waiting for the given `$promise` to be fulfilled.
|
||||
*
|
||||
* ```php
|
||||
* $result = Clue\React\Block\await($promise, $loop);
|
||||
* ```
|
||||
*
|
||||
* This function will only return after the given `$promise` has settled, i.e.
|
||||
* either fulfilled or rejected. In the meantime, the event loop will run any
|
||||
* events attached to the same loop until the promise settles.
|
||||
*
|
||||
* Once the promise is fulfilled, this function will return whatever the promise
|
||||
* resolved to.
|
||||
*
|
||||
* Once the promise is rejected, this will throw whatever the promise rejected
|
||||
* with. If the promise did not reject with an `Exception`, then this function
|
||||
* will throw an `UnexpectedValueException` instead.
|
||||
*
|
||||
* ```php
|
||||
* try {
|
||||
* $result = Clue\React\Block\await($promise, $loop);
|
||||
* // promise successfully fulfilled with $result
|
||||
* echo 'Result: ' . $result;
|
||||
* } catch (Exception $exception) {
|
||||
* // promise rejected with $exception
|
||||
* echo 'ERROR: ' . $exception->getMessage();
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* See also the [examples](../examples/).
|
||||
*
|
||||
* This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use. You can use a `null` value here in order to
|
||||
* use the [default loop](https://github.com/reactphp/event-loop#loop). This value
|
||||
* SHOULD NOT be given unless you're sure you want to explicitly use a given event
|
||||
* loop instance.
|
||||
*
|
||||
* If no `$timeout` argument is given and the promise stays pending, then this
|
||||
* will potentially wait/block forever until the promise is settled. To avoid
|
||||
* this, API authors creating promises are expected to provide means to
|
||||
* configure a timeout for the promise instead. For more details, see also the
|
||||
* [`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
|
||||
*
|
||||
* If the deprecated `$timeout` argument is given and the promise is still pending once the
|
||||
* timeout triggers, this will `cancel()` the promise and throw a `TimeoutException`.
|
||||
* This implies that if you pass a really small (or negative) value, it will still
|
||||
* start a timer and will thus trigger at the earliest possible time in the future.
|
||||
*
|
||||
* Note that this function will assume control over the event loop. Internally, it
|
||||
* will actually `run()` the loop until the promise settles and then calls `stop()` to
|
||||
* terminate execution of the loop. This means this function is more suited for
|
||||
* short-lived promise executions when using promise-based APIs is not feasible.
|
||||
* For long-running applications, using promise-based APIs by leveraging chained
|
||||
* `then()` calls is usually preferable.
|
||||
*
|
||||
* @param PromiseInterface $promise
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?float $timeout [deprecated] (optional) maximum timeout in seconds or null=wait forever
|
||||
* @return mixed returns whatever the promise resolves to
|
||||
* @throws Exception when the promise is rejected
|
||||
* @throws TimeoutException if the $timeout is given and triggers
|
||||
*/
|
||||
function await(PromiseInterface $promise, LoopInterface $loop = null, $timeout = null)
|
||||
{
|
||||
$wait = true;
|
||||
$resolved = null;
|
||||
$exception = null;
|
||||
$rejected = false;
|
||||
$loop = $loop ?: Loop::get();
|
||||
|
||||
if ($timeout !== null) {
|
||||
$promise = Timer\timeout($promise, $timeout, $loop);
|
||||
}
|
||||
|
||||
$promise->then(
|
||||
function ($c) use (&$resolved, &$wait, $loop) {
|
||||
$resolved = $c;
|
||||
$wait = false;
|
||||
$loop->stop();
|
||||
},
|
||||
function ($error) use (&$exception, &$rejected, &$wait, $loop) {
|
||||
$exception = $error;
|
||||
$rejected = true;
|
||||
$wait = false;
|
||||
$loop->stop();
|
||||
}
|
||||
);
|
||||
|
||||
// Explicitly overwrite argument with null value. This ensure that this
|
||||
// argument does not show up in the stack trace in PHP 7+ only.
|
||||
$promise = null;
|
||||
|
||||
while ($wait) {
|
||||
$loop->run();
|
||||
}
|
||||
|
||||
if ($rejected) {
|
||||
if (!$exception instanceof \Exception && !$exception instanceof \Throwable) {
|
||||
$exception = new \UnexpectedValueException(
|
||||
'Promise rejected with unexpected value of type ' . (is_object($exception) ? get_class($exception) : gettype($exception))
|
||||
);
|
||||
} elseif (!$exception instanceof \Exception) {
|
||||
$exception = new \UnexpectedValueException(
|
||||
'Promise rejected with unexpected ' . get_class($exception) . ': ' . $exception->getMessage(),
|
||||
$exception->getCode(),
|
||||
$exception
|
||||
);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for ANY of the given promises to be fulfilled.
|
||||
*
|
||||
* ```php
|
||||
* $promises = array(
|
||||
* $promise1,
|
||||
* $promise2
|
||||
* );
|
||||
*
|
||||
* $firstResult = Clue\React\Block\awaitAny($promises, $loop);
|
||||
*
|
||||
* echo 'First result: ' . $firstResult;
|
||||
* ```
|
||||
*
|
||||
* See also the [examples](../examples/).
|
||||
*
|
||||
* This function will only return after ANY of the given `$promises` has been
|
||||
* fulfilled or will throw when ALL of them have been rejected. In the meantime,
|
||||
* the event loop will run any events attached to the same loop.
|
||||
*
|
||||
* Once ANY promise is fulfilled, this function will return whatever this
|
||||
* promise resolved to and will try to `cancel()` all remaining promises.
|
||||
*
|
||||
* Once ALL promises reject, this function will fail and throw an `UnderflowException`.
|
||||
* Likewise, this will throw if an empty array of `$promises` is passed.
|
||||
*
|
||||
* This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use. You can use a `null` value here in order to
|
||||
* use the [default loop](https://github.com/reactphp/event-loop#loop). This value
|
||||
* SHOULD NOT be given unless you're sure you want to explicitly use a given event
|
||||
* loop instance.
|
||||
*
|
||||
* If no `$timeout` argument is given and ALL promises stay pending, then this
|
||||
* will potentially wait/block forever until the promise is fulfilled. To avoid
|
||||
* this, API authors creating promises are expected to provide means to
|
||||
* configure a timeout for the promise instead. For more details, see also the
|
||||
* [`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
|
||||
*
|
||||
* If the deprecated `$timeout` argument is given and ANY promises are still pending once
|
||||
* the timeout triggers, this will `cancel()` all pending promises and throw a
|
||||
* `TimeoutException`. This implies that if you pass a really small (or negative)
|
||||
* value, it will still start a timer and will thus trigger at the earliest
|
||||
* possible time in the future.
|
||||
*
|
||||
* Note that this function will assume control over the event loop. Internally, it
|
||||
* will actually `run()` the loop until the promise settles and then calls `stop()` to
|
||||
* terminate execution of the loop. This means this function is more suited for
|
||||
* short-lived promise executions when using promise-based APIs is not feasible.
|
||||
* For long-running applications, using promise-based APIs by leveraging chained
|
||||
* `then()` calls is usually preferable.
|
||||
*
|
||||
* @param PromiseInterface[] $promises
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?float $timeout [deprecated] (optional) maximum timeout in seconds or null=wait forever
|
||||
* @return mixed returns whatever the first promise resolves to
|
||||
* @throws Exception if ALL promises are rejected
|
||||
* @throws TimeoutException if the $timeout is given and triggers
|
||||
*/
|
||||
function awaitAny(array $promises, LoopInterface $loop = null, $timeout = null)
|
||||
{
|
||||
// Explicitly overwrite argument with null value. This ensure that this
|
||||
// argument does not show up in the stack trace in PHP 7+ only.
|
||||
$all = $promises;
|
||||
$promises = null;
|
||||
|
||||
try {
|
||||
// Promise\any() does not cope with an empty input array, so reject this here
|
||||
if (!$all) {
|
||||
throw new UnderflowException('Empty input array');
|
||||
}
|
||||
|
||||
$ret = await(Promise\any($all)->then(null, function () {
|
||||
// rejects with an array of rejection reasons => reject with Exception instead
|
||||
throw new Exception('All promises rejected');
|
||||
}), $loop, $timeout);
|
||||
} catch (TimeoutException $e) {
|
||||
// the timeout fired
|
||||
// => try to cancel all promises (rejected ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
throw $e;
|
||||
} catch (Exception $e) {
|
||||
// if the above throws, then ALL promises are already rejected
|
||||
// => try to cancel all promises (rejected ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
throw new UnderflowException('No promise could resolve', 0, $e);
|
||||
}
|
||||
|
||||
// if we reach this, then ANY of the given promises resolved
|
||||
// => try to cancel all promises (settled ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for ALL of the given promises to be fulfilled.
|
||||
*
|
||||
* ```php
|
||||
* $promises = array(
|
||||
* $promise1,
|
||||
* $promise2
|
||||
* );
|
||||
*
|
||||
* $allResults = Clue\React\Block\awaitAll($promises, $loop);
|
||||
*
|
||||
* echo 'First promise resolved with: ' . $allResults[0];
|
||||
* ```
|
||||
*
|
||||
* See also the [examples](../examples/).
|
||||
*
|
||||
* This function will only return after ALL of the given `$promises` have been
|
||||
* fulfilled or will throw when ANY of them have been rejected. In the meantime,
|
||||
* the event loop will run any events attached to the same loop.
|
||||
*
|
||||
* Once ALL promises are fulfilled, this will return an array with whatever
|
||||
* each promise resolves to. Array keys will be left intact, i.e. they can
|
||||
* be used to correlate the return array to the promises passed.
|
||||
*
|
||||
* Once ANY promise rejects, this will try to `cancel()` all remaining promises
|
||||
* and throw an `Exception`. If the promise did not reject with an `Exception`,
|
||||
* then this function will throw an `UnexpectedValueException` instead.
|
||||
*
|
||||
* This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use. You can use a `null` value here in order to
|
||||
* use the [default loop](https://github.com/reactphp/event-loop#loop). This value
|
||||
* SHOULD NOT be given unless you're sure you want to explicitly use a given event
|
||||
* loop instance.
|
||||
*
|
||||
* If no `$timeout` argument is given and ANY promises stay pending, then this
|
||||
* will potentially wait/block forever until the promise is fulfilled. To avoid
|
||||
* this, API authors creating promises are expected to provide means to
|
||||
* configure a timeout for the promise instead. For more details, see also the
|
||||
* [`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
|
||||
*
|
||||
* If the deprecated `$timeout` argument is given and ANY promises are still pending once
|
||||
* the timeout triggers, this will `cancel()` all pending promises and throw a
|
||||
* `TimeoutException`. This implies that if you pass a really small (or negative)
|
||||
* value, it will still start a timer and will thus trigger at the earliest
|
||||
* possible time in the future.
|
||||
*
|
||||
* Note that this function will assume control over the event loop. Internally, it
|
||||
* will actually `run()` the loop until the promise settles and then calls `stop()` to
|
||||
* terminate execution of the loop. This means this function is more suited for
|
||||
* short-lived promise executions when using promise-based APIs is not feasible.
|
||||
* For long-running applications, using promise-based APIs by leveraging chained
|
||||
* `then()` calls is usually preferable.
|
||||
*
|
||||
* @param PromiseInterface[] $promises
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?float $timeout [deprecated] (optional) maximum timeout in seconds or null=wait forever
|
||||
* @return array returns an array with whatever each promise resolves to
|
||||
* @throws Exception when ANY promise is rejected
|
||||
* @throws TimeoutException if the $timeout is given and triggers
|
||||
*/
|
||||
function awaitAll(array $promises, LoopInterface $loop = null, $timeout = null)
|
||||
{
|
||||
// Explicitly overwrite argument with null value. This ensure that this
|
||||
// argument does not show up in the stack trace in PHP 7+ only.
|
||||
$all = $promises;
|
||||
$promises = null;
|
||||
|
||||
try {
|
||||
return await(Promise\all($all), $loop, $timeout);
|
||||
} catch (Exception $e) {
|
||||
// ANY of the given promises rejected or the timeout fired
|
||||
// => try to cancel all promises (rejected ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* internal helper function used to iterate over an array of Promise instances and cancel() each
|
||||
*
|
||||
* @internal
|
||||
* @param array $promises
|
||||
* @return void
|
||||
*/
|
||||
function _cancelAllPromises(array $promises)
|
||||
{
|
||||
foreach ($promises as $promise) {
|
||||
if ($promise instanceof PromiseInterface && ($promise instanceof CancellablePromiseInterface || !\interface_exists('React\Promise\CancellablePromiseInterface'))) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
}
|
8
vendor/clue/block-react/src/functions_include.php
vendored
Normal file
8
vendor/clue/block-react/src/functions_include.php
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Block;
|
||||
|
||||
if (!function_exists('Clue\\React\\Block\\sleep')) {
|
||||
require __DIR__ . '/functions.php';
|
||||
}
|
||||
|
21
vendor/clue/buzz-react/LICENSE
vendored
Normal file
21
vendor/clue/buzz-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
38
vendor/clue/buzz-react/composer.json
vendored
Normal file
38
vendor/clue/buzz-react/composer.json
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "clue/buzz-react",
|
||||
"description": "Simple, async PSR-7 HTTP client for concurrently processing any number of HTTP requests, built on top of ReactPHP",
|
||||
"keywords": ["HTTP client", "PSR-7", "HTTP", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-buzz",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Buzz\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Buzz\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"psr/http-message": "^1.0",
|
||||
"react/event-loop": "^1.0 || ^0.5",
|
||||
"react/http-client": "^0.5.10",
|
||||
"react/promise": "^2.2.1 || ^1.2.1",
|
||||
"react/promise-stream": "^1.0 || ^0.1.2",
|
||||
"react/socket": "^1.1",
|
||||
"react/stream": "^1.0 || ^0.7",
|
||||
"ringcentral/psr7": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.0",
|
||||
"clue/http-proxy-react": "^1.3",
|
||||
"clue/reactphp-ssh-proxy": "^1.0",
|
||||
"clue/socks-react": "^1.0",
|
||||
"phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35",
|
||||
"react/http": "^0.8"
|
||||
}
|
||||
}
|
867
vendor/clue/buzz-react/src/Browser.php
vendored
Normal file
867
vendor/clue/buzz-react/src/Browser.php
vendored
Normal file
@ -0,0 +1,867 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz;
|
||||
|
||||
use Clue\React\Buzz\Io\Sender;
|
||||
use Clue\React\Buzz\Io\Transaction;
|
||||
use Clue\React\Buzz\Message\MessageFactory;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
class Browser
|
||||
{
|
||||
private $transaction;
|
||||
private $messageFactory;
|
||||
private $baseUrl;
|
||||
private $protocolVersion = '1.1';
|
||||
|
||||
/**
|
||||
* The `Browser` is responsible for sending HTTP requests to your HTTP server
|
||||
* and keeps track of pending incoming HTTP responses.
|
||||
* It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage).
|
||||
*
|
||||
* ```php
|
||||
* $loop = React\EventLoop\Factory::create();
|
||||
*
|
||||
* $browser = new Clue\React\Buzz\Browser($loop);
|
||||
* ```
|
||||
*
|
||||
* If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
|
||||
* proxy servers etc.), you can explicitly pass a custom instance of the
|
||||
* [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
|
||||
*
|
||||
* ```php
|
||||
* $connector = new React\Socket\Connector($loop, array(
|
||||
* 'dns' => '127.0.0.1',
|
||||
* 'tcp' => array(
|
||||
* 'bindto' => '192.168.10.1:0'
|
||||
* ),
|
||||
* 'tls' => array(
|
||||
* 'verify_peer' => false,
|
||||
* 'verify_peer_name' => false
|
||||
* )
|
||||
* ));
|
||||
*
|
||||
* $browser = new Clue\React\Buzz\Browser($loop, $connector);
|
||||
* ```
|
||||
*
|
||||
* @param LoopInterface $loop
|
||||
* @param ConnectorInterface|null $connector [optional] Connector to use.
|
||||
* Should be `null` in order to use default Connector.
|
||||
*/
|
||||
public function __construct(LoopInterface $loop, ConnectorInterface $connector = null)
|
||||
{
|
||||
$this->messageFactory = new MessageFactory();
|
||||
$this->transaction = new Transaction(
|
||||
Sender::createFromLoop($loop, $connector, $this->messageFactory),
|
||||
$this->messageFactory,
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP GET request
|
||||
*
|
||||
* ```php
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [example 01](../examples/01-google.php).
|
||||
*
|
||||
* > For BC reasons, this method accepts the `$url` as either a `string`
|
||||
* value or as an `UriInterface`. It's recommended to explicitly cast any
|
||||
* objects implementing `UriInterface` to `string`.
|
||||
*
|
||||
* @param string|UriInterface $url URL for the request.
|
||||
* @param array $headers
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function get($url, array $headers = array())
|
||||
{
|
||||
return $this->requestMayBeStreaming('GET', $url, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP POST request
|
||||
*
|
||||
* ```php
|
||||
* $browser->post(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'application/json'
|
||||
* ],
|
||||
* json_encode($data)
|
||||
* )->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump(json_decode((string)$response->getBody()));
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [example 04](../examples/04-post-json.php).
|
||||
*
|
||||
* This method is also commonly used to submit HTML form data:
|
||||
*
|
||||
* ```php
|
||||
* $data = [
|
||||
* 'user' => 'Alice',
|
||||
* 'password' => 'secret'
|
||||
* ];
|
||||
*
|
||||
* $browser->post(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'application/x-www-form-urlencoded'
|
||||
* ],
|
||||
* http_build_query($data)
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* $loop->addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->post($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* > For BC reasons, this method accepts the `$url` as either a `string`
|
||||
* value or as an `UriInterface`. It's recommended to explicitly cast any
|
||||
* objects implementing `UriInterface` to `string`.
|
||||
*
|
||||
* @param string|UriInterface $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $contents
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function post($url, array $headers = array(), $contents = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('POST', $url, $headers, $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP HEAD request
|
||||
*
|
||||
* ```php
|
||||
* $browser->head($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump($response->getHeaders());
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* > For BC reasons, this method accepts the `$url` as either a `string`
|
||||
* value or as an `UriInterface`. It's recommended to explicitly cast any
|
||||
* objects implementing `UriInterface` to `string`.
|
||||
*
|
||||
* @param string|UriInterface $url URL for the request.
|
||||
* @param array $headers
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function head($url, array $headers = array())
|
||||
{
|
||||
return $this->requestMayBeStreaming('HEAD', $url, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP PATCH request
|
||||
*
|
||||
* ```php
|
||||
* $browser->patch(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'application/json'
|
||||
* ],
|
||||
* json_encode($data)
|
||||
* )->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump(json_decode((string)$response->getBody()));
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* $loop->addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->patch($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* > For BC reasons, this method accepts the `$url` as either a `string`
|
||||
* value or as an `UriInterface`. It's recommended to explicitly cast any
|
||||
* objects implementing `UriInterface` to `string`.
|
||||
*
|
||||
* @param string|UriInterface $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $contents
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function patch($url, array $headers = array(), $contents = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('PATCH', $url , $headers, $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP PUT request
|
||||
*
|
||||
* ```php
|
||||
* $browser->put(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'text/xml'
|
||||
* ],
|
||||
* $xml->asXML()
|
||||
* )->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [example 05](../examples/05-put-xml.php).
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* $loop->addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->put($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* > For BC reasons, this method accepts the `$url` as either a `string`
|
||||
* value or as an `UriInterface`. It's recommended to explicitly cast any
|
||||
* objects implementing `UriInterface` to `string`.
|
||||
*
|
||||
* @param string|UriInterface $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $contents
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function put($url, array $headers = array(), $contents = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('PUT', $url, $headers, $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP DELETE request
|
||||
*
|
||||
* ```php
|
||||
* $browser->delete($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* > For BC reasons, this method accepts the `$url` as either a `string`
|
||||
* value or as an `UriInterface`. It's recommended to explicitly cast any
|
||||
* objects implementing `UriInterface` to `string`.
|
||||
*
|
||||
* @param string|UriInterface $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $contents
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function delete($url, array $headers = array(), $contents = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('DELETE', $url, $headers, $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an arbitrary HTTP request.
|
||||
*
|
||||
* The preferred way to send an HTTP request is by using the above
|
||||
* [request methods](#request-methods), for example the [`get()`](#get)
|
||||
* method to send an HTTP `GET` request.
|
||||
*
|
||||
* As an alternative, if you want to use a custom HTTP request method, you
|
||||
* can use this method:
|
||||
*
|
||||
* ```php
|
||||
* $browser->request('OPTIONS', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the size of the outgoing request body is known and non-empty.
|
||||
* For an empty request body, if will only include a `Content-Length: 0`
|
||||
* request header if the request method usually expects a request body (only
|
||||
* applies to `POST`, `PUT` and `PATCH`).
|
||||
*
|
||||
* If you're using a streaming request body (`ReadableStreamInterface`), it
|
||||
* will default to using `Transfer-Encoding: chunked` or you have to
|
||||
* explicitly pass in a matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* $loop->addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->request('POST', $url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* > Note that this method is available as of v2.9.0 and always buffers the
|
||||
* response body before resolving.
|
||||
* It does not respect the deprecated [`streaming` option](#withoptions).
|
||||
* If you want to stream the response body, you can use the
|
||||
* [`requestStreaming()`](#requeststreaming) method instead.
|
||||
*
|
||||
* @param string $method HTTP request method, e.g. GET/HEAD/POST etc.
|
||||
* @param string $url URL for the request
|
||||
* @param array $headers Additional request headers
|
||||
* @param string|ReadableStreamInterface $body HTTP request body contents
|
||||
* @return PromiseInterface<ResponseInterface,Exception>
|
||||
* @since 2.9.0
|
||||
*/
|
||||
public function request($method, $url, array $headers = array(), $body = '')
|
||||
{
|
||||
return $this->withOptions(array('streaming' => false))->requestMayBeStreaming($method, $url, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an arbitrary HTTP request and receives a streaming response without buffering the response body.
|
||||
*
|
||||
* The preferred way to send an HTTP request is by using the above
|
||||
* [request methods](#request-methods), for example the [`get()`](#get)
|
||||
* method to send an HTTP `GET` request. Each of these methods will buffer
|
||||
* the whole response body in memory by default. This is easy to get started
|
||||
* and works reasonably well for smaller responses.
|
||||
*
|
||||
* In some situations, it's a better idea to use a streaming approach, where
|
||||
* only small chunks have to be kept in memory. You can use this method to
|
||||
* send an arbitrary HTTP request and receive a streaming response. It uses
|
||||
* the same HTTP message API, but does not buffer the response body in
|
||||
* memory. It only processes the response body in small chunks as data is
|
||||
* received and forwards this data through [ReactPHP's Stream API](https://github.com/reactphp/stream).
|
||||
* This works for (any number of) responses of arbitrary sizes.
|
||||
*
|
||||
* ```php
|
||||
* $browser->requestStreaming('GET', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* $body = $response->getBody();
|
||||
* assert($body instanceof Psr\Http\Message\StreamInterface);
|
||||
* assert($body instanceof React\Stream\ReadableStreamInterface);
|
||||
*
|
||||
* $body->on('data', function ($chunk) {
|
||||
* echo $chunk;
|
||||
* });
|
||||
*
|
||||
* $body->on('error', function (Exception $error) {
|
||||
* echo 'Error: ' . $error->getMessage() . PHP_EOL;
|
||||
* });
|
||||
*
|
||||
* $body->on('close', function () {
|
||||
* echo '[DONE]' . PHP_EOL;
|
||||
* });
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface)
|
||||
* and the [streaming response](#streaming-response) for more details,
|
||||
* examples and possible use-cases.
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the size of the outgoing request body is known and non-empty.
|
||||
* For an empty request body, if will only include a `Content-Length: 0`
|
||||
* request header if the request method usually expects a request body (only
|
||||
* applies to `POST`, `PUT` and `PATCH`).
|
||||
*
|
||||
* If you're using a streaming request body (`ReadableStreamInterface`), it
|
||||
* will default to using `Transfer-Encoding: chunked` or you have to
|
||||
* explicitly pass in a matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* $loop->addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->requestStreaming('POST', $url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* > Note that this method is available as of v2.9.0 and always resolves the
|
||||
* response without buffering the response body.
|
||||
* It does not respect the deprecated [`streaming` option](#withoptions).
|
||||
* If you want to buffer the response body, use can use the
|
||||
* [`request()`](#request) method instead.
|
||||
*
|
||||
* @param string $method HTTP request method, e.g. GET/HEAD/POST etc.
|
||||
* @param string $url URL for the request
|
||||
* @param array $headers Additional request headers
|
||||
* @param string|ReadableStreamInterface $body HTTP request body contents
|
||||
* @return PromiseInterface<ResponseInterface,Exception>
|
||||
* @since 2.9.0
|
||||
*/
|
||||
public function requestStreaming($method, $url, $headers = array(), $contents = '')
|
||||
{
|
||||
return $this->withOptions(array('streaming' => true))->requestMayBeStreaming($method, $url, $headers, $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Deprecated] Submits an array of field values similar to submitting a form (`application/x-www-form-urlencoded`).
|
||||
*
|
||||
* ```php
|
||||
* // deprecated: see post() instead
|
||||
* $browser->submit($url, array('user' => 'test', 'password' => 'secret'));
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header for the encoded length of the given `$fields`.
|
||||
*
|
||||
* > For BC reasons, this method accepts the `$url` as either a `string`
|
||||
* value or as an `UriInterface`. It's recommended to explicitly cast any
|
||||
* objects implementing `UriInterface` to `string`.
|
||||
*
|
||||
* @param string|UriInterface $url URL for the request.
|
||||
* @param array $fields
|
||||
* @param array $headers
|
||||
* @param string $method
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
* @deprecated 2.9.0 See self::post() instead.
|
||||
* @see self::post()
|
||||
*/
|
||||
public function submit($url, array $fields, $headers = array(), $method = 'POST')
|
||||
{
|
||||
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
$contents = http_build_query($fields);
|
||||
|
||||
return $this->requestMayBeStreaming($method, $url, $headers, $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Deprecated] Sends an arbitrary instance implementing the [`RequestInterface`](#requestinterface) (PSR-7).
|
||||
*
|
||||
* The preferred way to send an HTTP request is by using the above
|
||||
* [request methods](#request-methods), for example the [`get()`](#get)
|
||||
* method to send an HTTP `GET` request.
|
||||
*
|
||||
* As an alternative, if you want to use a custom HTTP request method, you
|
||||
* can use this method:
|
||||
*
|
||||
* ```php
|
||||
* $request = new Request('OPTIONS', $url);
|
||||
*
|
||||
* // deprecated: see request() instead
|
||||
* $browser->send($request)->then(…);
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the size of the outgoing request body is known and non-empty.
|
||||
* For an empty request body, if will only include a `Content-Length: 0`
|
||||
* request header if the request method usually expects a request body (only
|
||||
* applies to `POST`, `PUT` and `PATCH`).
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
* @deprecated 2.9.0 See self::request() instead.
|
||||
* @see self::request()
|
||||
*/
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
if ($this->baseUrl !== null) {
|
||||
// ensure we're actually below the base URL
|
||||
$request = $request->withUri($this->messageFactory->expandBase($request->getUri(), $this->baseUrl));
|
||||
}
|
||||
|
||||
return $this->transaction->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the maximum timeout used for waiting for pending requests.
|
||||
*
|
||||
* You can pass in the number of seconds to use as a new timeout value:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withTimeout(10.0);
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `false` to disable any timeouts. In this case,
|
||||
* requests can stay pending forever:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withTimeout(false);
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `true` to re-enable default timeout handling. This
|
||||
* will respects PHP's `default_socket_timeout` setting (default 60s):
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withTimeout(true);
|
||||
* ```
|
||||
*
|
||||
* See also [timeouts](#timeouts) for more details about timeout handling.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given timeout value applied.
|
||||
*
|
||||
* @param bool|number $timeout
|
||||
* @return self
|
||||
*/
|
||||
public function withTimeout($timeout)
|
||||
{
|
||||
if ($timeout === true) {
|
||||
$timeout = null;
|
||||
} elseif ($timeout === false) {
|
||||
$timeout = -1;
|
||||
} elseif ($timeout < 0) {
|
||||
$timeout = 0;
|
||||
}
|
||||
|
||||
return $this->withOptions(array(
|
||||
'timeout' => $timeout,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes how HTTP redirects will be followed.
|
||||
*
|
||||
* You can pass in the maximum number of redirects to follow:
|
||||
*
|
||||
* ```php
|
||||
* $new = $browser->withFollowRedirects(5);
|
||||
* ```
|
||||
*
|
||||
* The request will automatically be rejected when the number of redirects
|
||||
* is exceeded. You can pass in a `0` to reject the request for any
|
||||
* redirects encountered:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withFollowRedirects(0);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // only non-redirected responses will now end up here
|
||||
* var_dump($response->getHeaders());
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `false` to disable following any redirects. In
|
||||
* this case, requests will resolve with the redirection response instead
|
||||
* of following the `Location` response header:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withFollowRedirects(false);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // any redirects will now end up here
|
||||
* var_dump($response->getHeaderLine('Location'));
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `true` to re-enable default redirect handling.
|
||||
* This defaults to following a maximum of 10 redirects:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withFollowRedirects(true);
|
||||
* ```
|
||||
*
|
||||
* See also [redirects](#redirects) for more details about redirect handling.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given redirect setting applied.
|
||||
*
|
||||
* @param bool|int $followRedirects
|
||||
* @return self
|
||||
*/
|
||||
public function withFollowRedirects($followRedirects)
|
||||
{
|
||||
return $this->withOptions(array(
|
||||
'followRedirects' => $followRedirects !== false,
|
||||
'maxRedirects' => \is_bool($followRedirects) ? null : $followRedirects
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes whether non-successful HTTP response status codes (4xx and 5xx) will be rejected.
|
||||
*
|
||||
* You can pass in a bool `false` to disable rejecting incoming responses
|
||||
* that use a 4xx or 5xx response status code. In this case, requests will
|
||||
* resolve with the response message indicating an error condition:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withRejectErrorResponse(false);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // any HTTP response will now end up here
|
||||
* var_dump($response->getStatusCode(), $response->getReasonPhrase());
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `true` to re-enable default status code handling.
|
||||
* This defaults to rejecting any response status codes in the 4xx or 5xx
|
||||
* range:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withRejectErrorResponse(true);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // any successful HTTP response will now end up here
|
||||
* var_dump($response->getStatusCode(), $response->getReasonPhrase());
|
||||
* }, function (Exception $e) {
|
||||
* if ($e instanceof Clue\React\Buzz\Message\ResponseException) {
|
||||
* // any HTTP response error message will now end up here
|
||||
* $response = $e->getResponse();
|
||||
* var_dump($response->getStatusCode(), $response->getReasonPhrase());
|
||||
* } else {
|
||||
* var_dump($e->getMessage());
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given setting applied.
|
||||
*
|
||||
* @param bool $obeySuccessCode
|
||||
* @return self
|
||||
*/
|
||||
public function withRejectErrorResponse($obeySuccessCode)
|
||||
{
|
||||
return $this->withOptions(array(
|
||||
'obeySuccessCode' => $obeySuccessCode,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the base URL used to resolve relative URLs to.
|
||||
*
|
||||
* If you configure a base URL, any requests to relative URLs will be
|
||||
* processed by first prepending this absolute base URL. Note that this
|
||||
* merely prepends the base URL and does *not* resolve any relative path
|
||||
* references (like `../` etc.). This is mostly useful for (RESTful) API
|
||||
* calls where all endpoints (URLs) are located under a common base URL.
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withBase('http://api.example.com/v3');
|
||||
*
|
||||
* // will request http://api.example.com/v3/example
|
||||
* $browser->get('/example')->then(…);
|
||||
* ```
|
||||
*
|
||||
* You can pass in a `null` base URL to return a new instance that does not
|
||||
* use a base URL:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withBase(null);
|
||||
* ```
|
||||
*
|
||||
* Accordingly, any requests using relative URLs to a browser that does not
|
||||
* use a base URL can not be completed and will be rejected without sending
|
||||
* a request.
|
||||
*
|
||||
* This method will throw an `InvalidArgumentException` if the given
|
||||
* `$baseUrl` argument is not a valid URL.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method
|
||||
* actually returns a *new* [`Browser`](#browser) instance with the given base URL applied.
|
||||
*
|
||||
* > For BC reasons, this method accepts the `$baseUrl` as either a `string`
|
||||
* value or as an `UriInterface`. It's recommended to explicitly cast any
|
||||
* objects implementing `UriInterface` to `string`.
|
||||
*
|
||||
* > Changelog: As of v2.9.0 this method accepts a `null` value to reset the
|
||||
* base URL. Earlier versions had to use the deprecated `withoutBase()`
|
||||
* method to reset the base URL.
|
||||
*
|
||||
* @param string|null|UriInterface $baseUrl absolute base URL
|
||||
* @return self
|
||||
* @throws InvalidArgumentException if the given $baseUrl is not a valid absolute URL
|
||||
* @see self::withoutBase()
|
||||
*/
|
||||
public function withBase($baseUrl)
|
||||
{
|
||||
$browser = clone $this;
|
||||
if ($baseUrl === null) {
|
||||
$browser->baseUrl = null;
|
||||
return $browser;
|
||||
}
|
||||
|
||||
$browser->baseUrl = $this->messageFactory->uri($baseUrl);
|
||||
if (!\in_array($browser->baseUrl->getScheme(), array('http', 'https')) || $browser->baseUrl->getHost() === '') {
|
||||
throw new \InvalidArgumentException('Base URL must be absolute');
|
||||
}
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the HTTP protocol version that will be used for all subsequent requests.
|
||||
*
|
||||
* All the above [request methods](#request-methods) default to sending
|
||||
* requests as HTTP/1.1. This is the preferred HTTP protocol version which
|
||||
* also provides decent backwards-compatibility with legacy HTTP/1.0
|
||||
* servers. As such, there should rarely be a need to explicitly change this
|
||||
* protocol version.
|
||||
*
|
||||
* If you want to explicitly use the legacy HTTP/1.0 protocol version, you
|
||||
* can use this method:
|
||||
*
|
||||
* ```php
|
||||
* $newBrowser = $browser->withProtocolVersion('1.0');
|
||||
*
|
||||
* $newBrowser->get($url)->then(…);
|
||||
* ```
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* new protocol version applied.
|
||||
*
|
||||
* @param string $protocolVersion HTTP protocol version to use, must be one of "1.1" or "1.0"
|
||||
* @return self
|
||||
* @throws InvalidArgumentException
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public function withProtocolVersion($protocolVersion)
|
||||
{
|
||||
if (!\in_array($protocolVersion, array('1.0', '1.1'), true)) {
|
||||
throw new InvalidArgumentException('Invalid HTTP protocol version, must be one of "1.1" or "1.0"');
|
||||
}
|
||||
|
||||
$browser = clone $this;
|
||||
$browser->protocolVersion = (string) $protocolVersion;
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the maximum size for buffering a response body.
|
||||
*
|
||||
* The preferred way to send an HTTP request is by using the above
|
||||
* [request methods](#request-methods), for example the [`get()`](#get)
|
||||
* method to send an HTTP `GET` request. Each of these methods will buffer
|
||||
* the whole response body in memory by default. This is easy to get started
|
||||
* and works reasonably well for smaller responses.
|
||||
*
|
||||
* By default, the response body buffer will be limited to 16 MiB. If the
|
||||
* response body exceeds this maximum size, the request will be rejected.
|
||||
*
|
||||
* You can pass in the maximum number of bytes to buffer:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withResponseBuffer(1024 * 1024);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // response body will not exceed 1 MiB
|
||||
* var_dump($response->getHeaders(), (string) $response->getBody());
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Note that the response body buffer has to be kept in memory for each
|
||||
* pending request until its transfer is completed and it will only be freed
|
||||
* after a pending request is fulfilled. As such, increasing this maximum
|
||||
* buffer size to allow larger response bodies is usually not recommended.
|
||||
* Instead, you can use the [`requestStreaming()` method](#requeststreaming)
|
||||
* to receive responses with arbitrary sizes without buffering. Accordingly,
|
||||
* this maximum buffer size setting has no effect on streaming responses.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given setting applied.
|
||||
*
|
||||
* @param int $maximumSize
|
||||
* @return self
|
||||
* @see self::requestStreaming()
|
||||
*/
|
||||
public function withResponseBuffer($maximumSize)
|
||||
{
|
||||
return $this->withOptions(array(
|
||||
'maximumSize' => $maximumSize
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* [Deprecated] Changes the [options](#options) to use:
|
||||
*
|
||||
* The [`Browser`](#browser) class exposes several options for the handling of
|
||||
* HTTP transactions. These options resemble some of PHP's
|
||||
* [HTTP context options](http://php.net/manual/en/context.http.php) and
|
||||
* can be controlled via the following API (and their defaults):
|
||||
*
|
||||
* ```php
|
||||
* // deprecated
|
||||
* $newBrowser = $browser->withOptions(array(
|
||||
* 'timeout' => null, // see withTimeout() instead
|
||||
* 'followRedirects' => true, // see withFollowRedirects() instead
|
||||
* 'maxRedirects' => 10, // see withFollowRedirects() instead
|
||||
* 'obeySuccessCode' => true, // see withRejectErrorResponse() instead
|
||||
* 'streaming' => false, // deprecated, see requestStreaming() instead
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* See also [timeouts](#timeouts), [redirects](#redirects) and
|
||||
* [streaming](#streaming) for more details.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* options applied.
|
||||
*
|
||||
* @param array $options
|
||||
* @return self
|
||||
* @deprecated 2.9.0 See self::withTimeout(), self::withFollowRedirects() and self::withRejectErrorResponse() instead.
|
||||
* @see self::withTimeout()
|
||||
* @see self::withFollowRedirects()
|
||||
* @see self::withRejectErrorResponse()
|
||||
*/
|
||||
public function withOptions(array $options)
|
||||
{
|
||||
$browser = clone $this;
|
||||
$browser->transaction = $this->transaction->withOptions($options);
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Deprecated] Removes the base URL.
|
||||
*
|
||||
* ```php
|
||||
* // deprecated: see withBase() instead
|
||||
* $newBrowser = $browser->withoutBase();
|
||||
* ```
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withoutBase()` method
|
||||
* actually returns a *new* [`Browser`](#browser) instance without any base URL applied.
|
||||
*
|
||||
* See also [`withBase()`](#withbase).
|
||||
*
|
||||
* @return self
|
||||
* @deprecated 2.9.0 See self::withBase() instead.
|
||||
* @see self::withBase()
|
||||
*/
|
||||
public function withoutBase()
|
||||
{
|
||||
return $this->withBase(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string|UriInterface $url
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $contents
|
||||
* @return PromiseInterface<ResponseInterface,Exception>
|
||||
*/
|
||||
private function requestMayBeStreaming($method, $url, array $headers = array(), $contents = '')
|
||||
{
|
||||
return $this->send($this->messageFactory->request($method, $url, $headers, $contents, $this->protocolVersion));
|
||||
}
|
||||
}
|
93
vendor/clue/buzz-react/src/Io/ChunkedEncoder.php
vendored
Normal file
93
vendor/clue/buzz-react/src/Io/ChunkedEncoder.php
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Encodes given payload stream with "Transfer-Encoding: chunked" and emits encoded data
|
||||
*
|
||||
* This is used internally to encode outgoing requests with this encoding.
|
||||
*
|
||||
* @internal
|
||||
* @link https://github.com/reactphp/http/blob/master/src/Io/ChunkedEncoder.php Originally from react/http
|
||||
*/
|
||||
class ChunkedEncoder extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
return Util::pipe($this, $dest, $options);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
if ($data !== '') {
|
||||
$this->emit('data', array(
|
||||
dechex(strlen($data)) . "\r\n" . $data . "\r\n"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->emit('data', array("0\r\n\r\n"));
|
||||
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
}
|
161
vendor/clue/buzz-react/src/Io/Sender.php
vendored
Normal file
161
vendor/clue/buzz-react/src/Io/Sender.php
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz\Io;
|
||||
|
||||
use Clue\React\Buzz\Message\MessageFactory;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\HttpClient\Client as HttpClient;
|
||||
use React\HttpClient\Response as ResponseStream;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Sends requests and receives responses
|
||||
*
|
||||
* The `Sender` is responsible for passing the [`RequestInterface`](#requestinterface) objects to
|
||||
* the underlying [`HttpClient`](https://github.com/reactphp/http-client) library
|
||||
* and keeps track of its transmission and converts its reponses back to [`ResponseInterface`](#responseinterface) objects.
|
||||
*
|
||||
* It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
|
||||
* and the default [`Connector`](https://github.com/reactphp/socket-client) and [DNS `Resolver`](https://github.com/reactphp/dns).
|
||||
*
|
||||
* The `Sender` class mostly exists in order to abstract changes on the underlying
|
||||
* components away from this package in order to provide backwards and forwards
|
||||
* compatibility.
|
||||
*
|
||||
* @internal You SHOULD NOT rely on this API, it is subject to change without prior notice!
|
||||
* @see Browser
|
||||
*/
|
||||
class Sender
|
||||
{
|
||||
/**
|
||||
* create a new default sender attached to the given event loop
|
||||
*
|
||||
* This method is used internally to create the "default sender".
|
||||
*
|
||||
* You may also use this method if you need custom DNS or connector
|
||||
* settings. You can use this method manually like this:
|
||||
*
|
||||
* ```php
|
||||
* $connector = new \React\Socket\Connector($loop);
|
||||
* $sender = \Clue\React\Buzz\Io\Sender::createFromLoop($loop, $connector);
|
||||
* ```
|
||||
*
|
||||
* @param LoopInterface $loop
|
||||
* @param ConnectorInterface|null $connector
|
||||
* @return self
|
||||
*/
|
||||
public static function createFromLoop(LoopInterface $loop, ConnectorInterface $connector = null, MessageFactory $messageFactory)
|
||||
{
|
||||
return new self(new HttpClient($loop, $connector), $messageFactory);
|
||||
}
|
||||
|
||||
private $http;
|
||||
private $messageFactory;
|
||||
|
||||
/**
|
||||
* [internal] Instantiate Sender
|
||||
*
|
||||
* @param HttpClient $http
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(HttpClient $http, MessageFactory $messageFactory)
|
||||
{
|
||||
$this->http = $http;
|
||||
$this->messageFactory = $messageFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @internal
|
||||
* @param RequestInterface $request
|
||||
* @return PromiseInterface Promise<ResponseInterface, Exception>
|
||||
*/
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
$body = $request->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size !== null && $size !== 0) {
|
||||
// automatically assign a "Content-Length" request header if the body size is known and non-empty
|
||||
$request = $request->withHeader('Content-Length', (string)$size);
|
||||
} elseif ($size === 0 && \in_array($request->getMethod(), array('POST', 'PUT', 'PATCH'))) {
|
||||
// only assign a "Content-Length: 0" request header if the body is expected for certain methods
|
||||
$request = $request->withHeader('Content-Length', '0');
|
||||
} elseif ($body instanceof ReadableStreamInterface && $body->isReadable() && !$request->hasHeader('Content-Length')) {
|
||||
// use "Transfer-Encoding: chunked" when this is a streaming body and body size is unknown
|
||||
$request = $request->withHeader('Transfer-Encoding', 'chunked');
|
||||
} else {
|
||||
// do not use chunked encoding if size is known or if this is an empty request body
|
||||
$size = 0;
|
||||
}
|
||||
|
||||
$headers = array();
|
||||
foreach ($request->getHeaders() as $name => $values) {
|
||||
$headers[$name] = implode(', ', $values);
|
||||
}
|
||||
|
||||
$requestStream = $this->http->request($request->getMethod(), (string)$request->getUri(), $headers, $request->getProtocolVersion());
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use ($requestStream) {
|
||||
// close request stream if request is cancelled
|
||||
$reject(new \RuntimeException('Request cancelled'));
|
||||
$requestStream->close();
|
||||
});
|
||||
|
||||
$requestStream->on('error', function($error) use ($deferred) {
|
||||
$deferred->reject($error);
|
||||
});
|
||||
|
||||
$messageFactory = $this->messageFactory;
|
||||
$requestStream->on('response', function (ResponseStream $responseStream) use ($deferred, $messageFactory, $request) {
|
||||
// apply response header values from response stream
|
||||
$deferred->resolve($messageFactory->response(
|
||||
$responseStream->getVersion(),
|
||||
$responseStream->getCode(),
|
||||
$responseStream->getReasonPhrase(),
|
||||
$responseStream->getHeaders(),
|
||||
$responseStream,
|
||||
$request->getMethod()
|
||||
));
|
||||
});
|
||||
|
||||
if ($body instanceof ReadableStreamInterface) {
|
||||
if ($body->isReadable()) {
|
||||
// length unknown => apply chunked transfer-encoding
|
||||
if ($size === null) {
|
||||
$body = new ChunkedEncoder($body);
|
||||
}
|
||||
|
||||
// pipe body into request stream
|
||||
// add dummy write to immediately start request even if body does not emit any data yet
|
||||
$body->pipe($requestStream);
|
||||
$requestStream->write('');
|
||||
|
||||
$body->on('close', $close = function () use ($deferred, $requestStream) {
|
||||
$deferred->reject(new \RuntimeException('Request failed because request body closed unexpectedly'));
|
||||
$requestStream->close();
|
||||
});
|
||||
$body->on('error', function ($e) use ($deferred, $requestStream, $close, $body) {
|
||||
$body->removeListener('close', $close);
|
||||
$deferred->reject(new \RuntimeException('Request failed because request body reported an error', 0, $e));
|
||||
$requestStream->close();
|
||||
});
|
||||
$body->on('end', function () use ($close, $body) {
|
||||
$body->removeListener('close', $close);
|
||||
});
|
||||
} else {
|
||||
// stream is not readable => end request without body
|
||||
$requestStream->end();
|
||||
}
|
||||
} else {
|
||||
// body is fully buffered => write as one chunk
|
||||
$requestStream->end((string)$body);
|
||||
}
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
305
vendor/clue/buzz-react/src/Io/Transaction.php
vendored
Normal file
305
vendor/clue/buzz-react/src/Io/Transaction.php
vendored
Normal file
@ -0,0 +1,305 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz\Io;
|
||||
|
||||
use Clue\React\Buzz\Message\ResponseException;
|
||||
use Clue\React\Buzz\Message\MessageFactory;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Transaction
|
||||
{
|
||||
private $sender;
|
||||
private $messageFactory;
|
||||
private $loop;
|
||||
|
||||
// context: http.timeout (ini_get('default_socket_timeout'): 60)
|
||||
private $timeout;
|
||||
|
||||
// context: http.follow_location (true)
|
||||
private $followRedirects = true;
|
||||
|
||||
// context: http.max_redirects (10)
|
||||
private $maxRedirects = 10;
|
||||
|
||||
// context: http.ignore_errors (false)
|
||||
private $obeySuccessCode = true;
|
||||
|
||||
private $streaming = false;
|
||||
|
||||
private $maximumSize = 16777216; // 16 MiB = 2^24 bytes
|
||||
|
||||
public function __construct(Sender $sender, MessageFactory $messageFactory, LoopInterface $loop)
|
||||
{
|
||||
$this->sender = $sender;
|
||||
$this->messageFactory = $messageFactory;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return self returns new instance, without modifying existing instance
|
||||
*/
|
||||
public function withOptions(array $options)
|
||||
{
|
||||
$transaction = clone $this;
|
||||
foreach ($options as $name => $value) {
|
||||
if (property_exists($transaction, $name)) {
|
||||
// restore default value if null is given
|
||||
if ($value === null) {
|
||||
$default = new self($this->sender, $this->messageFactory, $this->loop);
|
||||
$value = $default->$name;
|
||||
}
|
||||
|
||||
$transaction->$name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
$deferred = new Deferred(function () use (&$deferred) {
|
||||
if (isset($deferred->pending)) {
|
||||
$deferred->pending->cancel();
|
||||
unset($deferred->pending);
|
||||
}
|
||||
});
|
||||
|
||||
$deferred->numRequests = 0;
|
||||
|
||||
// use timeout from options or default to PHP's default_socket_timeout (60)
|
||||
$timeout = (float)($this->timeout !== null ? $this->timeout : ini_get("default_socket_timeout"));
|
||||
|
||||
$loop = $this->loop;
|
||||
$this->next($request, $deferred)->then(
|
||||
function (ResponseInterface $response) use ($deferred, $loop, &$timeout) {
|
||||
if (isset($deferred->timeout)) {
|
||||
$loop->cancelTimer($deferred->timeout);
|
||||
unset($deferred->timeout);
|
||||
}
|
||||
$timeout = -1;
|
||||
$deferred->resolve($response);
|
||||
},
|
||||
function ($e) use ($deferred, $loop, &$timeout) {
|
||||
if (isset($deferred->timeout)) {
|
||||
$loop->cancelTimer($deferred->timeout);
|
||||
unset($deferred->timeout);
|
||||
}
|
||||
$timeout = -1;
|
||||
$deferred->reject($e);
|
||||
}
|
||||
);
|
||||
|
||||
if ($timeout < 0) {
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
$body = $request->getBody();
|
||||
if ($body instanceof ReadableStreamInterface && $body->isReadable()) {
|
||||
$that = $this;
|
||||
$body->on('close', function () use ($that, $deferred, &$timeout) {
|
||||
if ($timeout >= 0) {
|
||||
$that->applyTimeout($deferred, $timeout);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$this->applyTimeout($deferred, $timeout);
|
||||
}
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param Deferred $deferred
|
||||
* @param number $timeout
|
||||
* @return void
|
||||
*/
|
||||
public function applyTimeout(Deferred $deferred, $timeout)
|
||||
{
|
||||
$deferred->timeout = $this->loop->addTimer($timeout, function () use ($timeout, $deferred) {
|
||||
$deferred->reject(new \RuntimeException(
|
||||
'Request timed out after ' . $timeout . ' seconds'
|
||||
));
|
||||
if (isset($deferred->pending)) {
|
||||
$deferred->pending->cancel();
|
||||
unset($deferred->pending);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function next(RequestInterface $request, Deferred $deferred)
|
||||
{
|
||||
$this->progress('request', array($request));
|
||||
|
||||
$that = $this;
|
||||
++$deferred->numRequests;
|
||||
|
||||
$promise = $this->sender->send($request);
|
||||
|
||||
if (!$this->streaming) {
|
||||
$promise = $promise->then(function ($response) use ($deferred, $that) {
|
||||
return $that->bufferResponse($response, $deferred);
|
||||
});
|
||||
}
|
||||
|
||||
$deferred->pending = $promise;
|
||||
|
||||
return $promise->then(
|
||||
function (ResponseInterface $response) use ($request, $that, $deferred) {
|
||||
return $that->onResponse($response, $request, $deferred);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param ResponseInterface $response
|
||||
* @return PromiseInterface Promise<ResponseInterface, Exception>
|
||||
*/
|
||||
public function bufferResponse(ResponseInterface $response, $deferred)
|
||||
{
|
||||
$stream = $response->getBody();
|
||||
|
||||
$size = $stream->getSize();
|
||||
if ($size !== null && $size > $this->maximumSize) {
|
||||
$stream->close();
|
||||
return \React\Promise\reject(new \OverflowException(
|
||||
'Response body size of ' . $size . ' bytes exceeds maximum of ' . $this->maximumSize . ' bytes',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0
|
||||
));
|
||||
}
|
||||
|
||||
// body is not streaming => already buffered
|
||||
if (!$stream instanceof ReadableStreamInterface) {
|
||||
return \React\Promise\resolve($response);
|
||||
}
|
||||
|
||||
// buffer stream and resolve with buffered body
|
||||
$messageFactory = $this->messageFactory;
|
||||
$maximumSize = $this->maximumSize;
|
||||
$promise = \React\Promise\Stream\buffer($stream, $maximumSize)->then(
|
||||
function ($body) use ($response, $messageFactory) {
|
||||
return $response->withBody($messageFactory->body($body));
|
||||
},
|
||||
function ($e) use ($stream, $maximumSize) {
|
||||
// try to close stream if buffering fails (or is cancelled)
|
||||
$stream->close();
|
||||
|
||||
if ($e instanceof \OverflowException) {
|
||||
$e = new \OverflowException(
|
||||
'Response body size exceeds maximum of ' . $maximumSize . ' bytes',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0
|
||||
);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
);
|
||||
|
||||
$deferred->pending = $promise;
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param ResponseInterface $response
|
||||
* @param RequestInterface $request
|
||||
* @throws ResponseException
|
||||
* @return ResponseInterface|PromiseInterface
|
||||
*/
|
||||
public function onResponse(ResponseInterface $response, RequestInterface $request, $deferred)
|
||||
{
|
||||
$this->progress('response', array($response, $request));
|
||||
|
||||
// follow 3xx (Redirection) response status codes if Location header is present and not explicitly disabled
|
||||
// @link https://tools.ietf.org/html/rfc7231#section-6.4
|
||||
if ($this->followRedirects && ($response->getStatusCode() >= 300 && $response->getStatusCode() < 400) && $response->hasHeader('Location')) {
|
||||
return $this->onResponseRedirect($response, $request, $deferred);
|
||||
}
|
||||
|
||||
// only status codes 200-399 are considered to be valid, reject otherwise
|
||||
if ($this->obeySuccessCode && ($response->getStatusCode() < 200 || $response->getStatusCode() >= 400)) {
|
||||
throw new ResponseException($response);
|
||||
}
|
||||
|
||||
// resolve our initial promise
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
* @param RequestInterface $request
|
||||
* @return PromiseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function onResponseRedirect(ResponseInterface $response, RequestInterface $request, Deferred $deferred)
|
||||
{
|
||||
// resolve location relative to last request URI
|
||||
$location = $this->messageFactory->uriRelative($request->getUri(), $response->getHeaderLine('Location'));
|
||||
|
||||
$request = $this->makeRedirectRequest($request, $location);
|
||||
$this->progress('redirect', array($request));
|
||||
|
||||
if ($deferred->numRequests >= $this->maxRedirects) {
|
||||
throw new \RuntimeException('Maximum number of redirects (' . $this->maxRedirects . ') exceeded');
|
||||
}
|
||||
|
||||
return $this->next($request, $deferred);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param UriInterface $location
|
||||
* @return RequestInterface
|
||||
*/
|
||||
private function makeRedirectRequest(RequestInterface $request, UriInterface $location)
|
||||
{
|
||||
$originalHost = $request->getUri()->getHost();
|
||||
$request = $request
|
||||
->withoutHeader('Host')
|
||||
->withoutHeader('Content-Type')
|
||||
->withoutHeader('Content-Length');
|
||||
|
||||
// Remove authorization if changing hostnames (but not if just changing ports or protocols).
|
||||
if ($location->getHost() !== $originalHost) {
|
||||
$request = $request->withoutHeader('Authorization');
|
||||
}
|
||||
|
||||
// naïve approach..
|
||||
$method = ($request->getMethod() === 'HEAD') ? 'HEAD' : 'GET';
|
||||
|
||||
return $this->messageFactory->request($method, $location, $request->getHeaders());
|
||||
}
|
||||
|
||||
private function progress($name, array $args = array())
|
||||
{
|
||||
return;
|
||||
|
||||
echo $name;
|
||||
|
||||
foreach ($args as $arg) {
|
||||
echo ' ';
|
||||
if ($arg instanceof ResponseInterface) {
|
||||
echo 'HTTP/' . $arg->getProtocolVersion() . ' ' . $arg->getStatusCode() . ' ' . $arg->getReasonPhrase();
|
||||
} elseif ($arg instanceof RequestInterface) {
|
||||
echo $arg->getMethod() . ' ' . $arg->getRequestTarget() . ' HTTP/' . $arg->getProtocolVersion();
|
||||
} else {
|
||||
echo $arg;
|
||||
}
|
||||
}
|
||||
|
||||
echo PHP_EOL;
|
||||
}
|
||||
}
|
139
vendor/clue/buzz-react/src/Message/MessageFactory.php
vendored
Normal file
139
vendor/clue/buzz-react/src/Message/MessageFactory.php
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz\Message;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use RingCentral\Psr7\Request;
|
||||
use RingCentral\Psr7\Response;
|
||||
use RingCentral\Psr7\Uri;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class MessageFactory
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of RequestInterface for the given request parameters
|
||||
*
|
||||
* @param string $method
|
||||
* @param string|UriInterface $uri
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $content
|
||||
* @param string $protocolVersion
|
||||
* @return Request
|
||||
*/
|
||||
public function request($method, $uri, $headers = array(), $content = '', $protocolVersion = '1.1')
|
||||
{
|
||||
return new Request($method, $uri, $headers, $this->body($content), $protocolVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of ResponseInterface for the given response parameters
|
||||
*
|
||||
* @param string $protocolVersion
|
||||
* @param int $status
|
||||
* @param string $reason
|
||||
* @param array $headers
|
||||
* @param ReadableStreamInterface|string $body
|
||||
* @param ?string $requestMethod
|
||||
* @return Response
|
||||
* @uses self::body()
|
||||
*/
|
||||
public function response($protocolVersion, $status, $reason, $headers = array(), $body = '', $requestMethod = null)
|
||||
{
|
||||
$response = new Response($status, $headers, $body instanceof ReadableStreamInterface ? null : $body, $protocolVersion, $reason);
|
||||
|
||||
if ($body instanceof ReadableStreamInterface) {
|
||||
$length = null;
|
||||
$code = $response->getStatusCode();
|
||||
if ($requestMethod === 'HEAD' || ($code >= 100 && $code < 200) || $code == 204 || $code == 304) {
|
||||
$length = 0;
|
||||
} elseif (\strtolower($response->getHeaderLine('Transfer-Encoding')) === 'chunked') {
|
||||
$length = null;
|
||||
} elseif ($response->hasHeader('Content-Length')) {
|
||||
$length = (int)$response->getHeaderLine('Content-Length');
|
||||
}
|
||||
|
||||
$response = $response->withBody(new ReadableBodyStream($body, $length));
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of StreamInterface for the given body contents
|
||||
*
|
||||
* @param ReadableStreamInterface|string $body
|
||||
* @return StreamInterface
|
||||
*/
|
||||
public function body($body)
|
||||
{
|
||||
if ($body instanceof ReadableStreamInterface) {
|
||||
return new ReadableBodyStream($body);
|
||||
}
|
||||
|
||||
return \RingCentral\Psr7\stream_for($body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of UriInterface for the given URI string
|
||||
*
|
||||
* @param string $uri
|
||||
* @return UriInterface
|
||||
*/
|
||||
public function uri($uri)
|
||||
{
|
||||
return new Uri($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of UriInterface for the given URI string relative to the given base URI
|
||||
*
|
||||
* @param UriInterface $base
|
||||
* @param string $uri
|
||||
* @return UriInterface
|
||||
*/
|
||||
public function uriRelative(UriInterface $base, $uri)
|
||||
{
|
||||
return Uri::resolve($base, $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the given relative or absolute $uri by appending it behind $this base URI
|
||||
*
|
||||
* The given $uri parameter can be either a relative or absolute URI and
|
||||
* as such can not contain any URI template placeholders.
|
||||
*
|
||||
* As such, the outcome of this method represents a valid, absolute URI
|
||||
* which will be returned as an instance implementing `UriInterface`.
|
||||
*
|
||||
* If the given $uri is a relative URI, it will simply be appended behind $base URI.
|
||||
*
|
||||
* If the given $uri is an absolute URI, it will simply be returned as-is.
|
||||
*
|
||||
* @param UriInterface $uri
|
||||
* @param UriInterface $base
|
||||
* @return UriInterface
|
||||
*/
|
||||
public function expandBase(UriInterface $uri, UriInterface $base)
|
||||
{
|
||||
if ($uri->getScheme() !== '') {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
$uri = (string)$uri;
|
||||
$base = (string)$base;
|
||||
|
||||
if ($uri !== '' && substr($base, -1) !== '/' && substr($uri, 0, 1) !== '?') {
|
||||
$base .= '/';
|
||||
}
|
||||
|
||||
if (isset($uri[0]) && $uri[0] === '/') {
|
||||
$uri = substr($uri, 1);
|
||||
}
|
||||
|
||||
return $this->uri($base . $uri);
|
||||
}
|
||||
}
|
153
vendor/clue/buzz-react/src/Message/ReadableBodyStream.php
vendored
Normal file
153
vendor/clue/buzz-react/src/Message/ReadableBodyStream.php
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz\Message;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ReadableBodyStream extends EventEmitter implements ReadableStreamInterface, StreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $position = 0;
|
||||
private $size;
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input, $size = null)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->size = $size;
|
||||
|
||||
$that = $this;
|
||||
$pos =& $this->position;
|
||||
$input->on('data', function ($data) use ($that, &$pos, $size) {
|
||||
$that->emit('data', array($data));
|
||||
|
||||
$pos += \strlen($data);
|
||||
if ($size !== null && $pos >= $size) {
|
||||
$that->handleEnd();
|
||||
}
|
||||
});
|
||||
$input->on('error', function ($error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
$that->close();
|
||||
});
|
||||
$input->on('end', array($that, 'handleEnd'));
|
||||
$input->on('close', array($that, 'close'));
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->closed = true;
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function eof()
|
||||
{
|
||||
return !$this->isReadable();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function detach()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function tell()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function write($string)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function read($length)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return ($key === null) ? array() : null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if ($this->position !== $this->size && $this->size !== null) {
|
||||
$this->emit('error', array(new \UnderflowException('Unexpected end of response body after ' . $this->position . '/' . $this->size . ' bytes')));
|
||||
} else {
|
||||
$this->emit('end');
|
||||
}
|
||||
|
||||
$this->close();
|
||||
}
|
||||
}
|
43
vendor/clue/buzz-react/src/Message/ResponseException.php
vendored
Normal file
43
vendor/clue/buzz-react/src/Message/ResponseException.php
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz\Message;
|
||||
|
||||
use RuntimeException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* The `ResponseException` is an `Exception` sub-class that will be used to reject
|
||||
* a request promise if the remote server returns a non-success status code
|
||||
* (anything but 2xx or 3xx).
|
||||
* You can control this behavior via the [`withRejectErrorResponse()` method](#withrejecterrorresponse).
|
||||
*
|
||||
* The `getCode(): int` method can be used to
|
||||
* return the HTTP response status code.
|
||||
*/
|
||||
class ResponseException extends RuntimeException
|
||||
{
|
||||
private $response;
|
||||
|
||||
public function __construct(ResponseInterface $response, $message = null, $code = null, $previous = null)
|
||||
{
|
||||
if ($message === null) {
|
||||
$message = 'HTTP status code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ')';
|
||||
}
|
||||
if ($code === null) {
|
||||
$code = $response->getStatusCode();
|
||||
}
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access its underlying [`ResponseInterface`](#responseinterface) object.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
21
vendor/clue/connection-manager-extra/LICENSE
vendored
Normal file
21
vendor/clue/connection-manager-extra/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
29
vendor/clue/connection-manager-extra/composer.json
vendored
Normal file
29
vendor/clue/connection-manager-extra/composer.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "clue/connection-manager-extra",
|
||||
"description": "Extra decorators for creating async TCP/IP connections, built on top of ReactPHP's Socket component",
|
||||
"keywords": ["Socket", "network", "connection", "timeout", "delay", "reject", "repeat", "retry", "random", "acl", "firewall", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-connection-manager-extra",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "ConnectionManager\\Extra\\": "src" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "ConnectionManager\\Tests\\Extra\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/socket": "^1.0 || ^0.8 || ^0.7",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
|
||||
"react/promise": "^2.1 || ^1.2.1",
|
||||
"react/promise-timer": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8"
|
||||
}
|
||||
}
|
30
vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php
vendored
Normal file
30
vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Timer;
|
||||
|
||||
class ConnectionManagerDelay implements ConnectorInterface
|
||||
{
|
||||
private $connectionManager;
|
||||
private $delay;
|
||||
private $loop;
|
||||
|
||||
public function __construct(ConnectorInterface $connectionManager, $delay, LoopInterface $loop)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->delay = $delay;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$connectionManager = $this->connectionManager;
|
||||
|
||||
return Timer\resolve($this->delay, $this->loop)->then(function () use ($connectionManager, $uri) {
|
||||
return $connectionManager->connect($uri);
|
||||
});
|
||||
}
|
||||
}
|
41
vendor/clue/connection-manager-extra/src/ConnectionManagerReject.php
vendored
Normal file
41
vendor/clue/connection-manager-extra/src/ConnectionManagerReject.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Promise;
|
||||
use Exception;
|
||||
|
||||
// a simple connection manager that rejects every single connection attempt
|
||||
class ConnectionManagerReject implements ConnectorInterface
|
||||
{
|
||||
private $reason = 'Connection rejected';
|
||||
|
||||
/**
|
||||
* @param null|string|callable $reason
|
||||
*/
|
||||
public function __construct($reason = null)
|
||||
{
|
||||
if ($reason !== null) {
|
||||
$this->reason = $reason;
|
||||
}
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$reason = $this->reason;
|
||||
if (!is_string($reason)) {
|
||||
try {
|
||||
$reason = $reason($uri);
|
||||
} catch (\Exception $e) {
|
||||
$reason = $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$reason instanceof \Exception) {
|
||||
$reason = new Exception($reason);
|
||||
}
|
||||
|
||||
return Promise\reject($reason);
|
||||
}
|
||||
}
|
52
vendor/clue/connection-manager-extra/src/ConnectionManagerRepeat.php
vendored
Normal file
52
vendor/clue/connection-manager-extra/src/ConnectionManagerRepeat.php
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use InvalidArgumentException;
|
||||
use Exception;
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
|
||||
class ConnectionManagerRepeat implements ConnectorInterface
|
||||
{
|
||||
protected $connectionManager;
|
||||
protected $maximumTries;
|
||||
|
||||
public function __construct(ConnectorInterface $connectionManager, $maximumTries)
|
||||
{
|
||||
if ($maximumTries < 1) {
|
||||
throw new InvalidArgumentException('Maximum number of tries must be >= 1');
|
||||
}
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->maximumTries = $maximumTries;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$tries = $this->maximumTries;
|
||||
$connector = $this->connectionManager;
|
||||
|
||||
return new Promise(function ($resolve, $reject) use ($uri, &$pending, &$tries, $connector) {
|
||||
$try = function ($error = null) use (&$try, &$pending, &$tries, $uri, $connector, $resolve, $reject) {
|
||||
if ($tries > 0) {
|
||||
--$tries;
|
||||
$pending = $connector->connect($uri);
|
||||
$pending->then($resolve, $try);
|
||||
} else {
|
||||
$reject(new Exception('Connection still fails even after retrying', 0, $error));
|
||||
}
|
||||
};
|
||||
|
||||
$try();
|
||||
}, function ($_, $reject) use (&$pending, &$tries) {
|
||||
// stop retrying, reject results and cancel pending attempt
|
||||
$tries = 0;
|
||||
$reject(new \RuntimeException('Cancelled'));
|
||||
|
||||
if ($pending instanceof CancellablePromiseInterface) {
|
||||
$pending->cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
26
vendor/clue/connection-manager-extra/src/ConnectionManagerSwappable.php
vendored
Normal file
26
vendor/clue/connection-manager-extra/src/ConnectionManagerSwappable.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
|
||||
// connection manager decorator which simplifies exchanging the actual connection manager during runtime
|
||||
class ConnectionManagerSwappable implements ConnectorInterface
|
||||
{
|
||||
protected $connectionManager;
|
||||
|
||||
public function __construct(ConnectorInterface $connectionManager)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
return $this->connectionManager->connect($uri);
|
||||
}
|
||||
|
||||
public function setConnectionManager(ConnectorInterface $connectionManager)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
}
|
||||
}
|
35
vendor/clue/connection-manager-extra/src/ConnectionManagerTimeout.php
vendored
Normal file
35
vendor/clue/connection-manager-extra/src/ConnectionManagerTimeout.php
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Timer;
|
||||
|
||||
class ConnectionManagerTimeout implements ConnectorInterface
|
||||
{
|
||||
private $connectionManager;
|
||||
private $timeout;
|
||||
private $loop;
|
||||
|
||||
public function __construct(ConnectorInterface $connectionManager, $timeout, LoopInterface $loop)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->timeout = $timeout;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$promise = $this->connectionManager->connect($uri);
|
||||
|
||||
return Timer\timeout($promise, $this->timeout, $this->loop)->then(null, function ($e) use ($promise) {
|
||||
// connection successfully established but timeout already expired => close successful connection
|
||||
$promise->then(function ($connection) {
|
||||
$connection->end();
|
||||
});
|
||||
|
||||
throw $e;
|
||||
});
|
||||
}
|
||||
}
|
36
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php
vendored
Normal file
36
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
use ConnectionManager\Extra\Multiple\ConnectionManagerConsecutive;
|
||||
use React\Promise;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
|
||||
class ConnectionManagerConcurrent extends ConnectionManagerConsecutive
|
||||
{
|
||||
public function connect($uri)
|
||||
{
|
||||
$all = array();
|
||||
foreach ($this->managers as $connector) {
|
||||
/* @var $connection Connector */
|
||||
$all []= $connector->connect($uri);
|
||||
}
|
||||
return Promise\any($all)->then(function ($conn) use ($all) {
|
||||
// a connection attempt succeeded
|
||||
// => cancel all pending connection attempts
|
||||
foreach ($all as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
|
||||
// if promise resolves despite cancellation, immediately close stream
|
||||
$promise->then(function ($stream) use ($conn) {
|
||||
if ($stream !== $conn) {
|
||||
$stream->close();
|
||||
}
|
||||
});
|
||||
}
|
||||
return $conn;
|
||||
});
|
||||
}
|
||||
}
|
62
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConsecutive.php
vendored
Normal file
62
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConsecutive.php
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Promise;
|
||||
use UnderflowException;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
|
||||
class ConnectionManagerConsecutive implements ConnectorInterface
|
||||
{
|
||||
protected $managers;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ConnectorInterface[] $managers
|
||||
*/
|
||||
public function __construct(array $managers)
|
||||
{
|
||||
if (!$managers) {
|
||||
throw new \InvalidArgumentException('List of connectors must not be empty');
|
||||
}
|
||||
$this->managers = $managers;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
return $this->tryConnection($this->managers, $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ConnectorInterface[] $managers
|
||||
* @param string $uri
|
||||
* @return Promise
|
||||
* @internal
|
||||
*/
|
||||
public function tryConnection(array $managers, $uri)
|
||||
{
|
||||
return new Promise\Promise(function ($resolve, $reject) use (&$managers, &$pending, $uri) {
|
||||
$try = function () use (&$try, &$managers, $uri, $resolve, $reject, &$pending) {
|
||||
if (!$managers) {
|
||||
return $reject(new UnderflowException('No more managers to try to connect through'));
|
||||
}
|
||||
|
||||
$manager = array_shift($managers);
|
||||
$pending = $manager->connect($uri);
|
||||
$pending->then($resolve, $try);
|
||||
};
|
||||
|
||||
$try();
|
||||
}, function ($_, $reject) use (&$managers, &$pending) {
|
||||
// stop retrying, reject results and cancel pending attempt
|
||||
$managers = array();
|
||||
$reject(new \RuntimeException('Cancelled'));
|
||||
|
||||
if ($pending instanceof CancellablePromiseInterface) {
|
||||
$pending->cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
14
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerRandom.php
vendored
Normal file
14
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerRandom.php
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
class ConnectionManagerRandom extends ConnectionManagerConsecutive
|
||||
{
|
||||
public function connect($uri)
|
||||
{
|
||||
$managers = $this->managers;
|
||||
shuffle($managers);
|
||||
|
||||
return $this->tryConnection($managers, $uri);
|
||||
}
|
||||
}
|
111
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerSelective.php
vendored
Normal file
111
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerSelective.php
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Promise;
|
||||
use UnderflowException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ConnectionManagerSelective implements ConnectorInterface
|
||||
{
|
||||
private $managers;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ConnectorInterface[] $managers
|
||||
*/
|
||||
public function __construct(array $managers)
|
||||
{
|
||||
foreach ($managers as $filter => $manager) {
|
||||
$host = $filter;
|
||||
$portMin = 0;
|
||||
$portMax = 65535;
|
||||
|
||||
// search colon (either single one OR preceded by "]" due to IPv6)
|
||||
$colon = strrpos($host, ':');
|
||||
if ($colon !== false && (strpos($host, ':') === $colon || substr($host, $colon - 1, 1) === ']' )) {
|
||||
if (!isset($host[$colon + 1])) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has no port after colon');
|
||||
}
|
||||
|
||||
$minus = strpos($host, '-', $colon);
|
||||
if ($minus === false) {
|
||||
$portMin = $portMax = (int)substr($host, $colon + 1);
|
||||
|
||||
if (substr($host, $colon + 1) !== (string)$portMin) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has no valid port after colon');
|
||||
}
|
||||
} else {
|
||||
$portMin = (int)substr($host, $colon + 1, ($minus - $colon));
|
||||
$portMax = (int)substr($host, $minus + 1);
|
||||
|
||||
if (substr($host, $colon + 1) !== ($portMin . '-' . $portMax)) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has no valid port range after colon');
|
||||
}
|
||||
|
||||
if ($portMin > $portMax) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has port range mixed up');
|
||||
}
|
||||
}
|
||||
$host = substr($host, 0, $colon);
|
||||
}
|
||||
|
||||
if ($host === '') {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has an empty host');
|
||||
}
|
||||
|
||||
if (!$manager instanceof ConnectorInterface) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" is not a valid connector');
|
||||
}
|
||||
}
|
||||
|
||||
$this->managers = $managers;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$parts = parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri);
|
||||
if (!isset($parts) || !isset($parts['scheme'], $parts['host'], $parts['port'])) {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid URI'));
|
||||
}
|
||||
|
||||
$connector = $this->getConnectorForTarget(
|
||||
trim($parts['host'], '[]'),
|
||||
$parts['port']
|
||||
);
|
||||
|
||||
if ($connector === null) {
|
||||
return Promise\reject(new UnderflowException('No connector for given target found'));
|
||||
}
|
||||
|
||||
return $connector->connect($uri);
|
||||
}
|
||||
|
||||
private function getConnectorForTarget($targetHost, $targetPort)
|
||||
{
|
||||
foreach ($this->managers as $host => $connector) {
|
||||
$portMin = 0;
|
||||
$portMax = 65535;
|
||||
|
||||
// search colon (either single one OR preceded by "]" due to IPv6)
|
||||
$colon = strrpos($host, ':');
|
||||
if ($colon !== false && (strpos($host, ':') === $colon || substr($host, $colon - 1, 1) === ']' )) {
|
||||
$minus = strpos($host, '-', $colon);
|
||||
if ($minus === false) {
|
||||
$portMin = $portMax = (int)substr($host, $colon + 1);
|
||||
} else {
|
||||
$portMin = (int)substr($host, $colon + 1, ($minus - $colon));
|
||||
$portMax = (int)substr($host, $minus + 1);
|
||||
}
|
||||
$host = trim(substr($host, 0, $colon), '[]');
|
||||
}
|
||||
|
||||
if ($targetPort >= $portMin && $targetPort <= $portMax && fnmatch($host, $targetHost)) {
|
||||
return $connector;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
21
vendor/clue/http-proxy-react/LICENSE
vendored
Normal file
21
vendor/clue/http-proxy-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
31
vendor/clue/http-proxy-react/composer.json
vendored
Normal file
31
vendor/clue/http-proxy-react/composer.json
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "clue/http-proxy-react",
|
||||
"description": "Async HTTP proxy connector, tunnel any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP",
|
||||
"keywords": ["HTTP", "CONNECT", "proxy", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-http-proxy",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/promise": " ^2.1 || ^1.2.1",
|
||||
"react/socket": "^1.9",
|
||||
"ringcentral/psr7": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.1",
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/http": "^1.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\HttpProxy\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\HttpProxy\\": "tests/" }
|
||||
}
|
||||
}
|
274
vendor/clue/http-proxy-react/src/ProxyConnector.php
vendored
Normal file
274
vendor/clue/http-proxy-react/src/ProxyConnector.php
vendored
Normal file
@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\HttpProxy;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use RingCentral\Psr7;
|
||||
use React\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\FixedUriConnector;
|
||||
use React\Socket\UnixConnector;
|
||||
|
||||
/**
|
||||
* A simple Connector that uses an HTTP CONNECT proxy to create plain TCP/IP connections to any destination
|
||||
*
|
||||
* [you] -> [proxy] -> [destination]
|
||||
*
|
||||
* This is most frequently used to issue HTTPS requests to your destination.
|
||||
* However, this is actually performed on a higher protocol layer and this
|
||||
* connector is actually inherently a general-purpose plain TCP/IP connector.
|
||||
*
|
||||
* Note that HTTP CONNECT proxies often restrict which ports one may connect to.
|
||||
* Many (public) proxy servers do in fact limit this to HTTPS (443) only.
|
||||
*
|
||||
* If you want to establish a TLS connection (such as HTTPS) between you and
|
||||
* your destination, you may want to wrap this connector in a SecureConnector
|
||||
* instance.
|
||||
*
|
||||
* Note that communication between the client and the proxy is usually via an
|
||||
* unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
|
||||
* setup, because you can still establish a TLS connection between you and the
|
||||
* destination host as above.
|
||||
*
|
||||
* If you want to connect to a (rather rare) HTTPS proxy, you may want use its
|
||||
* HTTPS port (443) and use a SecureConnector instance to create a secure
|
||||
* connection to the proxy.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc7231#section-4.3.6
|
||||
*/
|
||||
class ProxyConnector implements ConnectorInterface
|
||||
{
|
||||
private $connector;
|
||||
private $proxyUri;
|
||||
private $headers = '';
|
||||
|
||||
/**
|
||||
* Instantiate a new ProxyConnector which uses the given $proxyUrl
|
||||
*
|
||||
* @param string $proxyUrl The proxy URL may or may not contain a scheme and
|
||||
* port definition. The default port will be `80` for HTTP (or `443` for
|
||||
* HTTPS), but many common HTTP proxy servers use custom ports.
|
||||
* @param ?ConnectorInterface $connector (Optional) Connector to use.
|
||||
* @param array $httpHeaders Custom HTTP headers to be sent to the proxy.
|
||||
* @throws InvalidArgumentException if the proxy URL is invalid
|
||||
*/
|
||||
public function __construct($proxyUrl, ConnectorInterface $connector = null, array $httpHeaders = array())
|
||||
{
|
||||
// support `http+unix://` scheme for Unix domain socket (UDS) paths
|
||||
if (preg_match('/^http\+unix:\/\/(.*?@)?(.+?)$/', $proxyUrl, $match)) {
|
||||
// rewrite URI to parse authentication from dummy host
|
||||
$proxyUrl = 'http://' . $match[1] . 'localhost';
|
||||
|
||||
// connector uses Unix transport scheme and explicit path given
|
||||
$connector = new FixedUriConnector(
|
||||
'unix://' . $match[2],
|
||||
$connector ?: new UnixConnector()
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($proxyUrl, '://') === false) {
|
||||
$proxyUrl = 'http://' . $proxyUrl;
|
||||
}
|
||||
|
||||
$parts = parse_url($proxyUrl);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host']) || ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')) {
|
||||
throw new InvalidArgumentException('Invalid proxy URL "' . $proxyUrl . '"');
|
||||
}
|
||||
|
||||
// apply default port and TCP/TLS transport for given scheme
|
||||
if (!isset($parts['port'])) {
|
||||
$parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
|
||||
}
|
||||
$parts['scheme'] = $parts['scheme'] === 'https' ? 'tls' : 'tcp';
|
||||
|
||||
$this->connector = $connector ?: new Connector();
|
||||
$this->proxyUri = $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port'];
|
||||
|
||||
// prepare Proxy-Authorization header if URI contains username/password
|
||||
if (isset($parts['user']) || isset($parts['pass'])) {
|
||||
$this->headers = 'Proxy-Authorization: Basic ' . base64_encode(
|
||||
rawurldecode($parts['user'] . ':' . (isset($parts['pass']) ? $parts['pass'] : ''))
|
||||
) . "\r\n";
|
||||
}
|
||||
|
||||
// append any additional custom request headers
|
||||
foreach ($httpHeaders as $name => $values) {
|
||||
foreach ((array)$values as $value) {
|
||||
$this->headers .= $name . ': ' . $value . "\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
if (strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
}
|
||||
|
||||
$parts = parse_url($uri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
|
||||
}
|
||||
|
||||
$target = $parts['host'] . ':' . $parts['port'];
|
||||
|
||||
// construct URI to HTTP CONNECT proxy server to connect to
|
||||
$proxyUri = $this->proxyUri;
|
||||
|
||||
// append path from URI if given
|
||||
if (isset($parts['path'])) {
|
||||
$proxyUri .= $parts['path'];
|
||||
}
|
||||
|
||||
// parse query args
|
||||
$args = array();
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $args);
|
||||
}
|
||||
|
||||
// append hostname from URI to query string unless explicitly given
|
||||
if (!isset($args['hostname'])) {
|
||||
$args['hostname'] = trim($parts['host'], '[]');
|
||||
}
|
||||
|
||||
// append query string
|
||||
$proxyUri .= '?' . http_build_query($args, '', '&');
|
||||
|
||||
// append fragment from URI if given
|
||||
if (isset($parts['fragment'])) {
|
||||
$proxyUri .= '#' . $parts['fragment'];
|
||||
}
|
||||
|
||||
$connecting = $this->connector->connect($proxyUri);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use ($connecting, $uri) {
|
||||
$reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' cancelled while waiting for proxy (ECONNABORTED)',
|
||||
defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
|
||||
// either close active connection or cancel pending connection attempt
|
||||
$connecting->then(function (ConnectionInterface $stream) {
|
||||
$stream->close();
|
||||
});
|
||||
$connecting->cancel();
|
||||
});
|
||||
|
||||
$headers = $this->headers;
|
||||
$connecting->then(function (ConnectionInterface $stream) use ($target, $headers, $deferred, $uri) {
|
||||
// keep buffering data until headers are complete
|
||||
$buffer = '';
|
||||
$stream->on('data', $fn = function ($chunk) use (&$buffer, $deferred, $stream, &$fn, $uri) {
|
||||
$buffer .= $chunk;
|
||||
|
||||
$pos = strpos($buffer, "\r\n\r\n");
|
||||
if ($pos !== false) {
|
||||
// end of headers received => stop buffering
|
||||
$stream->removeListener('data', $fn);
|
||||
$fn = null;
|
||||
|
||||
// try to parse headers as response message
|
||||
try {
|
||||
$response = Psr7\parse_response(substr($buffer, 0, $pos));
|
||||
} catch (Exception $e) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy returned invalid response (EBADMSG)',
|
||||
defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71,
|
||||
$e
|
||||
));
|
||||
$stream->close();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($response->getStatusCode() === 407) {
|
||||
// map status code 407 (Proxy Authentication Required) to EACCES
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy denied access with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (EACCES)',
|
||||
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
|
||||
));
|
||||
$stream->close();
|
||||
return;
|
||||
} elseif ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
|
||||
// map non-2xx status code to ECONNREFUSED
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy refused connection with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
|
||||
));
|
||||
$stream->close();
|
||||
return;
|
||||
}
|
||||
|
||||
// all okay, resolve with stream instance
|
||||
$deferred->resolve($stream);
|
||||
|
||||
// emit remaining incoming as data event
|
||||
$buffer = (string)substr($buffer, $pos + 4);
|
||||
if ($buffer !== '') {
|
||||
$stream->emit('data', array($buffer));
|
||||
$buffer = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// stop buffering when 8 KiB have been read
|
||||
if (isset($buffer[8192])) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy response headers exceed maximum of 8 KiB (EMSGSIZE)',
|
||||
defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
$stream->close();
|
||||
}
|
||||
});
|
||||
|
||||
$stream->on('error', function (Exception $e) use ($deferred, $uri) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy caused a stream error (EIO)',
|
||||
defined('SOCKET_EIO') ? SOCKET_EIO : 5,
|
||||
$e
|
||||
));
|
||||
});
|
||||
|
||||
$stream->on('close', function () use ($deferred, $uri) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy was lost while waiting for response (ECONNRESET)',
|
||||
defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104
|
||||
));
|
||||
});
|
||||
|
||||
$stream->write("CONNECT " . $target . " HTTP/1.1\r\nHost: " . $target . "\r\n" . $headers . "\r\n");
|
||||
}, function (Exception $e) use ($deferred, $uri) {
|
||||
$deferred->reject($e = new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy failed (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111,
|
||||
$e
|
||||
));
|
||||
|
||||
// avoid garbage references by replacing all closures in call stack.
|
||||
// what a lovely piece of code!
|
||||
$r = new \ReflectionProperty('Exception', 'trace');
|
||||
$r->setAccessible(true);
|
||||
$trace = $r->getValue($e);
|
||||
|
||||
// Exception trace arguments are not available on some PHP 7.4 installs
|
||||
// @codeCoverageIgnoreStart
|
||||
foreach ($trace as &$one) {
|
||||
if (isset($one['args'])) {
|
||||
foreach ($one['args'] as &$arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$arg = 'Object(' . get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$r->setValue($e, $trace);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
21
vendor/clue/mq-react/LICENSE
vendored
Normal file
21
vendor/clue/mq-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
29
vendor/clue/mq-react/composer.json
vendored
Normal file
29
vendor/clue/mq-react/composer.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "clue/mq-react",
|
||||
"description": "Mini Queue, the lightweight in-memory message queue to concurrently do many (but not too many) things at once, built on top of ReactPHP",
|
||||
"keywords": ["Message Queue", "Mini Queue", "job", "message", "worker", "queue", "rate limit", "throttle", "concurrency", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-mq",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Mq\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Mq\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/promise": "^2.2.1 || ^1.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.0",
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/http": "^1.5"
|
||||
}
|
||||
}
|
448
vendor/clue/mq-react/src/Queue.php
vendored
Normal file
448
vendor/clue/mq-react/src/Queue.php
vendored
Normal file
@ -0,0 +1,448 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Mq;
|
||||
|
||||
use React\Promise;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* The `Queue` is responsible for managing your operations and ensuring not too
|
||||
* many operations are executed at once. It's a very simple and lightweight
|
||||
* in-memory implementation of the
|
||||
* [leaky bucket](https://en.wikipedia.org/wiki/Leaky_bucket#As_a_queue) algorithm.
|
||||
*
|
||||
* This means that you control how many operations can be executed concurrently.
|
||||
* If you add a job to the queue and it still below the limit, it will be executed
|
||||
* immediately. If you keep adding new jobs to the queue and its concurrency limit
|
||||
* is reached, it will not start a new operation and instead queue this for future
|
||||
* execution. Once one of the pending operations complete, it will pick the next
|
||||
* job from the queue and execute this operation.
|
||||
*/
|
||||
class Queue implements \Countable
|
||||
{
|
||||
private $concurrency;
|
||||
private $limit;
|
||||
private $handler;
|
||||
|
||||
private $pending = 0;
|
||||
private $queue = array();
|
||||
|
||||
/**
|
||||
* Concurrently process all given jobs through the given `$handler`.
|
||||
*
|
||||
* This is a convenience method which uses the `Queue` internally to
|
||||
* schedule all jobs while limiting concurrency to ensure no more than
|
||||
* `$concurrency` jobs ever run at once. It will return a promise which
|
||||
* resolves with the results of all jobs on success.
|
||||
*
|
||||
* ```php
|
||||
* $browser = new React\Http\Browser();
|
||||
*
|
||||
* $promise = Queue::all(3, $urls, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
*
|
||||
* $promise->then(function (array $responses) {
|
||||
* echo 'All ' . count($responses) . ' successful!' . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* If either of the jobs fail, it will reject the resulting promise and will
|
||||
* try to cancel all outstanding jobs. Similarly, calling `cancel()` on the
|
||||
* resulting promise will try to cancel all outstanding jobs. See
|
||||
* [promises](#promises) and [cancellation](#cancellation) for details.
|
||||
*
|
||||
* The `$concurrency` parameter sets a new soft limit for the maximum number
|
||||
* of jobs to handle concurrently. Finding a good concurrency limit depends
|
||||
* on your particular use case. It's common to limit concurrency to a rather
|
||||
* small value, as doing more than a dozen of things at once may easily
|
||||
* overwhelm the receiving side. Using a `1` value will ensure that all jobs
|
||||
* are processed one after another, effectively creating a "waterfall" of
|
||||
* jobs. Using a value less than 1 will reject with an
|
||||
* `InvalidArgumentException` without processing any jobs.
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently
|
||||
* $promise = Queue::all(10, $jobs, $handler);
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // handle each job after another without concurrency (waterfall)
|
||||
* $promise = Queue::all(1, $jobs, $handler);
|
||||
* ```
|
||||
*
|
||||
* The `$jobs` parameter must be an array with all jobs to process. Each
|
||||
* value in this array will be passed to the `$handler` to start one job.
|
||||
* The array keys will be preserved in the resulting array, while the array
|
||||
* values will be replaced with the job results as returned by the
|
||||
* `$handler`. If this array is empty, this method will resolve with an
|
||||
* empty array without processing any jobs.
|
||||
*
|
||||
* The `$handler` parameter must be a valid callable that accepts your job
|
||||
* parameters, invokes the appropriate operation and returns a Promise as a
|
||||
* placeholder for its future result. If the given argument is not a valid
|
||||
* callable, this method will reject with an `InvalidArgumentException`
|
||||
* without processing any jobs.
|
||||
*
|
||||
* ```php
|
||||
* // using a Closure as handler is usually recommended
|
||||
* $promise = Queue::all(10, $jobs, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // accepts any callable, so PHP's array notation is also supported
|
||||
* $promise = Queue::all(10, $jobs, array($browser, 'get'));
|
||||
* ```
|
||||
*
|
||||
* > Keep in mind that returning an array of response messages means that
|
||||
* the whole response body has to be kept in memory.
|
||||
*
|
||||
* @param int $concurrency concurrency soft limit
|
||||
* @param array $jobs
|
||||
* @param callable $handler
|
||||
* @return PromiseInterface Returns a Promise<mixed[]> which resolves with an array of all resolution values
|
||||
* or rejects when any of the operations reject.
|
||||
*/
|
||||
public static function all($concurrency, array $jobs, $handler)
|
||||
{
|
||||
try {
|
||||
// limit number of concurrent operations
|
||||
$q = new self($concurrency, null, $handler);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// reject if $concurrency or $handler is invalid
|
||||
return Promise\reject($e);
|
||||
}
|
||||
|
||||
// try invoking all operations and automatically queue excessive ones
|
||||
$promises = array_map($q, $jobs);
|
||||
|
||||
return new Promise\Promise(function ($resolve, $reject) use ($promises) {
|
||||
Promise\all($promises)->then($resolve, function ($e) use ($promises, $reject) {
|
||||
// cancel all pending promises if a single promise fails
|
||||
foreach (array_reverse($promises) as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// reject with original rejection message
|
||||
$reject($e);
|
||||
});
|
||||
}, function () use ($promises) {
|
||||
// cancel all pending promises on cancellation
|
||||
foreach (array_reverse($promises) as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Concurrently process the given jobs through the given `$handler` and
|
||||
* resolve with first resolution value.
|
||||
*
|
||||
* This is a convenience method which uses the `Queue` internally to
|
||||
* schedule all jobs while limiting concurrency to ensure no more than
|
||||
* `$concurrency` jobs ever run at once. It will return a promise which
|
||||
* resolves with the result of the first job on success and will then try
|
||||
* to `cancel()` all outstanding jobs.
|
||||
*
|
||||
* ```php
|
||||
* $browser = new React\Http\Browser();
|
||||
*
|
||||
* $promise = Queue::any(3, $urls, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
*
|
||||
* $promise->then(function (ResponseInterface $response) {
|
||||
* echo 'First response: ' . $response->getBody() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* If all of the jobs fail, it will reject the resulting promise. Similarly,
|
||||
* calling `cancel()` on the resulting promise will try to cancel all
|
||||
* outstanding jobs. See [promises](#promises) and
|
||||
* [cancellation](#cancellation) for details.
|
||||
*
|
||||
* The `$concurrency` parameter sets a new soft limit for the maximum number
|
||||
* of jobs to handle concurrently. Finding a good concurrency limit depends
|
||||
* on your particular use case. It's common to limit concurrency to a rather
|
||||
* small value, as doing more than a dozen of things at once may easily
|
||||
* overwhelm the receiving side. Using a `1` value will ensure that all jobs
|
||||
* are processed one after another, effectively creating a "waterfall" of
|
||||
* jobs. Using a value less than 1 will reject with an
|
||||
* `InvalidArgumentException` without processing any jobs.
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently
|
||||
* $promise = Queue::any(10, $jobs, $handler);
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // handle each job after another without concurrency (waterfall)
|
||||
* $promise = Queue::any(1, $jobs, $handler);
|
||||
* ```
|
||||
*
|
||||
* The `$jobs` parameter must be an array with all jobs to process. Each
|
||||
* value in this array will be passed to the `$handler` to start one job.
|
||||
* The array keys have no effect, the promise will simply resolve with the
|
||||
* job results of the first successful job as returned by the `$handler`.
|
||||
* If this array is empty, this method will reject without processing any
|
||||
* jobs.
|
||||
*
|
||||
* The `$handler` parameter must be a valid callable that accepts your job
|
||||
* parameters, invokes the appropriate operation and returns a Promise as a
|
||||
* placeholder for its future result. If the given argument is not a valid
|
||||
* callable, this method will reject with an `InvalidArgumentExceptionn`
|
||||
* without processing any jobs.
|
||||
*
|
||||
* ```php
|
||||
* // using a Closure as handler is usually recommended
|
||||
* $promise = Queue::any(10, $jobs, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // accepts any callable, so PHP's array notation is also supported
|
||||
* $promise = Queue::any(10, $jobs, array($browser, 'get'));
|
||||
* ```
|
||||
*
|
||||
* @param int $concurrency concurrency soft limit
|
||||
* @param array $jobs
|
||||
* @param callable $handler
|
||||
* @return PromiseInterface Returns a Promise<mixed> which resolves with a single resolution value
|
||||
* or rejects when all of the operations reject.
|
||||
*/
|
||||
public static function any($concurrency, array $jobs, $handler)
|
||||
{
|
||||
// explicitly reject with empty jobs (https://github.com/reactphp/promise/pull/34)
|
||||
if (!$jobs) {
|
||||
return Promise\reject(new \UnderflowException('No jobs given'));
|
||||
}
|
||||
|
||||
try {
|
||||
// limit number of concurrent operations
|
||||
$q = new self($concurrency, null, $handler);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// reject if $concurrency or $handler is invalid
|
||||
return Promise\reject($e);
|
||||
}
|
||||
|
||||
// try invoking all operations and automatically queue excessive ones
|
||||
$promises = array_map($q, $jobs);
|
||||
|
||||
return new Promise\Promise(function ($resolve, $reject) use ($promises) {
|
||||
Promise\any($promises)->then(function ($result) use ($promises, $resolve) {
|
||||
// cancel all pending promises if a single result is ready
|
||||
foreach (array_reverse($promises) as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// resolve with original resolution value
|
||||
$resolve($result);
|
||||
}, $reject);
|
||||
}, function () use ($promises) {
|
||||
// cancel all pending promises on cancellation
|
||||
foreach (array_reverse($promises) as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new queue object.
|
||||
*
|
||||
* You can create any number of queues, for example when you want to apply
|
||||
* different limits to different kind of operations.
|
||||
*
|
||||
* The `$concurrency` parameter sets a new soft limit for the maximum number
|
||||
* of jobs to handle concurrently. Finding a good concurrency limit depends
|
||||
* on your particular use case. It's common to limit concurrency to a rather
|
||||
* small value, as doing more than a dozen of things at once may easily
|
||||
* overwhelm the receiving side.
|
||||
*
|
||||
* The `$limit` parameter sets a new hard limit on how many jobs may be
|
||||
* outstanding (kept in memory) at once. Depending on your particular use
|
||||
* case, it's usually safe to keep a few hundreds or thousands of jobs in
|
||||
* memory. If you do not want to apply an upper limit, you can pass a `null`
|
||||
* value which is semantically more meaningful than passing a big number.
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently, but keep no more than 1000 in memory
|
||||
* $q = new Queue(10, 1000, $handler);
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently, do not limit queue size
|
||||
* $q = new Queue(10, null, $handler);
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently, reject all further jobs
|
||||
* $q = new Queue(10, 10, $handler);
|
||||
* ```
|
||||
*
|
||||
* The `$handler` parameter must be a valid callable that accepts your job
|
||||
* parameters, invokes the appropriate operation and returns a Promise as a
|
||||
* placeholder for its future result.
|
||||
*
|
||||
* ```php
|
||||
* // using a Closure as handler is usually recommended
|
||||
* $q = new Queue(10, null, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // PHP's array callable as handler is also supported
|
||||
* $q = new Queue(10, null, array($browser, 'get'));
|
||||
* ```
|
||||
*
|
||||
* @param int $concurrency concurrency soft limit
|
||||
* @param int|null $limit queue hard limit or NULL=unlimited
|
||||
* @param callable $handler
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($concurrency, $limit, $handler)
|
||||
{
|
||||
if ($concurrency < 1 || ($limit !== null && ($limit < 1 || $concurrency > $limit))) {
|
||||
throw new \InvalidArgumentException('Invalid limit given');
|
||||
}
|
||||
if (!is_callable($handler)) {
|
||||
throw new \InvalidArgumentException('Invalid handler given');
|
||||
}
|
||||
|
||||
$this->concurrency = $concurrency;
|
||||
$this->limit = $limit;
|
||||
$this->handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Queue instance is invokable, so that invoking `$q(...$args)` will
|
||||
* actually be forwarded as `$handler(...$args)` as given in the
|
||||
* `$handler` argument when concurrency is still below limits.
|
||||
*
|
||||
* Each operation may take some time to complete, but due to its async nature you
|
||||
* can actually start any number of (queued) operations. Once the concurrency limit
|
||||
* is reached, this invocation will simply be queued and this will return a pending
|
||||
* promise which will start the actual operation once another operation is
|
||||
* completed. This means that this is handled entirely transparently and you do not
|
||||
* need to worry about this concurrency limit yourself.
|
||||
*
|
||||
* @return \React\Promise\PromiseInterface
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
// happy path: simply invoke handler if we're below concurrency limit
|
||||
if ($this->pending < $this->concurrency) {
|
||||
++$this->pending;
|
||||
|
||||
// invoke handler and await its resolution before invoking next queued job
|
||||
return $this->await(
|
||||
call_user_func_array($this->handler, func_get_args())
|
||||
);
|
||||
}
|
||||
|
||||
// we're currently above concurrency limit, make sure we do not exceed maximum queue limit
|
||||
if ($this->limit !== null && $this->count() >= $this->limit) {
|
||||
return Promise\reject(new \OverflowException('Maximum queue limit of ' . $this->limit . ' exceeded'));
|
||||
}
|
||||
|
||||
// if we reach this point, then this job will need to be queued
|
||||
// get next queue position
|
||||
$queue =& $this->queue;
|
||||
$queue[] = null;
|
||||
end($queue);
|
||||
$id = key($queue);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use (&$queue, $id, &$deferred) {
|
||||
// forward cancellation to pending operation if it is currently executing
|
||||
if (isset($deferred->pending) && $deferred->pending instanceof CancellablePromiseInterface) {
|
||||
$deferred->pending->cancel();
|
||||
}
|
||||
unset($deferred->pending);
|
||||
|
||||
if (isset($deferred->args)) {
|
||||
// queued promise cancelled before its handler is invoked
|
||||
// remove from queue and reject explicitly
|
||||
unset($queue[$id], $deferred->args);
|
||||
$reject(new \RuntimeException('Cancelled queued job before processing started'));
|
||||
}
|
||||
});
|
||||
|
||||
// queue job to process if number of pending jobs is below concurrency limit again
|
||||
$deferred->args = func_get_args();
|
||||
$queue[$id] = $deferred;
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return $this->pending + count($this->queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function await(PromiseInterface $promise)
|
||||
{
|
||||
$that = $this;
|
||||
|
||||
return $promise->then(function ($result) use ($that) {
|
||||
$that->processQueue();
|
||||
|
||||
return $result;
|
||||
}, function ($error) use ($that) {
|
||||
$that->processQueue();
|
||||
|
||||
return Promise\reject($error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function processQueue()
|
||||
{
|
||||
// skip if we're still above concurrency limit or there's no queued job waiting
|
||||
if (--$this->pending >= $this->concurrency || !$this->queue) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* @var $deferred Deferred */
|
||||
$deferred = reset($this->queue);
|
||||
unset($this->queue[key($this->queue)]);
|
||||
|
||||
// once number of pending jobs is below concurrency limit again:
|
||||
// await this situation, invoke handler and await its resolution before invoking next queued job
|
||||
++$this->pending;
|
||||
|
||||
$promise = call_user_func_array($this->handler, $deferred->args);
|
||||
$deferred->pending = $promise;
|
||||
unset($deferred->args);
|
||||
|
||||
// invoke handler and await its resolution before invoking next queued job
|
||||
$this->await($promise)->then(
|
||||
function ($result) use ($deferred) {
|
||||
unset($deferred->pending);
|
||||
$deferred->resolve($result);
|
||||
},
|
||||
function ($e) use ($deferred) {
|
||||
unset($deferred->pending);
|
||||
$deferred->reject($e);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
19
vendor/clue/redis-protocol/composer.json
vendored
Normal file
19
vendor/clue/redis-protocol/composer.json
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "clue/redis-protocol",
|
||||
"description": "A streaming redis wire protocol parser and serializer implementation in PHP",
|
||||
"keywords": ["streaming", "redis", "protocol", "parser", "serializer"],
|
||||
"homepage": "https://github.com/clue/php-redis-protocol",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Clue\\Redis\\Protocol": "src" }
|
||||
}
|
||||
}
|
51
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php
vendored
Normal file
51
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol;
|
||||
|
||||
use Clue\Redis\Protocol\Parser\ParserInterface;
|
||||
use Clue\Redis\Protocol\Parser\ResponseParser;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
use Clue\Redis\Protocol\Serializer\RecursiveSerializer;
|
||||
use Clue\Redis\Protocol\Parser\RequestParser;
|
||||
|
||||
/**
|
||||
* Provides factory methods used to instantiate the best available protocol implementation
|
||||
*/
|
||||
class Factory
|
||||
{
|
||||
/**
|
||||
* instantiate the best available protocol response parser implementation
|
||||
*
|
||||
* This is the parser every redis client implementation should use in order
|
||||
* to parse incoming response messages from a redis server.
|
||||
*
|
||||
* @return ParserInterface
|
||||
*/
|
||||
public function createResponseParser()
|
||||
{
|
||||
return new ResponseParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* instantiate the best available protocol request parser implementation
|
||||
*
|
||||
* This is most useful for a redis server implementation which needs to
|
||||
* process client requests.
|
||||
*
|
||||
* @return ParserInterface
|
||||
*/
|
||||
public function createRequestParser()
|
||||
{
|
||||
return new RequestParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* instantiate the best available protocol serializer implementation
|
||||
*
|
||||
* @return SerializerInterface
|
||||
*/
|
||||
public function createSerializer()
|
||||
{
|
||||
return new RecursiveSerializer();
|
||||
}
|
||||
}
|
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php
vendored
Normal file
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class BulkReply implements ModelInterface
|
||||
{
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* create bulk reply (string reply)
|
||||
*
|
||||
* @param string|null $data
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
if ($value !== null) {
|
||||
$value = (string)$value;
|
||||
}
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getBulkMessage($this->value);
|
||||
}
|
||||
}
|
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php
vendored
Normal file
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Exception;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
/**
|
||||
*
|
||||
* @link http://redis.io/topics/protocol#status-reply
|
||||
*/
|
||||
class ErrorReply extends Exception implements ModelInterface
|
||||
{
|
||||
/**
|
||||
* create error status reply (single line error message)
|
||||
*
|
||||
* @param string|ErrorReplyException $message
|
||||
* @return string
|
||||
*/
|
||||
public function __construct($message, $code = 0, $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->getMessage();
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getErrorMessage($this->getMessage());
|
||||
}
|
||||
}
|
31
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php
vendored
Normal file
31
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class IntegerReply implements ModelInterface
|
||||
{
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* create integer reply
|
||||
*
|
||||
* @param int $data
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = (int)$value;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getIntegerMessage($this->value);
|
||||
}
|
||||
}
|
23
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php
vendored
Normal file
23
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
interface ModelInterface
|
||||
{
|
||||
/**
|
||||
* Returns value of this model as a native representation for PHP
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValueNative();
|
||||
|
||||
/**
|
||||
* Returns the serialized representation of this protocol message
|
||||
*
|
||||
* @param SerializerInterface $serializer;
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageSerialized(SerializerInterface $serializer);
|
||||
}
|
100
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php
vendored
Normal file
100
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class MultiBulkReply implements ModelInterface
|
||||
{
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* create multi bulk reply (an array of other replies, usually bulk replies)
|
||||
*
|
||||
* @param array|null $data
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $data = null)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
if ($this->data === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
foreach ($this->data as $one) {
|
||||
if ($one instanceof ModelInterface) {
|
||||
$ret []= $one->getValueNative();
|
||||
} else {
|
||||
$ret []= $one;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getMultiBulkMessage($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this model represents a valid unified request protocol message
|
||||
*
|
||||
* The new unified protocol was introduced in Redis 1.2, but it became the
|
||||
* standard way for talking with the Redis server in Redis 2.0. The unified
|
||||
* request protocol is what Redis already uses in replies in order to send
|
||||
* list of items to clients, and is called a Multi Bulk Reply.
|
||||
*
|
||||
* @return boolean
|
||||
* @link http://redis.io/topics/protocol
|
||||
*/
|
||||
public function isRequest()
|
||||
{
|
||||
if (!$this->data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->data as $one) {
|
||||
if (!($one instanceof BulkReply) && !is_string($one)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getRequestModel()
|
||||
{
|
||||
if (!$this->data) {
|
||||
throw new UnexpectedValueException('Null-multi-bulk message can not be represented as a request, must contain string/bulk values');
|
||||
}
|
||||
|
||||
$command = null;
|
||||
$args = array();
|
||||
|
||||
foreach ($this->data as $one) {
|
||||
if ($one instanceof BulkReply) {
|
||||
$one = $one->getValueNative();
|
||||
} elseif (!is_string($one)) {
|
||||
throw new UnexpectedValueException('Message can not be represented as a request, must only contain string/bulk values');
|
||||
}
|
||||
|
||||
if ($command === null) {
|
||||
$command = $one;
|
||||
} else {
|
||||
$args []= $one;
|
||||
}
|
||||
}
|
||||
|
||||
return new Request($command, $args);
|
||||
}
|
||||
}
|
53
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php
vendored
Normal file
53
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\BulkReply;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class Request implements ModelInterface
|
||||
{
|
||||
private $command;
|
||||
private $args;
|
||||
|
||||
public function __construct($command, array $args = array())
|
||||
{
|
||||
$this->command = $command;
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
public function getArgs()
|
||||
{
|
||||
return $this->args;
|
||||
}
|
||||
|
||||
public function getReplyModel()
|
||||
{
|
||||
$models = array(new BulkReply($this->command));
|
||||
foreach ($this->args as $arg) {
|
||||
$models []= new BulkReply($arg);
|
||||
}
|
||||
|
||||
return new MultiBulkReply($models);
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
$ret = $this->args;
|
||||
array_unshift($ret, $this->command);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getRequestMessage($this->command, $this->args);
|
||||
}
|
||||
}
|
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php
vendored
Normal file
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
/**
|
||||
*
|
||||
* @link http://redis.io/topics/protocol#status-reply
|
||||
*/
|
||||
class StatusReply implements ModelInterface
|
||||
{
|
||||
private $message;
|
||||
|
||||
/**
|
||||
* create status reply (single line message)
|
||||
*
|
||||
* @param string|Status $message
|
||||
* @return string
|
||||
*/
|
||||
public function __construct($message)
|
||||
{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getStatusMessage($this->message);
|
||||
}
|
||||
}
|
40
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php
vendored
Normal file
40
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use UnderflowException;
|
||||
|
||||
class MessageBuffer implements ParserInterface
|
||||
{
|
||||
private $parser;
|
||||
private $incomingQueue = array();
|
||||
|
||||
public function __construct(ParserInterface $parser)
|
||||
{
|
||||
$this->parser = $parser;
|
||||
}
|
||||
|
||||
public function popIncomingModel()
|
||||
{
|
||||
if (!$this->incomingQueue) {
|
||||
throw new UnderflowException('Incoming message queue is empty');
|
||||
}
|
||||
return array_shift($this->incomingQueue);
|
||||
}
|
||||
|
||||
public function hasIncomingModel()
|
||||
{
|
||||
return ($this->incomingQueue) ? true : false;
|
||||
}
|
||||
|
||||
public function pushIncoming($data)
|
||||
{
|
||||
$ret = $this->parser->pushIncoming($data);
|
||||
|
||||
foreach ($ret as $one) {
|
||||
$this->incomingQueue []= $one;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
10
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php
vendored
Normal file
10
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use UnexpectedValueException;
|
||||
|
||||
class ParserException extends UnexpectedValueException
|
||||
{
|
||||
|
||||
}
|
28
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php
vendored
Normal file
28
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Parser\ParserException;
|
||||
|
||||
interface ParserInterface
|
||||
{
|
||||
/**
|
||||
* push a chunk of the redis protocol message into the buffer and parse
|
||||
*
|
||||
* You can push any number of bytes of a redis protocol message into the
|
||||
* parser and it will try to parse messages from its data stream. So you can
|
||||
* pass data directly from your socket stream and the parser will return the
|
||||
* right amount of message model objects for you.
|
||||
*
|
||||
* If you pass an incomplete message, expect it to return an empty array. If
|
||||
* your incomplete message is split to across multiple chunks, the parsed
|
||||
* message model will be returned once the parser has sufficient data.
|
||||
*
|
||||
* @param string $dataChunk
|
||||
* @return ModelInterface[] 0+ message models
|
||||
* @throws ParserException if the message can not be parsed
|
||||
* @see self::popIncomingModel()
|
||||
*/
|
||||
public function pushIncoming($dataChunk);
|
||||
}
|
125
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php
vendored
Normal file
125
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use Clue\Redis\Protocol\Parser\ParserException;
|
||||
use Clue\Redis\Protocol\Model\Request;
|
||||
|
||||
class RequestParser implements ParserInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
|
||||
private $incomingBuffer = '';
|
||||
private $incomingOffset = 0;
|
||||
|
||||
public function pushIncoming($dataChunk)
|
||||
{
|
||||
$this->incomingBuffer .= $dataChunk;
|
||||
|
||||
$parsed = array();
|
||||
|
||||
do {
|
||||
$saved = $this->incomingOffset;
|
||||
$message = $this->readRequest();
|
||||
if ($message === null) {
|
||||
// restore previous position for next parsing attempt
|
||||
$this->incomingOffset = $saved;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($message !== false) {
|
||||
$parsed []= $message;
|
||||
}
|
||||
} while($this->incomingBuffer !== '');
|
||||
|
||||
if ($this->incomingOffset !== 0) {
|
||||
$this->incomingBuffer = (string)substr($this->incomingBuffer, $this->incomingOffset);
|
||||
$this->incomingOffset = 0;
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to parse request from incoming buffer
|
||||
*
|
||||
* @throws ParserException if the incoming buffer is invalid
|
||||
* @return Request|null
|
||||
*/
|
||||
private function readRequest()
|
||||
{
|
||||
$crlf = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset);
|
||||
if ($crlf === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// line starts with a multi-bulk header "*"
|
||||
if (isset($this->incomingBuffer[$this->incomingOffset]) && $this->incomingBuffer[$this->incomingOffset] === '*') {
|
||||
$line = substr($this->incomingBuffer, $this->incomingOffset + 1, $crlf - $this->incomingOffset + 1);
|
||||
$this->incomingOffset = $crlf + 2;
|
||||
$count = (int)$line;
|
||||
|
||||
if ($count <= 0) {
|
||||
return false;
|
||||
}
|
||||
$command = null;
|
||||
$args = array();
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
$sub = $this->readBulk();
|
||||
if ($sub === null) {
|
||||
return null;
|
||||
}
|
||||
if ($command === null) {
|
||||
$command = $sub;
|
||||
} else {
|
||||
$args []= $sub;
|
||||
}
|
||||
}
|
||||
return new Request($command, $args);
|
||||
}
|
||||
|
||||
// parse an old inline request instead
|
||||
$line = substr($this->incomingBuffer, $this->incomingOffset, $crlf - $this->incomingOffset);
|
||||
$this->incomingOffset = $crlf + 2;
|
||||
|
||||
$args = preg_split('/ +/', trim($line, ' '));
|
||||
$command = array_shift($args);
|
||||
|
||||
if ($command === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Request($command, $args);
|
||||
}
|
||||
|
||||
private function readBulk()
|
||||
{
|
||||
$crlf = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset);
|
||||
if ($crlf === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// line has to start with a bulk header "$"
|
||||
if (!isset($this->incomingBuffer[$this->incomingOffset]) || $this->incomingBuffer[$this->incomingOffset] !== '$') {
|
||||
throw new ParserException('ERR Protocol error: expected \'$\', got \'' . substr($this->incomingBuffer, $this->incomingOffset, 1) . '\'');
|
||||
}
|
||||
|
||||
$line = substr($this->incomingBuffer, $this->incomingOffset + 1, $crlf - $this->incomingOffset + 1);
|
||||
$this->incomingOffset = $crlf + 2;
|
||||
$size = (int)$line;
|
||||
|
||||
if ($size < 0) {
|
||||
throw new ParserException('ERR Protocol error: invalid bulk length');
|
||||
}
|
||||
|
||||
if (!isset($this->incomingBuffer[$this->incomingOffset + $size + 1])) {
|
||||
// check enough bytes + crlf are buffered
|
||||
return null;
|
||||
}
|
||||
|
||||
$ret = substr($this->incomingBuffer, $this->incomingOffset, $size);
|
||||
$this->incomingOffset += $size + 2;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
151
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php
vendored
Normal file
151
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use Clue\Redis\Protocol\Parser\ParserInterface;
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\BulkReply;
|
||||
use Clue\Redis\Protocol\Model\ErrorReply;
|
||||
use Clue\Redis\Protocol\Model\IntegerReply;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use Clue\Redis\Protocol\Model\StatusReply;
|
||||
use Clue\Redis\Protocol\Parser\ParserException;
|
||||
|
||||
/**
|
||||
* Simple recursive redis wire protocol parser
|
||||
*
|
||||
* Heavily influenced by blocking parser implementation from jpd/redisent.
|
||||
*
|
||||
* @link https://github.com/jdp/redisent
|
||||
* @link http://redis.io/topics/protocol
|
||||
*/
|
||||
class ResponseParser implements ParserInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
|
||||
private $incomingBuffer = '';
|
||||
private $incomingOffset = 0;
|
||||
|
||||
public function pushIncoming($dataChunk)
|
||||
{
|
||||
$this->incomingBuffer .= $dataChunk;
|
||||
|
||||
return $this->tryParsingIncomingMessages();
|
||||
}
|
||||
|
||||
private function tryParsingIncomingMessages()
|
||||
{
|
||||
$messages = array();
|
||||
|
||||
do {
|
||||
$message = $this->readResponse();
|
||||
if ($message === null) {
|
||||
// restore previous position for next parsing attempt
|
||||
$this->incomingOffset = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
$messages []= $message;
|
||||
|
||||
$this->incomingBuffer = (string)substr($this->incomingBuffer, $this->incomingOffset);
|
||||
$this->incomingOffset = 0;
|
||||
} while($this->incomingBuffer !== '');
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
private function readLine()
|
||||
{
|
||||
$pos = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset);
|
||||
|
||||
if ($pos === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ret = (string)substr($this->incomingBuffer, $this->incomingOffset, $pos - $this->incomingOffset);
|
||||
$this->incomingOffset = $pos + 2;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function readLength($len)
|
||||
{
|
||||
$ret = substr($this->incomingBuffer, $this->incomingOffset, $len);
|
||||
if (strlen($ret) !== $len) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->incomingOffset += $len;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to parse response from incoming buffer
|
||||
*
|
||||
* ripped from jdp/redisent, with some minor modifications to read from
|
||||
* the incoming buffer instead of issuing a blocking fread on a stream
|
||||
*
|
||||
* @throws ParserException if the incoming buffer is invalid
|
||||
* @return ModelInterface|null
|
||||
* @link https://github.com/jdp/redisent
|
||||
*/
|
||||
private function readResponse()
|
||||
{
|
||||
/* Parse the response based on the reply identifier */
|
||||
$reply = $this->readLine();
|
||||
if ($reply === null) {
|
||||
return null;
|
||||
}
|
||||
switch (substr($reply, 0, 1)) {
|
||||
/* Error reply */
|
||||
case '-':
|
||||
$response = new ErrorReply(substr($reply, 1));
|
||||
break;
|
||||
/* Inline reply */
|
||||
case '+':
|
||||
$response = new StatusReply(substr($reply, 1));
|
||||
break;
|
||||
/* Bulk reply */
|
||||
case '$':
|
||||
$size = (int)substr($reply, 1);
|
||||
if ($size === -1) {
|
||||
return new BulkReply(null);
|
||||
}
|
||||
$data = $this->readLength($size);
|
||||
if ($data === null) {
|
||||
return null;
|
||||
}
|
||||
if ($this->readLength(2) === null) { /* discard crlf */
|
||||
return null;
|
||||
}
|
||||
$response = new BulkReply($data);
|
||||
break;
|
||||
/* Multi-bulk reply */
|
||||
case '*':
|
||||
$count = (int)substr($reply, 1);
|
||||
if ($count === -1) {
|
||||
return new MultiBulkReply(null);
|
||||
}
|
||||
$response = array();
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$sub = $this->readResponse();
|
||||
if ($sub === null) {
|
||||
return null;
|
||||
}
|
||||
$response []= $sub;
|
||||
}
|
||||
$response = new MultiBulkReply($response);
|
||||
break;
|
||||
/* Integer reply */
|
||||
case ':':
|
||||
$response = new IntegerReply(substr($reply, 1));
|
||||
break;
|
||||
default:
|
||||
throw new ParserException('Invalid message can not be parsed: "' . $reply . '"');
|
||||
break;
|
||||
}
|
||||
/* Party on */
|
||||
return $response;
|
||||
}
|
||||
}
|
111
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php
vendored
Normal file
111
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Serializer;
|
||||
|
||||
use Clue\Redis\Protocol\Model\StatusReply;
|
||||
use InvalidArgumentException;
|
||||
use Exception;
|
||||
use Clue\Redis\Protocol\Model\BulkReply;
|
||||
use Clue\Redis\Protocol\Model\IntegerReply;
|
||||
use Clue\Redis\Protocol\Model\ErrorReply;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\Request;
|
||||
|
||||
class RecursiveSerializer implements SerializerInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
|
||||
public function getRequestMessage($command, array $args = array())
|
||||
{
|
||||
$data = '*' . (count($args) + 1) . "\r\n$" . strlen($command) . "\r\n" . $command . "\r\n";
|
||||
foreach ($args as $arg) {
|
||||
$data .= '$' . strlen($arg) . "\r\n" . $arg . "\r\n";
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function createRequestModel($command, array $args = array())
|
||||
{
|
||||
return new Request($command, $args);
|
||||
}
|
||||
|
||||
public function getReplyMessage($data)
|
||||
{
|
||||
if (is_string($data) || $data === null) {
|
||||
return $this->getBulkMessage($data);
|
||||
} else if (is_int($data) || is_float($data) || is_bool($data)) {
|
||||
return $this->getIntegerMessage($data);
|
||||
} else if ($data instanceof Exception) {
|
||||
return $this->getErrorMessage($data->getMessage());
|
||||
} else if (is_array($data)) {
|
||||
return $this->getMultiBulkMessage($data);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid data type passed for serialization');
|
||||
}
|
||||
}
|
||||
|
||||
public function createReplyModel($data)
|
||||
{
|
||||
if (is_string($data) || $data === null) {
|
||||
return new BulkReply($data);
|
||||
} else if (is_int($data) || is_float($data) || is_bool($data)) {
|
||||
return new IntegerReply($data);
|
||||
} else if ($data instanceof Exception) {
|
||||
return new ErrorReply($data->getMessage());
|
||||
} else if (is_array($data)) {
|
||||
$models = array();
|
||||
foreach ($data as $one) {
|
||||
$models []= $this->createReplyModel($one);
|
||||
}
|
||||
return new MultiBulkReply($models);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid data type passed for serialization');
|
||||
}
|
||||
}
|
||||
|
||||
public function getBulkMessage($data)
|
||||
{
|
||||
if ($data === null) {
|
||||
/* null bulk reply */
|
||||
return '$-1' . self::CRLF;
|
||||
}
|
||||
/* bulk reply */
|
||||
return '$' . strlen($data) . self::CRLF . $data . self::CRLF;
|
||||
}
|
||||
|
||||
public function getErrorMessage($data)
|
||||
{
|
||||
/* error status reply */
|
||||
return '-' . $data . self::CRLF;
|
||||
}
|
||||
|
||||
public function getIntegerMessage($data)
|
||||
{
|
||||
return ':' . (int)$data . self::CRLF;
|
||||
}
|
||||
|
||||
public function getMultiBulkMessage($data)
|
||||
{
|
||||
if ($data === null) {
|
||||
/* null multi bulk reply */
|
||||
return '*-1' . self::CRLF;
|
||||
}
|
||||
/* multi bulk reply */
|
||||
$ret = '*' . count($data) . self::CRLF;
|
||||
foreach ($data as $one) {
|
||||
if ($one instanceof ModelInterface) {
|
||||
$ret .= $one->getMessageSerialized($this);
|
||||
} else {
|
||||
$ret .= $this->getReplyMessage($one);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getStatusMessage($data)
|
||||
{
|
||||
/* status reply */
|
||||
return '+' . $data . self::CRLF;
|
||||
}
|
||||
}
|
83
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php
vendored
Normal file
83
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Serializer;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ErrorReplyException;
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
|
||||
interface SerializerInterface
|
||||
{
|
||||
/**
|
||||
* create a serialized unified request protocol message
|
||||
*
|
||||
* This is the *one* method most redis client libraries will likely want to
|
||||
* use in order to send a serialized message (a request) over the* wire to
|
||||
* your redis server instance.
|
||||
*
|
||||
* This method should be used in favor of constructing a request model and
|
||||
* then serializing it. While its effect might be equivalent, this method
|
||||
* is likely to (i.e. it /could/) provide a faster implementation.
|
||||
*
|
||||
* @param string $command
|
||||
* @param array $args
|
||||
* @return string
|
||||
* @see self::createRequestMessage()
|
||||
*/
|
||||
public function getRequestMessage($command, array $args = array());
|
||||
|
||||
/**
|
||||
* create a unified request protocol message model
|
||||
*
|
||||
* @param string $command
|
||||
* @param array $args
|
||||
* @return MultiBulkReply
|
||||
*/
|
||||
public function createRequestModel($command, array $args = array());
|
||||
|
||||
/**
|
||||
* create a serialized unified protocol reply message
|
||||
*
|
||||
* This is most useful for a redis server implementation which needs to
|
||||
* process client requests and send resulting reply messages.
|
||||
*
|
||||
* This method does its best to guess to right reply type and then returns
|
||||
* a serialized version of the message. It follows the "redis to lua
|
||||
* conversion table" (see link) which means most basic types can be mapped
|
||||
* as is.
|
||||
*
|
||||
* This method should be used in favor of constructing a reply model and
|
||||
* then serializing it. While its effect might be equivalent, this method
|
||||
* is likely to (i.e. it /could/) provide a faster implementation.
|
||||
*
|
||||
* Note however, you may still want to explicitly create a nested reply
|
||||
* model hierarchy if you need more control over the serialized message. For
|
||||
* instance, a null value will always be returned as a Null-Bulk-Reply, so
|
||||
* there's no way to express a Null-Multi-Bulk-Reply, unless you construct
|
||||
* it explicitly.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return string
|
||||
* @see self::createReplyModel()
|
||||
* @link http://redis.io/commands/eval
|
||||
*/
|
||||
public function getReplyMessage($data);
|
||||
|
||||
/**
|
||||
* create response message by determining datatype from given argument
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return ModelInterface
|
||||
*/
|
||||
public function createReplyModel($data);
|
||||
|
||||
public function getBulkMessage($data);
|
||||
|
||||
public function getErrorMessage($data);
|
||||
|
||||
public function getIntegerMessage($data);
|
||||
|
||||
public function getMultiBulkMessage($data);
|
||||
|
||||
public function getStatusMessage($data);
|
||||
}
|
21
vendor/clue/redis-react/LICENSE
vendored
Normal file
21
vendor/clue/redis-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
32
vendor/clue/redis-react/composer.json
vendored
Normal file
32
vendor/clue/redis-react/composer.json
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "clue/redis-react",
|
||||
"description": "Async Redis client implementation, built on top of ReactPHP.",
|
||||
"keywords": ["Redis", "database", "client", "async", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-redis",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"clue/redis-protocol": "0.3.*",
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^2.0 || ^1.1",
|
||||
"react/promise-timer": "^1.8",
|
||||
"react/socket": "^1.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.1",
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Redis\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Redis\\": "tests/" }
|
||||
}
|
||||
}
|
54
vendor/clue/redis-react/src/Client.php
vendored
Normal file
54
vendor/clue/redis-react/src/Client.php
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Redis;
|
||||
|
||||
use Evenement\EventEmitterInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* Simple interface for executing redis commands
|
||||
*
|
||||
* @event error(Exception $error)
|
||||
* @event close()
|
||||
*
|
||||
* @event message($channel, $message)
|
||||
* @event subscribe($channel, $numberOfChannels)
|
||||
* @event unsubscribe($channel, $numberOfChannels)
|
||||
*
|
||||
* @event pmessage($pattern, $channel, $message)
|
||||
* @event psubscribe($channel, $numberOfChannels)
|
||||
* @event punsubscribe($channel, $numberOfChannels)
|
||||
*/
|
||||
interface Client extends EventEmitterInterface
|
||||
{
|
||||
/**
|
||||
* Invoke the given command and return a Promise that will be fulfilled when the request has been replied to
|
||||
*
|
||||
* This is a magic method that will be invoked when calling any redis
|
||||
* command on this instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string[] $args
|
||||
* @return PromiseInterface Promise<mixed,Exception>
|
||||
*/
|
||||
public function __call($name, $args);
|
||||
|
||||
/**
|
||||
* end connection once all pending requests have been replied to
|
||||
*
|
||||
* @return void
|
||||
* @uses self::close() once all replies have been received
|
||||
* @see self::close() for closing the connection immediately
|
||||
*/
|
||||
public function end();
|
||||
|
||||
/**
|
||||
* close connection immediately
|
||||
*
|
||||
* This will emit the "close" event.
|
||||
*
|
||||
* @return void
|
||||
* @see self::end() for closing the connection once the client is idle
|
||||
*/
|
||||
public function close();
|
||||
}
|
191
vendor/clue/redis-react/src/Factory.php
vendored
Normal file
191
vendor/clue/redis-react/src/Factory.php
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Redis;
|
||||
|
||||
use Clue\Redis\Protocol\Factory as ProtocolFactory;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectorInterface;
|
||||
|
||||
class Factory
|
||||
{
|
||||
/** @var LoopInterface */
|
||||
private $loop;
|
||||
|
||||
/** @var ConnectorInterface */
|
||||
private $connector;
|
||||
|
||||
/** @var ProtocolFactory */
|
||||
private $protocol;
|
||||
|
||||
/**
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?ConnectorInterface $connector
|
||||
* @param ?ProtocolFactory $protocol
|
||||
*/
|
||||
public function __construct(LoopInterface $loop = null, ConnectorInterface $connector = null, ProtocolFactory $protocol = null)
|
||||
{
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->connector = $connector ?: new Connector(array(), $this->loop);
|
||||
$this->protocol = $protocol ?: new ProtocolFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Redis client connected to address of given redis instance
|
||||
*
|
||||
* @param string $uri Redis server URI to connect to
|
||||
* @return \React\Promise\PromiseInterface<Client,\Exception> Promise that will
|
||||
* be fulfilled with `Client` on success or rejects with `\Exception` on error.
|
||||
*/
|
||||
public function createClient($uri)
|
||||
{
|
||||
// support `redis+unix://` scheme for Unix domain socket (UDS) paths
|
||||
if (preg_match('/^(redis\+unix:\/\/(?:[^:]*:[^@]*@)?)(.+?)?$/', $uri, $match)) {
|
||||
$parts = parse_url($match[1] . 'localhost/' . $match[2]);
|
||||
} else {
|
||||
if (strpos($uri, '://') === false) {
|
||||
$uri = 'redis://' . $uri;
|
||||
}
|
||||
|
||||
$parts = parse_url($uri);
|
||||
}
|
||||
|
||||
$uri = preg_replace(array('/(:)[^:\/]*(@)/', '/([?&]password=).*?($|&)/'), '$1***$2', $uri);
|
||||
if ($parts === false || !isset($parts['scheme'], $parts['host']) || !in_array($parts['scheme'], array('redis', 'rediss', 'redis+unix'))) {
|
||||
return \React\Promise\reject(new \InvalidArgumentException(
|
||||
'Invalid Redis URI given (EINVAL)',
|
||||
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
|
||||
));
|
||||
}
|
||||
|
||||
$args = array();
|
||||
parse_str(isset($parts['query']) ? $parts['query'] : '', $args);
|
||||
|
||||
$authority = $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 6379);
|
||||
if ($parts['scheme'] === 'rediss') {
|
||||
$authority = 'tls://' . $authority;
|
||||
} elseif ($parts['scheme'] === 'redis+unix') {
|
||||
$authority = 'unix://' . substr($parts['path'], 1);
|
||||
unset($parts['path']);
|
||||
}
|
||||
$connecting = $this->connector->connect($authority);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use ($connecting, $uri) {
|
||||
// connection cancelled, start with rejecting attempt, then clean up
|
||||
$reject(new \RuntimeException(
|
||||
'Connection to ' . $uri . ' cancelled (ECONNABORTED)',
|
||||
defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
|
||||
// either close successful connection or cancel pending connection attempt
|
||||
$connecting->then(function (ConnectionInterface $connection) {
|
||||
$connection->close();
|
||||
});
|
||||
$connecting->cancel();
|
||||
});
|
||||
|
||||
$protocol = $this->protocol;
|
||||
$promise = $connecting->then(function (ConnectionInterface $stream) use ($protocol) {
|
||||
return new StreamingClient($stream, $protocol->createResponseParser(), $protocol->createSerializer());
|
||||
}, function (\Exception $e) use ($uri) {
|
||||
throw new \RuntimeException(
|
||||
'Connection to ' . $uri . ' failed: ' . $e->getMessage(),
|
||||
$e->getCode(),
|
||||
$e
|
||||
);
|
||||
});
|
||||
|
||||
// use `?password=secret` query or `user:secret@host` password form URL
|
||||
$pass = isset($args['password']) ? $args['password'] : (isset($parts['pass']) ? rawurldecode($parts['pass']) : null);
|
||||
if (isset($args['password']) || isset($parts['pass'])) {
|
||||
$pass = isset($args['password']) ? $args['password'] : rawurldecode($parts['pass']);
|
||||
$promise = $promise->then(function (StreamingClient $redis) use ($pass, $uri) {
|
||||
return $redis->auth($pass)->then(
|
||||
function () use ($redis) {
|
||||
return $redis;
|
||||
},
|
||||
function (\Exception $e) use ($redis, $uri) {
|
||||
$redis->close();
|
||||
|
||||
$const = '';
|
||||
$errno = $e->getCode();
|
||||
if ($errno === 0) {
|
||||
$const = ' (EACCES)';
|
||||
$errno = $e->getCode() ?: (defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
|
||||
}
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Connection to ' . $uri . ' failed during AUTH command: ' . $e->getMessage() . $const,
|
||||
$errno,
|
||||
$e
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// use `?db=1` query or `/1` path (skip first slash)
|
||||
if (isset($args['db']) || (isset($parts['path']) && $parts['path'] !== '/')) {
|
||||
$db = isset($args['db']) ? $args['db'] : substr($parts['path'], 1);
|
||||
$promise = $promise->then(function (StreamingClient $redis) use ($db, $uri) {
|
||||
return $redis->select($db)->then(
|
||||
function () use ($redis) {
|
||||
return $redis;
|
||||
},
|
||||
function (\Exception $e) use ($redis, $uri) {
|
||||
$redis->close();
|
||||
|
||||
$const = '';
|
||||
$errno = $e->getCode();
|
||||
if ($errno === 0 && strpos($e->getMessage(), 'NOAUTH ') === 0) {
|
||||
$const = ' (EACCES)';
|
||||
$errno = defined('SOCKET_EACCES') ? SOCKET_EACCES : 13;
|
||||
} elseif ($errno === 0) {
|
||||
$const = ' (ENOENT)';
|
||||
$errno = defined('SOCKET_ENOENT') ? SOCKET_ENOENT : 2;
|
||||
}
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Connection to ' . $uri . ' failed during SELECT command: ' . $e->getMessage() . $const,
|
||||
$errno,
|
||||
$e
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
$promise->then(array($deferred, 'resolve'), array($deferred, 'reject'));
|
||||
|
||||
// use timeout from explicit ?timeout=x parameter or default to PHP's default_socket_timeout (60)
|
||||
$timeout = isset($args['timeout']) ? (float) $args['timeout'] : (int) ini_get("default_socket_timeout");
|
||||
if ($timeout < 0) {
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
return \React\Promise\Timer\timeout($deferred->promise(), $timeout, $this->loop)->then(null, function ($e) use ($uri) {
|
||||
if ($e instanceof TimeoutException) {
|
||||
throw new \RuntimeException(
|
||||
'Connection to ' . $uri . ' timed out after ' . $e->getTimeout() . ' seconds (ETIMEDOUT)',
|
||||
defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110
|
||||
);
|
||||
}
|
||||
throw $e;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Redis client connected to address of given redis instance
|
||||
*
|
||||
* @param string $target
|
||||
* @return Client
|
||||
*/
|
||||
public function createLazyClient($target)
|
||||
{
|
||||
return new LazyClient($target, $this, $this->loop);
|
||||
}
|
||||
}
|
219
vendor/clue/redis-react/src/LazyClient.php
vendored
Normal file
219
vendor/clue/redis-react/src/LazyClient.php
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Redis;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\Util;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class LazyClient extends EventEmitter implements Client
|
||||
{
|
||||
private $target;
|
||||
/** @var Factory */
|
||||
private $factory;
|
||||
private $closed = false;
|
||||
private $promise;
|
||||
|
||||
private $loop;
|
||||
private $idlePeriod = 60.0;
|
||||
private $idleTimer;
|
||||
private $pending = 0;
|
||||
|
||||
private $subscribed = array();
|
||||
private $psubscribed = array();
|
||||
|
||||
/**
|
||||
* @param $target
|
||||
*/
|
||||
public function __construct($target, Factory $factory, LoopInterface $loop)
|
||||
{
|
||||
$args = array();
|
||||
\parse_str((string) \parse_url($target, \PHP_URL_QUERY), $args);
|
||||
if (isset($args['idle'])) {
|
||||
$this->idlePeriod = (float)$args['idle'];
|
||||
}
|
||||
|
||||
$this->target = $target;
|
||||
$this->factory = $factory;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
private function client()
|
||||
{
|
||||
if ($this->promise !== null) {
|
||||
return $this->promise;
|
||||
}
|
||||
|
||||
$self = $this;
|
||||
$pending =& $this->promise;
|
||||
$idleTimer=& $this->idleTimer;
|
||||
$subscribed =& $this->subscribed;
|
||||
$psubscribed =& $this->psubscribed;
|
||||
$loop = $this->loop;
|
||||
return $pending = $this->factory->createClient($this->target)->then(function (Client $redis) use ($self, &$pending, &$idleTimer, &$subscribed, &$psubscribed, $loop) {
|
||||
// connection completed => remember only until closed
|
||||
$redis->on('close', function () use (&$pending, $self, &$subscribed, &$psubscribed, &$idleTimer, $loop) {
|
||||
$pending = null;
|
||||
|
||||
// foward unsubscribe/punsubscribe events when underlying connection closes
|
||||
$n = count($subscribed);
|
||||
foreach ($subscribed as $channel => $_) {
|
||||
$self->emit('unsubscribe', array($channel, --$n));
|
||||
}
|
||||
$n = count($psubscribed);
|
||||
foreach ($psubscribed as $pattern => $_) {
|
||||
$self->emit('punsubscribe', array($pattern, --$n));
|
||||
}
|
||||
$subscribed = array();
|
||||
$psubscribed = array();
|
||||
|
||||
if ($idleTimer !== null) {
|
||||
$loop->cancelTimer($idleTimer);
|
||||
$idleTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
// keep track of all channels and patterns this connection is subscribed to
|
||||
$redis->on('subscribe', function ($channel) use (&$subscribed) {
|
||||
$subscribed[$channel] = true;
|
||||
});
|
||||
$redis->on('psubscribe', function ($pattern) use (&$psubscribed) {
|
||||
$psubscribed[$pattern] = true;
|
||||
});
|
||||
$redis->on('unsubscribe', function ($channel) use (&$subscribed) {
|
||||
unset($subscribed[$channel]);
|
||||
});
|
||||
$redis->on('punsubscribe', function ($pattern) use (&$psubscribed) {
|
||||
unset($psubscribed[$pattern]);
|
||||
});
|
||||
|
||||
Util::forwardEvents(
|
||||
$redis,
|
||||
$self,
|
||||
array(
|
||||
'message',
|
||||
'subscribe',
|
||||
'unsubscribe',
|
||||
'pmessage',
|
||||
'psubscribe',
|
||||
'punsubscribe',
|
||||
)
|
||||
);
|
||||
|
||||
return $redis;
|
||||
}, function (\Exception $e) use (&$pending) {
|
||||
// connection failed => discard connection attempt
|
||||
$pending = null;
|
||||
|
||||
throw $e;
|
||||
});
|
||||
}
|
||||
|
||||
public function __call($name, $args)
|
||||
{
|
||||
if ($this->closed) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'Connection closed (ENOTCONN)',
|
||||
defined('SOCKET_ENOTCONN') ? SOCKET_ENOTCONN : 107
|
||||
));
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
return $this->client()->then(function (Client $redis) use ($name, $args, $that) {
|
||||
$that->awake();
|
||||
return \call_user_func_array(array($redis, $name), $args)->then(
|
||||
function ($result) use ($that) {
|
||||
$that->idle();
|
||||
return $result;
|
||||
},
|
||||
function ($error) use ($that) {
|
||||
$that->idle();
|
||||
throw $error;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
if ($this->promise === null) {
|
||||
$this->close();
|
||||
}
|
||||
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
return $this->client()->then(function (Client $redis) use ($that) {
|
||||
$redis->on('close', function () use ($that) {
|
||||
$that->close();
|
||||
});
|
||||
$redis->end();
|
||||
});
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
// either close active connection or cancel pending connection attempt
|
||||
if ($this->promise !== null) {
|
||||
$this->promise->then(function (Client $redis) {
|
||||
$redis->close();
|
||||
});
|
||||
if ($this->promise !== null) {
|
||||
$this->promise->cancel();
|
||||
$this->promise = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->idleTimer !== null) {
|
||||
$this->loop->cancelTimer($this->idleTimer);
|
||||
$this->idleTimer = null;
|
||||
}
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function awake()
|
||||
{
|
||||
++$this->pending;
|
||||
|
||||
if ($this->idleTimer !== null) {
|
||||
$this->loop->cancelTimer($this->idleTimer);
|
||||
$this->idleTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function idle()
|
||||
{
|
||||
--$this->pending;
|
||||
|
||||
if ($this->pending < 1 && $this->idlePeriod >= 0 && !$this->subscribed && !$this->psubscribed && $this->promise !== null) {
|
||||
$idleTimer =& $this->idleTimer;
|
||||
$promise =& $this->promise;
|
||||
$idleTimer = $this->loop->addTimer($this->idlePeriod, function () use (&$idleTimer, &$promise) {
|
||||
$promise->then(function (Client $redis) {
|
||||
$redis->close();
|
||||
});
|
||||
$promise = null;
|
||||
$idleTimer = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
203
vendor/clue/redis-react/src/StreamingClient.php
vendored
Normal file
203
vendor/clue/redis-react/src/StreamingClient.php
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Redis;
|
||||
|
||||
use Clue\Redis\Protocol\Factory as ProtocolFactory;
|
||||
use Clue\Redis\Protocol\Model\ErrorReply;
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use Clue\Redis\Protocol\Parser\ParserException;
|
||||
use Clue\Redis\Protocol\Parser\ParserInterface;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
use Evenement\EventEmitter;
|
||||
use React\Promise\Deferred;
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class StreamingClient extends EventEmitter implements Client
|
||||
{
|
||||
private $stream;
|
||||
private $parser;
|
||||
private $serializer;
|
||||
private $requests = array();
|
||||
private $ending = false;
|
||||
private $closed = false;
|
||||
|
||||
private $subscribed = 0;
|
||||
private $psubscribed = 0;
|
||||
|
||||
public function __construct(DuplexStreamInterface $stream, ParserInterface $parser = null, SerializerInterface $serializer = null)
|
||||
{
|
||||
if ($parser === null || $serializer === null) {
|
||||
$factory = new ProtocolFactory();
|
||||
if ($parser === null) {
|
||||
$parser = $factory->createResponseParser();
|
||||
}
|
||||
if ($serializer === null) {
|
||||
$serializer = $factory->createSerializer();
|
||||
}
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$stream->on('data', function($chunk) use ($parser, $that) {
|
||||
try {
|
||||
$models = $parser->pushIncoming($chunk);
|
||||
} catch (ParserException $error) {
|
||||
$that->emit('error', array(new \UnexpectedValueException(
|
||||
'Invalid data received: ' . $error->getMessage() . ' (EBADMSG)',
|
||||
defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG : 77,
|
||||
$error
|
||||
)));
|
||||
$that->close();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($models as $data) {
|
||||
try {
|
||||
$that->handleMessage($data);
|
||||
} catch (\UnderflowException $error) {
|
||||
$that->emit('error', array($error));
|
||||
$that->close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$stream->on('close', array($this, 'close'));
|
||||
|
||||
$this->stream = $stream;
|
||||
$this->parser = $parser;
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
public function __call($name, $args)
|
||||
{
|
||||
$request = new Deferred();
|
||||
$promise = $request->promise();
|
||||
|
||||
$name = strtolower($name);
|
||||
|
||||
// special (p)(un)subscribe commands only accept a single parameter and have custom response logic applied
|
||||
static $pubsubs = array('subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe');
|
||||
|
||||
if ($this->ending) {
|
||||
$request->reject(new \RuntimeException(
|
||||
'Connection ' . ($this->closed ? 'closed' : 'closing'). ' (ENOTCONN)',
|
||||
defined('SOCKET_ENOTCONN') ? SOCKET_ENOTCONN : 107
|
||||
));
|
||||
} elseif (count($args) !== 1 && in_array($name, $pubsubs)) {
|
||||
$request->reject(new \InvalidArgumentException(
|
||||
'PubSub commands limited to single argument (EINVAL)',
|
||||
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
|
||||
));
|
||||
} elseif ($name === 'monitor') {
|
||||
$request->reject(new \BadMethodCallException(
|
||||
'MONITOR command explicitly not supported (ENOTSUP)',
|
||||
defined('SOCKET_ENOTSUP') ? SOCKET_ENOTSUP : (defined('SOCKET_EOPNOTSUPP') ? SOCKET_EOPNOTSUPP : 95)
|
||||
));
|
||||
} else {
|
||||
$this->stream->write($this->serializer->getRequestMessage($name, $args));
|
||||
$this->requests []= $request;
|
||||
}
|
||||
|
||||
if (in_array($name, $pubsubs)) {
|
||||
$that = $this;
|
||||
$subscribed =& $this->subscribed;
|
||||
$psubscribed =& $this->psubscribed;
|
||||
|
||||
$promise->then(function ($array) use ($that, &$subscribed, &$psubscribed) {
|
||||
$first = array_shift($array);
|
||||
|
||||
// (p)(un)subscribe messages are to be forwarded
|
||||
$that->emit($first, $array);
|
||||
|
||||
// remember number of (p)subscribe topics
|
||||
if ($first === 'subscribe' || $first === 'unsubscribe') {
|
||||
$subscribed = $array[1];
|
||||
} else {
|
||||
$psubscribed = $array[1];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
public function handleMessage(ModelInterface $message)
|
||||
{
|
||||
if (($this->subscribed !== 0 || $this->psubscribed !== 0) && $message instanceof MultiBulkReply) {
|
||||
$array = $message->getValueNative();
|
||||
$first = array_shift($array);
|
||||
|
||||
// pub/sub messages are to be forwarded and should not be processed as request responses
|
||||
if (in_array($first, array('message', 'pmessage'))) {
|
||||
$this->emit($first, $array);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->requests) {
|
||||
throw new \UnderflowException(
|
||||
'Unexpected reply received, no matching request found (ENOMSG)',
|
||||
defined('SOCKET_ENOMSG') ? SOCKET_ENOMSG : 42
|
||||
);
|
||||
}
|
||||
|
||||
$request = array_shift($this->requests);
|
||||
assert($request instanceof Deferred);
|
||||
|
||||
if ($message instanceof ErrorReply) {
|
||||
$request->reject($message);
|
||||
} else {
|
||||
$request->resolve($message->getValueNative());
|
||||
}
|
||||
|
||||
if ($this->ending && !$this->requests) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
$this->ending = true;
|
||||
|
||||
if (!$this->requests) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ending = true;
|
||||
$this->closed = true;
|
||||
|
||||
$remoteClosed = $this->stream->isReadable() === false && $this->stream->isWritable() === false;
|
||||
$this->stream->close();
|
||||
|
||||
$this->emit('close');
|
||||
|
||||
// reject all remaining requests in the queue
|
||||
while ($this->requests) {
|
||||
$request = array_shift($this->requests);
|
||||
assert($request instanceof Deferred);
|
||||
|
||||
if ($remoteClosed) {
|
||||
$request->reject(new \RuntimeException(
|
||||
'Connection closed by peer (ECONNRESET)',
|
||||
defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104
|
||||
));
|
||||
} else {
|
||||
$request->reject(new \RuntimeException(
|
||||
'Connection closing (ECONNABORTED)',
|
||||
defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
vendor/clue/soap-react/LICENSE
vendored
Normal file
21
vendor/clue/soap-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
27
vendor/clue/soap-react/composer.json
vendored
Normal file
27
vendor/clue/soap-react/composer.json
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "clue/soap-react",
|
||||
"description": "Simple, async SOAP webservice client library, built on top of ReactPHP",
|
||||
"keywords": ["SOAP", "SoapClient", "WebService", "WSDL", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-soap",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Soap\\": "src/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"clue/buzz-react": "^2.5",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
|
||||
"react/promise": "^2.1 || ^1.2",
|
||||
"ext-soap": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.0",
|
||||
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
|
||||
}
|
||||
}
|
323
vendor/clue/soap-react/src/Client.php
vendored
Normal file
323
vendor/clue/soap-react/src/Client.php
vendored
Normal file
@ -0,0 +1,323 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Soap;
|
||||
|
||||
use Clue\React\Buzz\Browser;
|
||||
use Clue\React\Soap\Protocol\ClientDecoder;
|
||||
use Clue\React\Soap\Protocol\ClientEncoder;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* The `Client` class is responsible for communication with the remote SOAP
|
||||
* WebService server.
|
||||
*
|
||||
* It requires a [`Browser`](https://github.com/clue/reactphp-buzz#browser) object
|
||||
* bound to the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
|
||||
* in order to handle async requests, the WSDL file contents and an optional
|
||||
* array of SOAP options:
|
||||
*
|
||||
* ```php
|
||||
* $loop = React\EventLoop\Factory::create();
|
||||
* $browser = new Clue\React\Buzz\Browser($loop);
|
||||
*
|
||||
* $wsdl = '<?xml …';
|
||||
* $options = array();
|
||||
*
|
||||
* $client = new Client($browser, $wsdl, $options);
|
||||
* ```
|
||||
*
|
||||
* If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
|
||||
* proxy servers etc.), you can explicitly pass a custom instance of the
|
||||
* [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
|
||||
* to the [`Browser`](https://github.com/clue/reactphp-buzz#browser) instance:
|
||||
*
|
||||
* ```php
|
||||
* $connector = new \React\Socket\Connector($loop, array(
|
||||
* 'dns' => '127.0.0.1',
|
||||
* 'tcp' => array(
|
||||
* 'bindto' => '192.168.10.1:0'
|
||||
* ),
|
||||
* 'tls' => array(
|
||||
* 'verify_peer' => false,
|
||||
* 'verify_peer_name' => false
|
||||
* )
|
||||
* ));
|
||||
*
|
||||
* $browser = new Browser($loop, $connector);
|
||||
* $client = new Client($browser, $wsdl);
|
||||
* ```
|
||||
*
|
||||
* The `Client` works similar to PHP's `SoapClient` (which it uses under the
|
||||
* hood), but leaves you the responsibility to load the WSDL file. This allows
|
||||
* you to use local WSDL files, WSDL files from a cache or the most common form,
|
||||
* downloading the WSDL file contents from an URL through the `Browser`:
|
||||
*
|
||||
* ```php
|
||||
* $browser = new Browser($loop);
|
||||
*
|
||||
* $browser->get($url)->then(
|
||||
* function (ResponseInterface $response) use ($browser) {
|
||||
* // WSDL file is ready, create client
|
||||
* $client = new Client($browser, (string)$response->getBody());
|
||||
*
|
||||
* // do something…
|
||||
* },
|
||||
* function (Exception $e) {
|
||||
* // an error occured while trying to download the WSDL
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* The `Client` constructor loads the given WSDL file contents into memory and
|
||||
* parses its definition. If the given WSDL file is invalid and can not be
|
||||
* parsed, this will throw a `SoapFault`:
|
||||
*
|
||||
* ```php
|
||||
* try {
|
||||
* $client = new Client($browser, $wsdl);
|
||||
* } catch (SoapFault $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* > Note that if you have `ext-xdebug` loaded, this may halt with a fatal
|
||||
* error instead of throwing a `SoapFault`. It is not recommended to use this
|
||||
* extension in production, so this should only ever affect test environments.
|
||||
*
|
||||
* The `Client` constructor accepts an array of options. All given options will
|
||||
* be passed through to the underlying `SoapClient`. However, not all options
|
||||
* make sense in this async implementation and as such may not have the desired
|
||||
* effect. See also [`SoapClient`](http://php.net/manual/en/soapclient.soapclient.php)
|
||||
* documentation for more details.
|
||||
*
|
||||
* If working in WSDL mode, the `$options` parameter is optional. If working in
|
||||
* non-WSDL mode, the WSDL parameter must be set to `null` and the options
|
||||
* parameter must contain the `location` and `uri` options, where `location` is
|
||||
* the URL of the SOAP server to send the request to, and `uri` is the target
|
||||
* namespace of the SOAP service:
|
||||
*
|
||||
* ```php
|
||||
* $client = new Client($browser, null, array(
|
||||
* 'location' => 'http://example.com',
|
||||
* 'uri' => 'http://ping.example.com',
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* Similarly, if working in WSDL mode, the `location` option can be used to
|
||||
* explicitly overwrite the URL of the SOAP server to send the request to:
|
||||
*
|
||||
* ```php
|
||||
* $client = new Client($browser, $wsdl, array(
|
||||
* 'location' => 'http://example.com'
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* You can use the `soap_version` option to change from the default SOAP 1.1 to
|
||||
* use SOAP 1.2 instead:
|
||||
*
|
||||
* ```php
|
||||
* $client = new Client($browser, $wsdl, array(
|
||||
* 'soap_version' => SOAP_1_2
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* You can use the `classmap` option to map certain WSDL types to PHP classes
|
||||
* like this:
|
||||
*
|
||||
* ```php
|
||||
* $client = new Client($browser, $wsdl, array(
|
||||
* 'classmap' => array(
|
||||
* 'getBankResponseType' => BankResponse::class
|
||||
* )
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* The `proxy_host` option (and family) is not supported by this library. As an
|
||||
* alternative, you can configure the given `$browser` instance to use an
|
||||
* [HTTP proxy server](https://github.com/clue/reactphp-buzz#http-proxy).
|
||||
* If you find any other option is missing or not supported here, PRs are much
|
||||
* appreciated!
|
||||
*
|
||||
* All public methods of the `Client` are considered *advanced usage*.
|
||||
* If you want to call RPC functions, see below for the [`Proxy`](#proxy) class.
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
private $browser;
|
||||
private $encoder;
|
||||
private $decoder;
|
||||
|
||||
/**
|
||||
* Instantiate a new SOAP client for the given WSDL contents.
|
||||
*
|
||||
* @param Browser $browser
|
||||
* @param string|null $wsdlContents
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(Browser $browser, $wsdlContents, array $options = array())
|
||||
{
|
||||
$wsdl = $wsdlContents !== null ? 'data://text/plain;base64,' . base64_encode($wsdlContents) : null;
|
||||
|
||||
// Accept HTTP responses with error status codes as valid responses.
|
||||
// This is done in order to process these error responses through the normal SOAP decoder.
|
||||
// Additionally, we explicitly limit number of redirects to zero because following redirects makes little sense
|
||||
// because it transforms the POST request to a GET one and hence loses the SOAP request body.
|
||||
$browser = $browser->withOptions(array(
|
||||
'obeySuccessCode' => false,
|
||||
'followRedirects' => true,
|
||||
'maxRedirects' => 0
|
||||
));
|
||||
|
||||
$this->browser = $browser;
|
||||
$this->encoder = new ClientEncoder($wsdl, $options);
|
||||
$this->decoder = new ClientDecoder($wsdl, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue the given function to be sent via SOAP and wait for a response from the remote web service.
|
||||
*
|
||||
* ```php
|
||||
* // advanced usage, see Proxy for recommended alternative
|
||||
* $promise = $client->soapCall('ping', array('hello', 42));
|
||||
* ```
|
||||
*
|
||||
* Note: This is considered *advanced usage*, you may want to look into using the [`Proxy`](#proxy) instead.
|
||||
*
|
||||
* ```php
|
||||
* $proxy = new Proxy($client);
|
||||
* $promise = $proxy->ping('hello', 42);
|
||||
* ```
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed[] $args
|
||||
* @return PromiseInterface Returns a Promise<mixed, Exception>
|
||||
*/
|
||||
public function soapCall($name, $args)
|
||||
{
|
||||
try {
|
||||
$request = $this->encoder->encode($name, $args);
|
||||
} catch (\Exception $e) {
|
||||
$deferred = new Deferred();
|
||||
$deferred->reject($e);
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
$decoder = $this->decoder;
|
||||
|
||||
return $this->browser->send($request)->then(
|
||||
function (ResponseInterface $response) use ($decoder, $name) {
|
||||
// HTTP response received => decode results for this function call
|
||||
return $decoder->decode($name, (string)$response->getBody());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of functions defined in the WSDL.
|
||||
*
|
||||
* It returns the equivalent of PHP's
|
||||
* [`SoapClient::__getFunctions()`](http://php.net/manual/en/soapclient.getfunctions.php).
|
||||
* In non-WSDL mode, this method returns `null`.
|
||||
*
|
||||
* @return string[]|null
|
||||
*/
|
||||
public function getFunctions()
|
||||
{
|
||||
return $this->encoder->__getFunctions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of types defined in the WSDL.
|
||||
*
|
||||
* It returns the equivalent of PHP's
|
||||
* [`SoapClient::__getTypes()`](http://php.net/manual/en/soapclient.gettypes.php).
|
||||
* In non-WSDL mode, this method returns `null`.
|
||||
*
|
||||
* @return string[]|null
|
||||
*/
|
||||
public function getTypes()
|
||||
{
|
||||
return $this->encoder->__getTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location (URI) of the given webservice `$function`.
|
||||
*
|
||||
* Note that this is not to be confused with the WSDL file location.
|
||||
* A WSDL file can contain any number of function definitions.
|
||||
* It's very common that all of these functions use the same location definition.
|
||||
* However, technically each function can potentially use a different location.
|
||||
*
|
||||
* The `$function` parameter should be a string with the the SOAP function name.
|
||||
* See also [`getFunctions()`](#getfunctions) for a list of all available functions.
|
||||
*
|
||||
* ```php
|
||||
* assert('http://example.com/soap/service' === $client->getLocation('echo'));
|
||||
* ```
|
||||
*
|
||||
* For easier access, this function also accepts a numeric function index.
|
||||
* It then uses [`getFunctions()`](#getfunctions) internally to get the function
|
||||
* name for the given index.
|
||||
* This is particularly useful for the very common case where all functions use the
|
||||
* same location and accessing the first location is sufficient.
|
||||
*
|
||||
* ```php
|
||||
* assert('http://example.com/soap/service' === $client->getLocation(0));
|
||||
* ```
|
||||
*
|
||||
* When the `location` option has been set in the `Client` constructor
|
||||
* (such as when in non-WSDL mode) or via the `withLocation()` method, this
|
||||
* method returns the value of the given location.
|
||||
*
|
||||
* Passing a `$function` not defined in the WSDL file will throw a `SoapFault`.
|
||||
*
|
||||
* @param string|int $function
|
||||
* @return string
|
||||
* @throws \SoapFault if given function does not exist
|
||||
* @see self::getFunctions()
|
||||
*/
|
||||
public function getLocation($function)
|
||||
{
|
||||
if (is_int($function)) {
|
||||
$functions = $this->getFunctions();
|
||||
if (isset($functions[$function]) && preg_match('/^\w+ (\w+)\(/', $functions[$function], $match)) {
|
||||
$function = $match[1];
|
||||
}
|
||||
}
|
||||
|
||||
// encode request for given $function
|
||||
return (string)$this->encoder->encode($function, array())->getUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new `Client` with the updated location (URI) for all functions.
|
||||
*
|
||||
* Note that this is not to be confused with the WSDL file location.
|
||||
* A WSDL file can contain any number of function definitions.
|
||||
* It's very common that all of these functions use the same location definition.
|
||||
* However, technically each function can potentially use a different location.
|
||||
*
|
||||
* ```php
|
||||
* $client = $client->withLocation('http://example.com/soap');
|
||||
*
|
||||
* assert('http://example.com/soap' === $client->getLocation('echo'));
|
||||
* ```
|
||||
*
|
||||
* As an alternative to this method, you can also set the `location` option
|
||||
* in the `Client` constructor (such as when in non-WSDL mode).
|
||||
*
|
||||
* @param string $location
|
||||
* @return self
|
||||
* @see self::getLocation()
|
||||
*/
|
||||
public function withLocation($location)
|
||||
{
|
||||
$client = clone $this;
|
||||
$client->encoder = clone $this->encoder;
|
||||
$client->encoder->__setLocation($location);
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
53
vendor/clue/soap-react/src/Protocol/ClientDecoder.php
vendored
Normal file
53
vendor/clue/soap-react/src/Protocol/ClientDecoder.php
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Soap\Protocol;
|
||||
|
||||
use \SoapClient;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ClientDecoder extends SoapClient
|
||||
{
|
||||
private $response = null;
|
||||
|
||||
/**
|
||||
* Decodes the SOAP response / return value from the given SOAP envelope (HTTP response body)
|
||||
*
|
||||
* @param string $function
|
||||
* @param string $response
|
||||
* @return mixed
|
||||
* @throws \SoapFault if response indicates a fault (error condition) or is invalid
|
||||
*/
|
||||
public function decode($function, $response)
|
||||
{
|
||||
// Temporarily save response internally for further processing
|
||||
$this->response = $response;
|
||||
|
||||
// Let's pretend we just invoked the given SOAP function.
|
||||
// This won't actually invoke anything (see `__doRequest()`), but this
|
||||
// requires a valid function name to match its definition in the WSDL.
|
||||
// Internally, simply use the injected response to parse its results.
|
||||
$ret = $this->__soapCall($function, array());
|
||||
$this->response = null;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the internal request logic to parse the response
|
||||
*
|
||||
* By overwriting this method, we can skip the actual request sending logic
|
||||
* and still use the internal parsing logic by injecting the response as
|
||||
* the return code in this method. This will implicitly be invoked by the
|
||||
* call to `pseudoCall()` in the above `decode()` method.
|
||||
*
|
||||
* @see SoapClient::__doRequest()
|
||||
*/
|
||||
public function __doRequest($request, $location, $action, $version, $one_way = 0)
|
||||
{
|
||||
// the actual result doesn't actually matter, just return the given result
|
||||
// this will be processed internally and will return the parsed result
|
||||
return $this->response;
|
||||
}
|
||||
}
|
69
vendor/clue/soap-react/src/Protocol/ClientEncoder.php
vendored
Normal file
69
vendor/clue/soap-react/src/Protocol/ClientEncoder.php
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Soap\Protocol;
|
||||
|
||||
use \SoapClient;
|
||||
use RingCentral\Psr7\Request;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ClientEncoder extends SoapClient
|
||||
{
|
||||
private $request = null;
|
||||
|
||||
/**
|
||||
* Encodes the given RPC function name and arguments as a SOAP request
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $args
|
||||
* @return Request
|
||||
* @throws \SoapFault if request is invalid according to WSDL
|
||||
*/
|
||||
public function encode($name, $args)
|
||||
{
|
||||
$this->__soapCall($name, $args);
|
||||
|
||||
$request = $this->request;
|
||||
$this->request = null;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the internal request logic to build the request message
|
||||
*
|
||||
* By overwriting this method, we can skip the actual request sending logic
|
||||
* and still use the internal request serializing logic by accessing the
|
||||
* given `$request` parameter and building our custom request object from
|
||||
* it. We skip/ignore its parsing logic by returing an empty response here.
|
||||
* This will implicitly be invoked by the call to `__soapCall()` in the
|
||||
* above `encode()` method.
|
||||
*
|
||||
* @see SoapClient::__doRequest()
|
||||
*/
|
||||
public function __doRequest($request, $location, $action, $version, $one_way = 0)
|
||||
{
|
||||
$headers = array();
|
||||
if ($version === SOAP_1_1) {
|
||||
$headers = array(
|
||||
'SOAPAction' => $action,
|
||||
'Content-Type' => 'text/xml; charset=utf-8'
|
||||
);
|
||||
} elseif ($version === SOAP_1_2) {
|
||||
$headers = array(
|
||||
'Content-Type' => 'application/soap+xml; charset=utf-8; action=' . $action
|
||||
);
|
||||
}
|
||||
|
||||
$this->request = new Request(
|
||||
'POST',
|
||||
(string)$location,
|
||||
$headers,
|
||||
(string)$request
|
||||
);
|
||||
|
||||
// do not actually block here, just pretend we're done...
|
||||
return '';
|
||||
}
|
||||
}
|
50
vendor/clue/soap-react/src/Proxy.php
vendored
Normal file
50
vendor/clue/soap-react/src/Proxy.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Soap;
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* The `Proxy` class wraps an existing [`Client`](#client) instance in order to ease calling
|
||||
* SOAP functions.
|
||||
*
|
||||
* ```php
|
||||
* $proxy = new Proxy($client);
|
||||
* ```
|
||||
*
|
||||
* Each and every method call to the `Proxy` class will be sent via SOAP.
|
||||
*
|
||||
* ```php
|
||||
* $proxy->myMethod($myArg1, $myArg2)->then(function ($response) {
|
||||
* // result received
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Please refer to your WSDL or its accompanying documentation for details
|
||||
* on which functions and arguments are supported.
|
||||
*
|
||||
* > Note that this class is called "Proxy" because it will forward (proxy) all
|
||||
* method calls to the actual SOAP service via the underlying
|
||||
* [`Client::soapCall()`](#soapcall) method. This is not to be confused with
|
||||
* using a proxy server. See [`Client`](#client) documentation for more
|
||||
* details on how to use an HTTP proxy server.
|
||||
*/
|
||||
final class Proxy
|
||||
{
|
||||
private $client;
|
||||
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed[] $args
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function __call($name, $args)
|
||||
{
|
||||
return $this->client->soapCall($name, $args);
|
||||
}
|
||||
}
|
21
vendor/clue/socket-raw/LICENSE
vendored
Normal file
21
vendor/clue/socket-raw/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
23
vendor/clue/socket-raw/composer.json
vendored
Normal file
23
vendor/clue/socket-raw/composer.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "clue/socket-raw",
|
||||
"description": "Simple and lightweight OOP wrapper for PHP's low-level sockets extension (ext-sockets).",
|
||||
"keywords": ["socket", "stream", "datagram", "dgram", "client", "server", "ipv6", "tcp", "udp", "icmp", "unix", "udg"],
|
||||
"homepage": "https://github.com/clue/socket-raw",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {"Socket\\Raw\\": "src"}
|
||||
},
|
||||
"require": {
|
||||
"ext-sockets": "*",
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
|
||||
}
|
||||
}
|
91
vendor/clue/socket-raw/src/Exception.php
vendored
Normal file
91
vendor/clue/socket-raw/src/Exception.php
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Socket\Raw;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class Exception extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* Create an Exception after a socket operation on the given $resource failed
|
||||
*
|
||||
* @param \Socket|resource $resource
|
||||
* @param string $messagePrefix
|
||||
* @return self
|
||||
* @uses socket_last_error() to get last socket error code
|
||||
* @uses socket_clear_error() to clear socket error code
|
||||
* @uses self::createFromCode() to automatically construct exception with full error message
|
||||
*/
|
||||
public static function createFromSocketResource($resource, $messagePrefix = 'Socket operation failed')
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
try {
|
||||
$code = socket_last_error($resource);
|
||||
} catch (\Error $e) {
|
||||
$code = SOCKET_ENOTSOCK;
|
||||
}
|
||||
} elseif (is_resource($resource)) {
|
||||
$code = socket_last_error($resource);
|
||||
socket_clear_error($resource);
|
||||
} else {
|
||||
// socket already closed, return fixed error code instead of operating on invalid handle
|
||||
$code = SOCKET_ENOTSOCK;
|
||||
}
|
||||
|
||||
return self::createFromCode($code, $messagePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Exception after a global socket operation failed (like socket creation)
|
||||
*
|
||||
* @param string $messagePrefix
|
||||
* @return self
|
||||
* @uses socket_last_error() to get last global error code
|
||||
* @uses socket_clear_error() to clear global error code
|
||||
* @uses self::createFromCode() to automatically construct exception with full error message
|
||||
*/
|
||||
public static function createFromGlobalSocketOperation($messagePrefix = 'Socket operation failed')
|
||||
{
|
||||
$code = socket_last_error();
|
||||
socket_clear_error();
|
||||
|
||||
return self::createFromCode($code, $messagePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Exception for given error $code
|
||||
*
|
||||
* @param int $code
|
||||
* @param string $messagePrefix
|
||||
* @return self
|
||||
* @throws Exception if given $val is boolean false
|
||||
* @uses self::getErrorMessage() to translate error code to error message
|
||||
*/
|
||||
public static function createFromCode($code, $messagePrefix = 'Socket error')
|
||||
{
|
||||
return new self($messagePrefix . ': ' . self::getErrorMessage($code), $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* get error message for given error code
|
||||
*
|
||||
* @param int $code error code
|
||||
* @return string
|
||||
* @uses socket_strerror() to translate error code to error message
|
||||
* @uses get_defined_constants() to check for related error constant
|
||||
*/
|
||||
protected static function getErrorMessage($code)
|
||||
{
|
||||
$string = socket_strerror($code);
|
||||
|
||||
// search constant starting with SOCKET_ for this error code
|
||||
foreach (get_defined_constants() as $key => $value) {
|
||||
if($value === $code && strpos($key, 'SOCKET_') === 0) {
|
||||
$string .= ' (' . $key . ')';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
282
vendor/clue/socket-raw/src/Factory.php
vendored
Normal file
282
vendor/clue/socket-raw/src/Factory.php
vendored
Normal file
@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
namespace Socket\Raw;
|
||||
|
||||
use \InvalidArgumentException;
|
||||
|
||||
class Factory
|
||||
{
|
||||
/**
|
||||
* create client socket connected to given target address
|
||||
*
|
||||
* @param string $address target address to connect to
|
||||
* @param null|float $timeout connection timeout (in seconds), default null = no limit
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws InvalidArgumentException if given address is invalid
|
||||
* @throws Exception on error
|
||||
* @uses self::createFromString()
|
||||
* @uses Socket::connect()
|
||||
* @uses Socket::connectTimeout()
|
||||
*/
|
||||
public function createClient($address, $timeout = null)
|
||||
{
|
||||
$socket = $this->createFromString($address, $scheme);
|
||||
|
||||
try {
|
||||
if ($timeout === null) {
|
||||
$socket->connect($address);
|
||||
} else {
|
||||
// connectTimeout enables non-blocking mode, so turn blocking on again
|
||||
$socket->connectTimeout($address, $timeout);
|
||||
$socket->setBlocking(true);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$socket->close();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* create server socket bound to given address (and start listening for streaming clients to connect to this stream socket)
|
||||
*
|
||||
* @param string $address address to bind socket to
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::createFromString()
|
||||
* @uses Socket::bind()
|
||||
* @uses Socket::listen() only for stream sockets (TCP/UNIX)
|
||||
*/
|
||||
public function createServer($address)
|
||||
{
|
||||
$socket = $this->createFromString($address, $scheme);
|
||||
|
||||
try {
|
||||
$socket->bind($address);
|
||||
|
||||
if ($socket->getType() === SOCK_STREAM) {
|
||||
$socket->listen();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$socket->close();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* create TCP/IPv4 stream socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createTcp4()
|
||||
{
|
||||
return $this->create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
}
|
||||
|
||||
/**
|
||||
* create TCP/IPv6 stream socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createTcp6()
|
||||
{
|
||||
return $this->create(AF_INET6, SOCK_STREAM, SOL_TCP);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UDP/IPv4 datagram socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createUdp4()
|
||||
{
|
||||
return $this->create(AF_INET, SOCK_DGRAM, SOL_UDP);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UDP/IPv6 datagram socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createUdp6()
|
||||
{
|
||||
return $this->create(AF_INET6, SOCK_DGRAM, SOL_UDP);
|
||||
}
|
||||
|
||||
/**
|
||||
* create local UNIX stream socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createUnix()
|
||||
{
|
||||
return $this->create(AF_UNIX, SOCK_STREAM, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* create local UNIX datagram socket (UDG)
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createUdg()
|
||||
{
|
||||
return $this->create(AF_UNIX, SOCK_DGRAM, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* create raw ICMP/IPv4 datagram socket (requires root!)
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createIcmp4()
|
||||
{
|
||||
return $this->create(AF_INET, SOCK_RAW, getprotobyname('icmp'));
|
||||
}
|
||||
|
||||
/**
|
||||
* create raw ICMPv6 (IPv6) datagram socket (requires root!)
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createIcmp6()
|
||||
{
|
||||
return $this->create(AF_INET6, SOCK_RAW, 58 /*getprotobyname('icmp')*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* create low level socket with given arguments
|
||||
*
|
||||
* @param int $domain
|
||||
* @param int $type
|
||||
* @param int $protocol
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception if creating socket fails
|
||||
* @throws \Error PHP 8 only: throws \Error when arguments are invalid
|
||||
* @uses socket_create()
|
||||
*/
|
||||
public function create($domain, $type, $protocol)
|
||||
{
|
||||
$sock = @socket_create($domain, $type, $protocol);
|
||||
if ($sock === false) {
|
||||
throw Exception::createFromGlobalSocketOperation('Unable to create socket');
|
||||
}
|
||||
return new Socket($sock);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a pair of indistinguishable sockets (commonly used in IPC)
|
||||
*
|
||||
* @param int $domain
|
||||
* @param int $type
|
||||
* @param int $protocol
|
||||
* @return \Socket\Raw\Socket[]
|
||||
* @throws Exception if creating pair of sockets fails
|
||||
* @throws \Error PHP 8 only: throws \Error when arguments are invalid
|
||||
* @uses socket_create_pair()
|
||||
*/
|
||||
public function createPair($domain, $type, $protocol)
|
||||
{
|
||||
$ret = @socket_create_pair($domain, $type, $protocol, $pair);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromGlobalSocketOperation('Unable to create pair of sockets');
|
||||
}
|
||||
return array(new Socket($pair[0]), new Socket($pair[1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* create TCP/IPv4 stream socket and listen for new connections
|
||||
*
|
||||
* @param int $port
|
||||
* @param int $backlog
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception if creating listening socket fails
|
||||
* @throws \Error PHP 8 only: throws \Error when arguments are invalid
|
||||
* @uses socket_create_listen()
|
||||
* @see self::createServer() as an alternative to bind to specific IP, IPv6, UDP, UNIX, UGP
|
||||
*/
|
||||
public function createListen($port, $backlog = 128)
|
||||
{
|
||||
$sock = @socket_create_listen($port, $backlog);
|
||||
if ($sock === false) {
|
||||
throw Exception::createFromGlobalSocketOperation('Unable to create listening socket');
|
||||
}
|
||||
return new Socket($sock);
|
||||
}
|
||||
|
||||
/**
|
||||
* create socket for given address
|
||||
*
|
||||
* @param string $address (passed by reference in order to remove scheme, if present)
|
||||
* @param string|null $scheme default scheme to use, defaults to TCP (passed by reference in order to update with actual scheme used)
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws InvalidArgumentException if given address is invalid
|
||||
* @throws Exception in case creating socket failed
|
||||
* @uses self::createTcp4() etc.
|
||||
*/
|
||||
public function createFromString(&$address, &$scheme)
|
||||
{
|
||||
if ($scheme === null) {
|
||||
$scheme = 'tcp';
|
||||
}
|
||||
|
||||
$hasScheme = false;
|
||||
|
||||
$pos = strpos($address, '://');
|
||||
if ($pos !== false) {
|
||||
$scheme = substr($address, 0, $pos);
|
||||
$address = substr($address, $pos + 3);
|
||||
$hasScheme = true;
|
||||
}
|
||||
|
||||
if (strpos($address, ':') !== strrpos($address, ':') && in_array($scheme, array('tcp', 'udp', 'icmp'))) {
|
||||
// TCP/UDP/ICMP address with several colons => must be IPv6
|
||||
$scheme .= '6';
|
||||
}
|
||||
|
||||
if ($scheme === 'tcp') {
|
||||
$socket = $this->createTcp4();
|
||||
} elseif ($scheme === 'udp') {
|
||||
$socket = $this->createUdp4();
|
||||
} elseif ($scheme === 'tcp6') {
|
||||
$socket = $this->createTcp6();
|
||||
} elseif ($scheme === 'udp6') {
|
||||
$socket = $this->createUdp6();
|
||||
} elseif ($scheme === 'unix') {
|
||||
$socket = $this->createUnix();
|
||||
} elseif ($scheme === 'udg') {
|
||||
$socket = $this->createUdg();
|
||||
} elseif ($scheme === 'icmp') {
|
||||
$socket = $this->createIcmp4();
|
||||
} elseif ($scheme === 'icmp6') {
|
||||
$socket = $this->createIcmp6();
|
||||
if ($hasScheme) {
|
||||
// scheme was stripped from address, resulting IPv6 must not
|
||||
// have a port (due to ICMP) and thus must not be enclosed in
|
||||
// square brackets
|
||||
$address = trim($address, '[]');
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid address scheme given');
|
||||
}
|
||||
return $socket;
|
||||
}
|
||||
}
|
562
vendor/clue/socket-raw/src/Socket.php
vendored
Normal file
562
vendor/clue/socket-raw/src/Socket.php
vendored
Normal file
@ -0,0 +1,562 @@
|
||||
<?php
|
||||
|
||||
namespace Socket\Raw;
|
||||
|
||||
/**
|
||||
* Simple and lightweight OOP wrapper for the low-level sockets extension (ext-sockets)
|
||||
*
|
||||
* @author clue
|
||||
* @link https://github.com/clue/php-socket-raw
|
||||
*/
|
||||
class Socket
|
||||
{
|
||||
/**
|
||||
* reference to actual socket resource
|
||||
*
|
||||
* @var \Socket|resource
|
||||
*/
|
||||
private $resource;
|
||||
|
||||
/**
|
||||
* instanciate socket wrapper for given socket resource
|
||||
*
|
||||
* should usually not be called manually, see Factory
|
||||
*
|
||||
* @param \Socket|resource $resource
|
||||
* @see Factory as the preferred (and simplest) way to construct socket instances
|
||||
*/
|
||||
public function __construct($resource)
|
||||
{
|
||||
$this->resource = $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* get actual socket resource
|
||||
*
|
||||
* @return \Socket|resource returns the socket resource (a `Socket` object as of PHP 8)
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
return $this->resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* accept an incomming connection on this listening socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket new connected socket used for communication
|
||||
* @throws Exception on error, if this is not a listening socket or there's no connection pending
|
||||
* @throws \Error PHP 8 only: throws \Error when socket is invalid
|
||||
* @see self::selectRead() to check if this listening socket can accept()
|
||||
* @see Factory::createServer() to create a listening socket
|
||||
* @see self::listen() has to be called first
|
||||
* @uses socket_accept()
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
$resource = @socket_accept($this->resource);
|
||||
if ($resource === false) {
|
||||
throw Exception::createFromGlobalSocketOperation();
|
||||
}
|
||||
return new Socket($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* binds a name/address/path to this socket
|
||||
*
|
||||
* has to be called before issuing connect() or listen()
|
||||
*
|
||||
* @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @uses socket_bind()
|
||||
*/
|
||||
public function bind($address)
|
||||
{
|
||||
$ret = @socket_bind($this->resource, $this->unformatAddress($address, $port), $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* close this socket
|
||||
*
|
||||
* ATTENTION: make sure to NOT re-use this socket instance after closing it!
|
||||
* its socket resource remains closed and most further operations will fail!
|
||||
*
|
||||
* @return self $this (chainable)
|
||||
* @throws \Error PHP 8 only: throws \Error when socket is invalid
|
||||
* @see self::shutdown() should be called before closing socket
|
||||
* @uses socket_close()
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
socket_close($this->resource);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* initiate a connection to given address
|
||||
*
|
||||
* @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @uses socket_connect()
|
||||
*/
|
||||
public function connect($address)
|
||||
{
|
||||
$ret = @socket_connect($this->resource, $this->unformatAddress($address, $port), $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a new connection to given address, wait for up to $timeout seconds
|
||||
*
|
||||
* The given $timeout parameter is an upper bound, a maximum time to wait
|
||||
* for the connection to be either accepted or rejected.
|
||||
*
|
||||
* The resulting socket resource will be set to non-blocking mode,
|
||||
* regardless of its previous state and whether this method succedes or
|
||||
* if it fails. Make sure to reset with `setBlocking(true)` if you want to
|
||||
* continue using blocking calls.
|
||||
*
|
||||
* @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path
|
||||
* @param float $timeout maximum time to wait (in seconds)
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @uses self::setBlocking() to enable non-blocking mode
|
||||
* @uses self::connect() to initiate the connection
|
||||
* @uses self::selectWrite() to wait for the connection to complete
|
||||
* @uses self::assertAlive() to check connection state
|
||||
*/
|
||||
public function connectTimeout($address, $timeout)
|
||||
{
|
||||
$this->setBlocking(false);
|
||||
|
||||
try {
|
||||
// socket is non-blocking, so connect should emit EINPROGRESS
|
||||
$this->connect($address);
|
||||
|
||||
// socket is already connected immediately?
|
||||
return $this;
|
||||
} catch (Exception $e) {
|
||||
// non-blocking connect() should be EINPROGRESS (or EWOULDBLOCK on Windows) => otherwise re-throw
|
||||
if ($e->getCode() !== SOCKET_EINPROGRESS && $e->getCode() !== SOCKET_EWOULDBLOCK) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// connection should be completed (or rejected) within timeout: socket becomes writable on success or error
|
||||
// Windows requires special care because it uses exceptfds for socket errors: https://github.com/reactphp/event-loop/issues/206
|
||||
$r = null;
|
||||
$w = array($this->resource);
|
||||
$e = DIRECTORY_SEPARATOR === '\\' ? $w : null;
|
||||
$ret = @socket_select($r, $w, $e, $timeout === null ? null : (int) $timeout, (int) (($timeout - floor($timeout)) * 1000000));
|
||||
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromGlobalSocketOperation('Failed to select socket for writing');
|
||||
} elseif ($ret === 0) {
|
||||
throw new Exception('Timed out while waiting for connection', SOCKET_ETIMEDOUT);
|
||||
}
|
||||
|
||||
// confirm connection success (or fail if connected has been rejected)
|
||||
$this->assertAlive();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get socket option
|
||||
*
|
||||
* @param int $level
|
||||
* @param int $optname
|
||||
* @return mixed
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @uses socket_get_option()
|
||||
*/
|
||||
public function getOption($level, $optname)
|
||||
{
|
||||
$value = @socket_get_option($this->resource, $level, $optname);
|
||||
if ($value === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get remote side's address/path
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket is invalid
|
||||
* @uses socket_getpeername()
|
||||
*/
|
||||
public function getPeerName()
|
||||
{
|
||||
$ret = @socket_getpeername($this->resource, $address, $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this->formatAddress($address, $port);
|
||||
}
|
||||
|
||||
/**
|
||||
* get local side's address/path
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket is invalid
|
||||
* @uses socket_getsockname()
|
||||
*/
|
||||
public function getSockName()
|
||||
{
|
||||
$ret = @socket_getsockname($this->resource, $address, $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this->formatAddress($address, $port);
|
||||
}
|
||||
|
||||
/**
|
||||
* start listen for incoming connections
|
||||
*
|
||||
* @param int $backlog maximum number of incoming connections to be queued
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @see self::bind() has to be called first to bind name to socket
|
||||
* @uses socket_listen()
|
||||
*/
|
||||
public function listen($backlog = 0)
|
||||
{
|
||||
$ret = @socket_listen($this->resource, $backlog);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* read up to $length bytes from connect()ed / accept()ed socket
|
||||
*
|
||||
* The $type parameter specifies if this should use either binary safe reading
|
||||
* (PHP_BINARY_READ, the default) or stop at CR or LF characters (PHP_NORMAL_READ)
|
||||
*
|
||||
* @param int $length maximum length to read
|
||||
* @param int $type either of PHP_BINARY_READ (the default) or PHP_NORMAL_READ
|
||||
* @return string
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @see self::recv() if you need to pass flags
|
||||
* @uses socket_read()
|
||||
*/
|
||||
public function read($length, $type = PHP_BINARY_READ)
|
||||
{
|
||||
$data = @socket_read($this->resource, $length, $type);
|
||||
if ($data === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* receive up to $length bytes from connect()ed / accept()ed socket
|
||||
*
|
||||
* @param int $length maximum length to read
|
||||
* @param int $flags
|
||||
* @return string
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @see self::read() if you do not need to pass $flags
|
||||
* @see self::recvFrom() if your socket is not connect()ed
|
||||
* @uses socket_recv()
|
||||
*/
|
||||
public function recv($length, $flags)
|
||||
{
|
||||
$ret = @socket_recv($this->resource, $buffer, $length, $flags);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* receive up to $length bytes from socket
|
||||
*
|
||||
* @param int $length maximum length to read
|
||||
* @param int $flags
|
||||
* @param string $remote reference will be filled with remote/peer address/path
|
||||
* @return string
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @see self::recv() if your socket is connect()ed
|
||||
* @uses socket_recvfrom()
|
||||
*/
|
||||
public function recvFrom($length, $flags, &$remote)
|
||||
{
|
||||
$ret = @socket_recvfrom($this->resource, $buffer, $length, $flags, $address, $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
$remote = $this->formatAddress($address, $port);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* check socket to see if a read/recv/revFrom will not block
|
||||
*
|
||||
* @param float|null $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit
|
||||
* @return boolean true = socket ready (read will not block), false = timeout expired, socket is not ready
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @uses socket_select()
|
||||
*/
|
||||
public function selectRead($sec = 0)
|
||||
{
|
||||
$usec = $sec === null ? 0 : (int) (($sec - floor($sec)) * 1000000);
|
||||
$r = array($this->resource);
|
||||
$n = null;
|
||||
$ret = @socket_select($r, $n, $n, $sec === null ? null : (int) $sec, $usec);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromGlobalSocketOperation('Failed to select socket for reading');
|
||||
}
|
||||
return !!$ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* check socket to see if a write/send/sendTo will not block
|
||||
*
|
||||
* @param float|null $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit
|
||||
* @return boolean true = socket ready (write will not block), false = timeout expired, socket is not ready
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @uses socket_select()
|
||||
*/
|
||||
public function selectWrite($sec = 0)
|
||||
{
|
||||
$usec = $sec === null ? 0 : (int) (($sec - floor($sec)) * 1000000);
|
||||
$w = array($this->resource);
|
||||
$n = null;
|
||||
$ret = @socket_select($n, $w, $n, $sec === null ? null : (int) $sec, $usec);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromGlobalSocketOperation('Failed to select socket for writing');
|
||||
}
|
||||
return !!$ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* send given $buffer to connect()ed / accept()ed socket
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param int $flags
|
||||
* @return int number of bytes actually written (make sure to check against given buffer length!)
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @see self::write() if you do not need to pass $flags
|
||||
* @see self::sendTo() if your socket is not connect()ed
|
||||
* @uses socket_send()
|
||||
*/
|
||||
public function send($buffer, $flags)
|
||||
{
|
||||
$ret = @socket_send($this->resource, $buffer, strlen($buffer), $flags);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* send given $buffer to socket
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param int $flags
|
||||
* @param string $remote remote/peer address/path
|
||||
* @return int number of bytes actually written
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @see self::send() if your socket is connect()ed
|
||||
* @uses socket_sendto()
|
||||
*/
|
||||
public function sendTo($buffer, $flags, $remote)
|
||||
{
|
||||
$ret = @socket_sendto($this->resource, $buffer, strlen($buffer), $flags, $this->unformatAddress($remote, $port), $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* enable/disable blocking/nonblocking mode (O_NONBLOCK flag)
|
||||
*
|
||||
* @param boolean $toggle
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @uses socket_set_block()
|
||||
* @uses socket_set_nonblock()
|
||||
*/
|
||||
public function setBlocking($toggle = true)
|
||||
{
|
||||
$ret = $toggle ? @socket_set_block($this->resource) : @socket_set_nonblock($this->resource);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set socket option
|
||||
*
|
||||
* @param int $level
|
||||
* @param int $optname
|
||||
* @param mixed $optval
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @see self::getOption()
|
||||
* @uses socket_set_option()
|
||||
*/
|
||||
public function setOption($level, $optname, $optval)
|
||||
{
|
||||
$ret = @socket_set_option($this->resource, $level, $optname, $optval);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* shuts down socket for receiving, sending or both
|
||||
*
|
||||
* @param int $how 0 = shutdown reading, 1 = shutdown writing, 2 = shutdown reading and writing
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @see self::close()
|
||||
* @uses socket_shutdown()
|
||||
*/
|
||||
public function shutdown($how = 2)
|
||||
{
|
||||
$ret = @socket_shutdown($this->resource, $how);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* write $buffer to connect()ed / accept()ed socket
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return int number of bytes actually written
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
|
||||
* @see self::send() if you need to pass flags
|
||||
* @uses socket_write()
|
||||
*/
|
||||
public function write($buffer)
|
||||
{
|
||||
$ret = @socket_write($this->resource, $buffer);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* get socket type as passed to socket_create()
|
||||
*
|
||||
* @return int usually either SOCK_STREAM or SOCK_DGRAM
|
||||
* @throws Exception on error
|
||||
* @throws \Error PHP 8 only: throws \Error when socket is invalid
|
||||
* @uses self::getOption()
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->getOption(SOL_SOCKET, SO_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* assert that this socket is alive and its error code is 0
|
||||
*
|
||||
* This will fetch and reset the current socket error code from the
|
||||
* socket and options and will throw an Exception along with error
|
||||
* message and code if the code is not 0, i.e. if it does indicate
|
||||
* an error situation.
|
||||
*
|
||||
* Calling this method should not be needed in most cases and is
|
||||
* likely to not throw an Exception. Each socket operation like
|
||||
* connect(), send(), etc. will throw a dedicated Exception in case
|
||||
* of an error anyway.
|
||||
*
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception if error code is not 0
|
||||
* @throws \Error PHP 8 only: throws \Error when socket is invalid
|
||||
* @uses self::getOption() to retrieve and clear current error code
|
||||
* @uses self::getErrorMessage() to translate error code to
|
||||
*/
|
||||
public function assertAlive()
|
||||
{
|
||||
$code = $this->getOption(SOL_SOCKET, SO_ERROR);
|
||||
if ($code !== 0) {
|
||||
throw Exception::createFromCode($code, 'Socket error');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* format given address/host/path and port
|
||||
*
|
||||
* @param string $address
|
||||
* @param int $port
|
||||
* @return string
|
||||
*/
|
||||
protected function formatAddress($address, $port)
|
||||
{
|
||||
if ($port !== 0) {
|
||||
if (strpos($address, ':') !== false) {
|
||||
$address = '[' . $address . ']';
|
||||
}
|
||||
$address .= ':' . $port;
|
||||
}
|
||||
return $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* format given address by splitting it into returned address and port set by reference
|
||||
*
|
||||
* @param string $address
|
||||
* @param int $port
|
||||
* @return string address with port removed
|
||||
*/
|
||||
protected function unformatAddress($address, &$port)
|
||||
{
|
||||
// [::1]:2 => ::1 2
|
||||
// test:2 => test 2
|
||||
// ::1 => ::1
|
||||
// test => test
|
||||
|
||||
$colon = strrpos($address, ':');
|
||||
|
||||
// there is a colon and this is the only colon or there's a closing IPv6 bracket right before it
|
||||
if ($colon !== false && (strpos($address, ':') === $colon || strpos($address, ']') === ($colon - 1))) {
|
||||
$port = (int)substr($address, $colon + 1);
|
||||
$address = substr($address, 0, $colon);
|
||||
|
||||
// remove IPv6 square brackets
|
||||
if (substr($address, 0, 1) === '[') {
|
||||
$address = substr($address, 1, -1);
|
||||
}
|
||||
}
|
||||
return $address;
|
||||
}
|
||||
}
|
21
vendor/clue/socks-react/LICENSE
vendored
Normal file
21
vendor/clue/socks-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
31
vendor/clue/socks-react/composer.json
vendored
Normal file
31
vendor/clue/socks-react/composer.json
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "clue/socks-react",
|
||||
"description": "Async SOCKS proxy connector client and server implementation, tunnel any TCP/IP-based protocol through a SOCKS5 or SOCKS4(a) proxy server, built on top of ReactPHP.",
|
||||
"keywords": ["socks client", "socks server", "socks5", "socks4a", "proxy server", "tcp tunnel", "async", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-socks",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/promise": "^2.1 || ^1.2",
|
||||
"react/socket": "^1.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.1",
|
||||
"clue/connection-manager-extra": "^1.0 || ^0.7",
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/http": "^1.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Socks\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Socks\\": "tests/" }
|
||||
}
|
||||
}
|
452
vendor/clue/socks-react/src/Client.php
vendored
Normal file
452
vendor/clue/socks-react/src/Client.php
vendored
Normal file
@ -0,0 +1,452 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\FixedUriConnector;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class Client implements ConnectorInterface
|
||||
{
|
||||
/**
|
||||
* @var ConnectorInterface
|
||||
*/
|
||||
private $connector;
|
||||
|
||||
private $socksUri;
|
||||
|
||||
private $protocolVersion = 5;
|
||||
|
||||
private $auth = null;
|
||||
|
||||
/**
|
||||
* @param string $socksUri
|
||||
* @param ?ConnectorInterface $connector
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct($socksUri, ConnectorInterface $connector = null)
|
||||
{
|
||||
// support `sockss://` scheme for SOCKS over TLS
|
||||
// support `socks+unix://` scheme for Unix domain socket (UDS) paths
|
||||
if (preg_match('/^(socks(?:5|4)?)(s|\+unix):\/\/(.*?@)?(.+?)$/', $socksUri, $match)) {
|
||||
// rewrite URI to parse SOCKS scheme, authentication and dummy host
|
||||
$socksUri = $match[1] . '://' . $match[3] . 'localhost';
|
||||
|
||||
// connector uses appropriate transport scheme and explicit host given
|
||||
$connector = new FixedUriConnector(
|
||||
($match[2] === 's' ? 'tls://' : 'unix://') . $match[4],
|
||||
$connector ?: new Connector()
|
||||
);
|
||||
}
|
||||
|
||||
// assume default scheme if none is given
|
||||
if (strpos($socksUri, '://') === false) {
|
||||
$socksUri = 'socks://' . $socksUri;
|
||||
}
|
||||
|
||||
// parse URI into individual parts
|
||||
$parts = parse_url($socksUri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'])) {
|
||||
throw new InvalidArgumentException('Invalid SOCKS server URI "' . $socksUri . '"');
|
||||
}
|
||||
|
||||
// assume default port
|
||||
if (!isset($parts['port'])) {
|
||||
$parts['port'] = 1080;
|
||||
}
|
||||
|
||||
// user or password in URI => SOCKS5 authentication
|
||||
if (isset($parts['user']) || isset($parts['pass'])) {
|
||||
if ($parts['scheme'] !== 'socks' && $parts['scheme'] !== 'socks5') {
|
||||
// fail if any other protocol version given explicitly
|
||||
throw new InvalidArgumentException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
|
||||
}
|
||||
$parts += array('user' => '', 'pass' => '');
|
||||
$this->setAuth(rawurldecode($parts['user']), rawurldecode($parts['pass']));
|
||||
}
|
||||
|
||||
// check for valid protocol version from URI scheme
|
||||
$this->setProtocolVersionFromScheme($parts['scheme']);
|
||||
|
||||
$this->socksUri = $parts['host'] . ':' . $parts['port'];
|
||||
$this->connector = $connector ?: new Connector();
|
||||
}
|
||||
|
||||
private function setProtocolVersionFromScheme($scheme)
|
||||
{
|
||||
if ($scheme === 'socks' || $scheme === 'socks5') {
|
||||
$this->protocolVersion = 5;
|
||||
} elseif ($scheme === 'socks4') {
|
||||
$this->protocolVersion = 4;
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid protocol version given "' . $scheme . '://"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set login data for username/password authentication method (RFC1929)
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @link http://tools.ietf.org/html/rfc1929
|
||||
*/
|
||||
private function setAuth($username, $password)
|
||||
{
|
||||
if (strlen($username) > 255 || strlen($password) > 255) {
|
||||
throw new InvalidArgumentException('Both username and password MUST NOT exceed a length of 255 bytes each');
|
||||
}
|
||||
$this->auth = pack('C2', 0x01, strlen($username)) . $username . pack('C', strlen($password)) . $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish a TCP/IP connection to the given target URI through the SOCKS server
|
||||
*
|
||||
* Many higher-level networking protocols build on top of TCP. It you're dealing
|
||||
* with one such client implementation, it probably uses/accepts an instance
|
||||
* implementing ReactPHP's `ConnectorInterface` (and usually its default `Connector`
|
||||
* instance). In this case you can also pass this `Connector` instance instead
|
||||
* to make this client implementation SOCKS-aware. That's it.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return PromiseInterface Promise<ConnectionInterface,Exception>
|
||||
*/
|
||||
public function connect($uri)
|
||||
{
|
||||
if (strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
}
|
||||
|
||||
$parts = parse_url($uri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
|
||||
}
|
||||
|
||||
$host = trim($parts['host'], '[]');
|
||||
$port = $parts['port'];
|
||||
|
||||
if (strlen($host) > 255 || $port > 65535 || $port < 0 || (string)$port !== (string)(int)$port) {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid target specified'));
|
||||
}
|
||||
|
||||
// construct URI to SOCKS server to connect to
|
||||
$socksUri = $this->socksUri;
|
||||
|
||||
// append path from URI if given
|
||||
if (isset($parts['path'])) {
|
||||
$socksUri .= $parts['path'];
|
||||
}
|
||||
|
||||
// parse query args
|
||||
$args = array();
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $args);
|
||||
}
|
||||
|
||||
// append hostname from URI to query string unless explicitly given
|
||||
if (!isset($args['hostname'])) {
|
||||
$args['hostname'] = $host;
|
||||
}
|
||||
|
||||
// append query string
|
||||
$socksUri .= '?' . http_build_query($args, '', '&');
|
||||
|
||||
// append fragment from URI if given
|
||||
if (isset($parts['fragment'])) {
|
||||
$socksUri .= '#' . $parts['fragment'];
|
||||
}
|
||||
|
||||
// start TCP/IP connection to SOCKS server
|
||||
$connecting = $this->connector->connect($socksUri);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use ($uri, $connecting) {
|
||||
$reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' cancelled while waiting for proxy (ECONNABORTED)',
|
||||
defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
|
||||
// either close active connection or cancel pending connection attempt
|
||||
$connecting->then(function (ConnectionInterface $stream) {
|
||||
$stream->close();
|
||||
});
|
||||
$connecting->cancel();
|
||||
});
|
||||
|
||||
// handle SOCKS protocol once connection is ready
|
||||
// resolve plain connection once SOCKS protocol is completed
|
||||
$that = $this;
|
||||
$connecting->then(
|
||||
function (ConnectionInterface $stream) use ($that, $host, $port, $deferred, $uri) {
|
||||
$that->handleConnectedSocks($stream, $host, $port, $deferred, $uri);
|
||||
},
|
||||
function (Exception $e) use ($uri, $deferred) {
|
||||
$deferred->reject($e = new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy failed (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111,
|
||||
$e
|
||||
));
|
||||
|
||||
// avoid garbage references by replacing all closures in call stack.
|
||||
// what a lovely piece of code!
|
||||
$r = new \ReflectionProperty('Exception', 'trace');
|
||||
$r->setAccessible(true);
|
||||
$trace = $r->getValue($e);
|
||||
|
||||
// Exception trace arguments are not available on some PHP 7.4 installs
|
||||
// @codeCoverageIgnoreStart
|
||||
foreach ($trace as &$one) {
|
||||
if (isset($one['args'])) {
|
||||
foreach ($one['args'] as &$arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$arg = 'Object(' . \get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$r->setValue($e, $trace);
|
||||
}
|
||||
);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper used to handle the communication with the SOCKS server
|
||||
*
|
||||
* @param ConnectionInterface $stream
|
||||
* @param string $host
|
||||
* @param int $port
|
||||
* @param Deferred $deferred
|
||||
* @param string $uri
|
||||
* @return void
|
||||
* @internal
|
||||
*/
|
||||
public function handleConnectedSocks(ConnectionInterface $stream, $host, $port, Deferred $deferred, $uri)
|
||||
{
|
||||
$reader = new StreamReader();
|
||||
$stream->on('data', array($reader, 'write'));
|
||||
|
||||
$stream->on('error', $onError = function (Exception $e) use ($deferred, $uri) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy caused a stream error (EIO)',
|
||||
defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e)
|
||||
);
|
||||
});
|
||||
|
||||
$stream->on('close', $onClose = function () use ($deferred, $uri) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy was lost while waiting for response from proxy (ECONNRESET)',
|
||||
defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104)
|
||||
);
|
||||
});
|
||||
|
||||
if ($this->protocolVersion === 5) {
|
||||
$promise = $this->handleSocks5($stream, $host, $port, $reader, $uri);
|
||||
} else {
|
||||
$promise = $this->handleSocks4($stream, $host, $port, $reader, $uri);
|
||||
}
|
||||
|
||||
$promise->then(function () use ($deferred, $stream, $reader, $onError, $onClose) {
|
||||
$stream->removeListener('data', array($reader, 'write'));
|
||||
$stream->removeListener('error', $onError);
|
||||
$stream->removeListener('close', $onClose);
|
||||
|
||||
$deferred->resolve($stream);
|
||||
}, function (Exception $error) use ($deferred, $stream, $uri) {
|
||||
// pass custom RuntimeException through as-is, otherwise wrap in protocol error
|
||||
if (!$error instanceof RuntimeException) {
|
||||
$error = new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy returned invalid response (EBADMSG)',
|
||||
defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71,
|
||||
$error
|
||||
);
|
||||
}
|
||||
|
||||
$deferred->reject($error);
|
||||
$stream->close();
|
||||
});
|
||||
}
|
||||
|
||||
private function handleSocks4(ConnectionInterface $stream, $host, $port, StreamReader $reader, $uri)
|
||||
{
|
||||
// do not resolve hostname. only try to convert to IP
|
||||
$ip = ip2long($host);
|
||||
|
||||
// send IP or (0.0.0.1) if invalid
|
||||
$data = pack('C2nNC', 0x04, 0x01, $port, $ip === false ? 1 : $ip, 0x00);
|
||||
|
||||
if ($ip === false) {
|
||||
// host is not a valid IP => send along hostname (SOCKS4a)
|
||||
$data .= $host . pack('C', 0x00);
|
||||
}
|
||||
|
||||
$stream->write($data);
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'null' => 'C',
|
||||
'status' => 'C',
|
||||
'port' => 'n',
|
||||
'ip' => 'N'
|
||||
))->then(function ($data) use ($uri) {
|
||||
if ($data['null'] !== 0x00) {
|
||||
throw new Exception('Invalid SOCKS response');
|
||||
}
|
||||
if ($data['status'] !== 0x5a) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy refused connection with error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamReader $reader, $uri)
|
||||
{
|
||||
// protocol version 5
|
||||
$data = pack('C', 0x05);
|
||||
|
||||
$auth = $this->auth;
|
||||
if ($auth === null) {
|
||||
// one method, no authentication
|
||||
$data .= pack('C2', 0x01, 0x00);
|
||||
} else {
|
||||
// two methods, username/password and no authentication
|
||||
$data .= pack('C3', 0x02, 0x02, 0x00);
|
||||
}
|
||||
$stream->write($data);
|
||||
|
||||
$that = $this;
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'method' => 'C'
|
||||
))->then(function ($data) use ($auth, $stream, $reader, $uri) {
|
||||
if ($data['version'] !== 0x05) {
|
||||
throw new Exception('Version/Protocol mismatch');
|
||||
}
|
||||
|
||||
if ($data['method'] === 0x02 && $auth !== null) {
|
||||
// username/password authentication requested and provided
|
||||
$stream->write($auth);
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'status' => 'C'
|
||||
))->then(function ($data) use ($uri) {
|
||||
if ($data['version'] !== 0x01 || $data['status'] !== 0x00) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy denied access with given authentication details (EACCES)',
|
||||
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
|
||||
);
|
||||
}
|
||||
});
|
||||
} else if ($data['method'] !== 0x00) {
|
||||
// any other method than "no authentication"
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy denied access due to unsupported authentication method (EACCES)',
|
||||
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
|
||||
);
|
||||
}
|
||||
})->then(function () use ($stream, $reader, $host, $port) {
|
||||
// do not resolve hostname. only try to convert to (binary/packed) IP
|
||||
$ip = @inet_pton($host);
|
||||
|
||||
$data = pack('C3', 0x05, 0x01, 0x00);
|
||||
if ($ip === false) {
|
||||
// not an IP, send as hostname
|
||||
$data .= pack('C2', 0x03, strlen($host)) . $host;
|
||||
} else {
|
||||
// send as IPv4 / IPv6
|
||||
$data .= pack('C', (strpos($host, ':') === false) ? 0x01 : 0x04) . $ip;
|
||||
}
|
||||
$data .= pack('n', $port);
|
||||
|
||||
$stream->write($data);
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'status' => 'C',
|
||||
'null' => 'C',
|
||||
'type' => 'C'
|
||||
));
|
||||
})->then(function ($data) use ($reader, $uri) {
|
||||
if ($data['version'] !== 0x05 || $data['null'] !== 0x00) {
|
||||
throw new Exception('Invalid SOCKS response');
|
||||
}
|
||||
if ($data['status'] !== 0x00) {
|
||||
// map limited list of SOCKS error codes to common socket error conditions
|
||||
// @link https://tools.ietf.org/html/rfc1928#section-6
|
||||
if ($data['status'] === Server::ERROR_GENERAL) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy refused connection with general server failure (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_NOT_ALLOWED_BY_RULESET) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy denied access due to ruleset (EACCES)',
|
||||
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_NETWORK_UNREACHABLE) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy reported network unreachable (ENETUNREACH)',
|
||||
defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_HOST_UNREACHABLE) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy reported host unreachable (EHOSTUNREACH)',
|
||||
defined('SOCKET_EHOSTUNREACH') ? SOCKET_EHOSTUNREACH : 113
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_CONNECTION_REFUSED) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy reported connection refused (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_TTL) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy reported TTL/timeout expired (ETIMEDOUT)',
|
||||
defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_COMMAND_UNSUPPORTED) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy does not support the CONNECT command (EPROTO)',
|
||||
defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_ADDRESS_UNSUPPORTED) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy does not support this address type (EPROTO)',
|
||||
defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71
|
||||
);
|
||||
}
|
||||
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy server refused connection with unknown error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
|
||||
);
|
||||
}
|
||||
if ($data['type'] === 0x01) {
|
||||
// IPv4 address => skip IP and port
|
||||
return $reader->readLength(6);
|
||||
} elseif ($data['type'] === 0x03) {
|
||||
// domain name => read domain name length
|
||||
return $reader->readBinary(array(
|
||||
'length' => 'C'
|
||||
))->then(function ($data) use ($reader) {
|
||||
// skip domain name and port
|
||||
return $reader->readLength($data['length'] + 2);
|
||||
});
|
||||
} elseif ($data['type'] === 0x04) {
|
||||
// IPv6 address => skip IP and port
|
||||
return $reader->readLength(18);
|
||||
} else {
|
||||
throw new Exception('Invalid SOCKS reponse: Invalid address type');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
399
vendor/clue/socks-react/src/Server.php
vendored
Normal file
399
vendor/clue/socks-react/src/Server.php
vendored
Normal file
@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use React\Socket\ServerInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use \UnexpectedValueException;
|
||||
use \InvalidArgumentException;
|
||||
use \Exception;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
|
||||
final class Server
|
||||
{
|
||||
// the following error codes are only used for SOCKS5 only
|
||||
/** @internal */
|
||||
const ERROR_GENERAL = 0x01;
|
||||
/** @internal */
|
||||
const ERROR_NOT_ALLOWED_BY_RULESET = 0x02;
|
||||
/** @internal */
|
||||
const ERROR_NETWORK_UNREACHABLE = 0x03;
|
||||
/** @internal */
|
||||
const ERROR_HOST_UNREACHABLE = 0x04;
|
||||
/** @internal */
|
||||
const ERROR_CONNECTION_REFUSED = 0x05;
|
||||
/** @internal */
|
||||
const ERROR_TTL = 0x06;
|
||||
/** @internal */
|
||||
const ERROR_COMMAND_UNSUPPORTED = 0x07;
|
||||
/** @internal */
|
||||
const ERROR_ADDRESS_UNSUPPORTED = 0x08;
|
||||
|
||||
/** @var LoopInterface */
|
||||
private $loop;
|
||||
|
||||
/** @var ConnectorInterface */
|
||||
private $connector;
|
||||
|
||||
/**
|
||||
* @var null|callable
|
||||
*/
|
||||
private $auth;
|
||||
|
||||
/**
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?ConnectorInterface $connector
|
||||
* @param null|array|callable $auth
|
||||
*/
|
||||
public function __construct(LoopInterface $loop = null, ConnectorInterface $connector = null, $auth = null)
|
||||
{
|
||||
if (\is_array($auth)) {
|
||||
// wrap authentication array in authentication callback
|
||||
$this->auth = function ($username, $password) use ($auth) {
|
||||
return \React\Promise\resolve(
|
||||
isset($auth[$username]) && (string)$auth[$username] === $password
|
||||
);
|
||||
};
|
||||
} elseif (\is_callable($auth)) {
|
||||
// wrap authentication callback in order to cast its return value to a promise
|
||||
$this->auth = function($username, $password, $remote) use ($auth) {
|
||||
return \React\Promise\resolve(
|
||||
\call_user_func($auth, $username, $password, $remote)
|
||||
);
|
||||
};
|
||||
} elseif ($auth !== null) {
|
||||
throw new \InvalidArgumentException('Invalid authenticator given');
|
||||
}
|
||||
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->connector = $connector ?: new Connector(array(), $this->loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerInterface $socket
|
||||
* @return void
|
||||
*/
|
||||
public function listen(ServerInterface $socket)
|
||||
{
|
||||
$that = $this;
|
||||
$socket->on('connection', function ($connection) use ($that) {
|
||||
$that->onConnection($connection);
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function onConnection(ConnectionInterface $connection)
|
||||
{
|
||||
$that = $this;
|
||||
$handling = $this->handleSocks($connection)->then(null, function () use ($connection, $that) {
|
||||
// SOCKS failed => close connection
|
||||
$that->endConnection($connection);
|
||||
});
|
||||
|
||||
$connection->on('close', function () use ($handling) {
|
||||
$handling->cancel();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* [internal] gracefully shutdown connection by flushing all remaining data and closing stream
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function endConnection(ConnectionInterface $stream)
|
||||
{
|
||||
$tid = true;
|
||||
$loop = $this->loop;
|
||||
|
||||
// cancel below timer in case connection is closed in time
|
||||
$stream->once('close', function () use (&$tid, $loop) {
|
||||
// close event called before the timer was set up, so everything is okay
|
||||
if ($tid === true) {
|
||||
// make sure to not start a useless timer
|
||||
$tid = false;
|
||||
} else {
|
||||
$loop->cancelTimer($tid);
|
||||
}
|
||||
});
|
||||
|
||||
// shut down connection by pausing input data, flushing outgoing buffer and then exit
|
||||
$stream->pause();
|
||||
$stream->end();
|
||||
|
||||
// check if connection is not already closed
|
||||
if ($tid === true) {
|
||||
// fall back to forcefully close connection in 3 seconds if buffer can not be flushed
|
||||
$tid = $loop->addTimer(3.0, array($stream,'close'));
|
||||
}
|
||||
}
|
||||
|
||||
private function handleSocks(ConnectionInterface $stream)
|
||||
{
|
||||
$reader = new StreamReader();
|
||||
$stream->on('data', array($reader, 'write'));
|
||||
|
||||
$that = $this;
|
||||
$auth = $this->auth;
|
||||
|
||||
return $reader->readByte()->then(function ($version) use ($stream, $that, $auth, $reader){
|
||||
if ($version === 0x04) {
|
||||
if ($auth !== null) {
|
||||
throw new UnexpectedValueException('SOCKS4 not allowed because authentication is required');
|
||||
}
|
||||
return $that->handleSocks4($stream, $reader);
|
||||
} else if ($version === 0x05) {
|
||||
return $that->handleSocks5($stream, $auth, $reader);
|
||||
}
|
||||
throw new UnexpectedValueException('Unexpected/unknown version number');
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleSocks4(ConnectionInterface $stream, StreamReader $reader)
|
||||
{
|
||||
$remote = $stream->getRemoteAddress();
|
||||
if ($remote !== null) {
|
||||
// remove transport scheme and prefix socks4:// instead
|
||||
$secure = strpos($remote, 'tls://') === 0;
|
||||
if (($pos = strpos($remote, '://')) !== false) {
|
||||
$remote = substr($remote, $pos + 3);
|
||||
}
|
||||
$remote = 'socks4' . ($secure ? 's' : '') . '://' . $remote;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
return $reader->readByteAssert(0x01)->then(function () use ($reader) {
|
||||
return $reader->readBinary(array(
|
||||
'port' => 'n',
|
||||
'ipLong' => 'N',
|
||||
'null' => 'C'
|
||||
));
|
||||
})->then(function ($data) use ($reader, $remote) {
|
||||
if ($data['null'] !== 0x00) {
|
||||
throw new Exception('Not a null byte');
|
||||
}
|
||||
if ($data['ipLong'] === 0) {
|
||||
throw new Exception('Invalid IP');
|
||||
}
|
||||
if ($data['port'] === 0) {
|
||||
throw new Exception('Invalid port');
|
||||
}
|
||||
if ($data['ipLong'] < 256) {
|
||||
// invalid IP => probably a SOCKS4a request which appends the hostname
|
||||
return $reader->readStringNull()->then(function ($string) use ($data, $remote){
|
||||
return array($string, $data['port'], $remote);
|
||||
});
|
||||
} else {
|
||||
$ip = long2ip($data['ipLong']);
|
||||
return array($ip, $data['port'], $remote);
|
||||
}
|
||||
})->then(function ($target) use ($stream, $that) {
|
||||
return $that->connectTarget($stream, $target)->then(function (ConnectionInterface $remote) use ($stream){
|
||||
$stream->write(pack('C8', 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
|
||||
|
||||
return $remote;
|
||||
}, function($error) use ($stream){
|
||||
$stream->end(pack('C8', 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
|
||||
|
||||
throw $error;
|
||||
});
|
||||
}, function($error) {
|
||||
throw new UnexpectedValueException('SOCKS4 protocol error',0,$error);
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleSocks5(ConnectionInterface $stream, $auth, StreamReader $reader)
|
||||
{
|
||||
$remote = $stream->getRemoteAddress();
|
||||
if ($remote !== null) {
|
||||
// remove transport scheme and prefix socks5:// instead
|
||||
$secure = strpos($remote, 'tls://') === 0;
|
||||
if (($pos = strpos($remote, '://')) !== false) {
|
||||
$remote = substr($remote, $pos + 3);
|
||||
}
|
||||
$remote = 'socks' . ($secure ? 's' : '') . '://' . $remote;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
return $reader->readByte()->then(function ($num) use ($reader) {
|
||||
// $num different authentication mechanisms offered
|
||||
return $reader->readLength($num);
|
||||
})->then(function ($methods) use ($reader, $stream, $auth, &$remote) {
|
||||
if ($auth === null && strpos($methods,"\x00") !== false) {
|
||||
// accept "no authentication"
|
||||
$stream->write(pack('C2', 0x05, 0x00));
|
||||
|
||||
return 0x00;
|
||||
} else if ($auth !== null && strpos($methods,"\x02") !== false) {
|
||||
// username/password authentication (RFC 1929) sub negotiation
|
||||
$stream->write(pack('C2', 0x05, 0x02));
|
||||
return $reader->readByteAssert(0x01)->then(function () use ($reader) {
|
||||
return $reader->readByte();
|
||||
})->then(function ($length) use ($reader) {
|
||||
return $reader->readLength($length);
|
||||
})->then(function ($username) use ($reader, $auth, $stream, &$remote) {
|
||||
return $reader->readByte()->then(function ($length) use ($reader) {
|
||||
return $reader->readLength($length);
|
||||
})->then(function ($password) use ($username, $auth, $stream, &$remote) {
|
||||
// username and password given => authenticate
|
||||
|
||||
// prefix username/password to remote URI
|
||||
if ($remote !== null) {
|
||||
$remote = str_replace('://', '://' . rawurlencode($username) . ':' . rawurlencode($password) . '@', $remote);
|
||||
}
|
||||
|
||||
return $auth($username, $password, $remote)->then(function ($authenticated) use ($stream) {
|
||||
if ($authenticated) {
|
||||
// accept auth
|
||||
$stream->write(pack('C2', 0x01, 0x00));
|
||||
} else {
|
||||
// reject auth => send any code but 0x00
|
||||
$stream->end(pack('C2', 0x01, 0xFF));
|
||||
throw new UnexpectedValueException('Authentication denied');
|
||||
}
|
||||
}, function ($e) use ($stream) {
|
||||
// reject failed authentication => send any code but 0x00
|
||||
$stream->end(pack('C2', 0x01, 0xFF));
|
||||
throw new UnexpectedValueException('Authentication error', 0, $e);
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// reject all offered authentication methods
|
||||
$stream->write(pack('C2', 0x05, 0xFF));
|
||||
throw new UnexpectedValueException('No acceptable authentication mechanism found');
|
||||
}
|
||||
})->then(function ($method) use ($reader) {
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'command' => 'C',
|
||||
'null' => 'C',
|
||||
'type' => 'C'
|
||||
));
|
||||
})->then(function ($data) use ($reader) {
|
||||
if ($data['version'] !== 0x05) {
|
||||
throw new UnexpectedValueException('Invalid SOCKS version');
|
||||
}
|
||||
if ($data['command'] !== 0x01) {
|
||||
throw new UnexpectedValueException('Only CONNECT requests supported', Server::ERROR_COMMAND_UNSUPPORTED);
|
||||
}
|
||||
// if ($data['null'] !== 0x00) {
|
||||
// throw new UnexpectedValueException('Reserved byte has to be NULL');
|
||||
// }
|
||||
if ($data['type'] === 0x03) {
|
||||
// target hostname string
|
||||
return $reader->readByte()->then(function ($len) use ($reader) {
|
||||
return $reader->readLength($len);
|
||||
});
|
||||
} else if ($data['type'] === 0x01) {
|
||||
// target IPv4
|
||||
return $reader->readLength(4)->then(function ($addr) {
|
||||
return inet_ntop($addr);
|
||||
});
|
||||
} else if ($data['type'] === 0x04) {
|
||||
// target IPv6
|
||||
return $reader->readLength(16)->then(function ($addr) {
|
||||
return inet_ntop($addr);
|
||||
});
|
||||
} else {
|
||||
throw new UnexpectedValueException('Invalid address type', Server::ERROR_ADDRESS_UNSUPPORTED);
|
||||
}
|
||||
})->then(function ($host) use ($reader, &$remote) {
|
||||
return $reader->readBinary(array('port'=>'n'))->then(function ($data) use ($host, &$remote) {
|
||||
return array($host, $data['port'], $remote);
|
||||
});
|
||||
})->then(function ($target) use ($that, $stream) {
|
||||
return $that->connectTarget($stream, $target);
|
||||
}, function($error) use ($stream) {
|
||||
throw new UnexpectedValueException('SOCKS5 protocol error', $error->getCode(), $error);
|
||||
})->then(function (ConnectionInterface $remote) use ($stream) {
|
||||
$stream->write(pack('C4Nn', 0x05, 0x00, 0x00, 0x01, 0, 0));
|
||||
|
||||
return $remote;
|
||||
}, function(Exception $error) use ($stream){
|
||||
$stream->write(pack('C4Nn', 0x05, $error->getCode() === 0 ? Server::ERROR_GENERAL : $error->getCode(), 0x00, 0x01, 0, 0));
|
||||
|
||||
throw $error;
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function connectTarget(ConnectionInterface $stream, array $target)
|
||||
{
|
||||
$uri = $target[0];
|
||||
if (strpos($uri, ':') !== false) {
|
||||
$uri = '[' . $uri . ']';
|
||||
}
|
||||
$uri .= ':' . $target[1];
|
||||
|
||||
// validate URI so a string hostname can not pass excessive URI parts
|
||||
$parts = parse_url('tcp://' . $uri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || count($parts) !== 3) {
|
||||
return \React\Promise\reject(new InvalidArgumentException('Invalid target URI given'));
|
||||
}
|
||||
|
||||
if (isset($target[2])) {
|
||||
$uri .= '?source=' . rawurlencode($target[2]);
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$connecting = $this->connector->connect($uri);
|
||||
|
||||
$stream->on('close', function () use ($connecting) {
|
||||
$connecting->cancel();
|
||||
});
|
||||
|
||||
return $connecting->then(function (ConnectionInterface $remote) use ($stream, $that) {
|
||||
$stream->pipe($remote, array('end'=>false));
|
||||
$remote->pipe($stream, array('end'=>false));
|
||||
|
||||
// remote end closes connection => stop reading from local end, try to flush buffer to local and disconnect local
|
||||
$remote->on('end', function() use ($stream, $that) {
|
||||
$that->endConnection($stream);
|
||||
});
|
||||
|
||||
// local end closes connection => stop reading from remote end, try to flush buffer to remote and disconnect remote
|
||||
$stream->on('end', function() use ($remote, $that) {
|
||||
$that->endConnection($remote);
|
||||
});
|
||||
|
||||
// set bigger buffer size of 100k to improve performance
|
||||
$stream->bufferSize = $remote->bufferSize = 100 * 1024 * 1024;
|
||||
|
||||
return $remote;
|
||||
}, function(Exception $error) {
|
||||
// default to general/unknown error
|
||||
$code = Server::ERROR_GENERAL;
|
||||
|
||||
// map common socket error conditions to limited list of SOCKS error codes
|
||||
if ((defined('SOCKET_EACCES') && $error->getCode() === SOCKET_EACCES) || $error->getCode() === 13) {
|
||||
$code = Server::ERROR_NOT_ALLOWED_BY_RULESET;
|
||||
} elseif ((defined('SOCKET_EHOSTUNREACH') && $error->getCode() === SOCKET_EHOSTUNREACH) || $error->getCode() === 113) {
|
||||
$code = Server::ERROR_HOST_UNREACHABLE;
|
||||
} elseif ((defined('SOCKET_ENETUNREACH') && $error->getCode() === SOCKET_ENETUNREACH) || $error->getCode() === 101) {
|
||||
$code = Server::ERROR_NETWORK_UNREACHABLE;
|
||||
} elseif ((defined('SOCKET_ECONNREFUSED') && $error->getCode() === SOCKET_ECONNREFUSED) || $error->getCode() === 111 || $error->getMessage() === 'Connection refused') {
|
||||
// Socket component does not currently assign an error code for this, so we have to resort to checking the exception message
|
||||
$code = Server::ERROR_CONNECTION_REFUSED;
|
||||
} elseif ((defined('SOCKET_ETIMEDOUT') && $error->getCode() === SOCKET_ETIMEDOUT) || $error->getCode() === 110 || $error instanceof TimeoutException) {
|
||||
// Socket component does not currently assign an error code for this, but we can rely on the TimeoutException
|
||||
$code = Server::ERROR_TTL;
|
||||
}
|
||||
|
||||
throw new UnexpectedValueException('Unable to connect to remote target', $code, $error);
|
||||
});
|
||||
}
|
||||
}
|
149
vendor/clue/socks-react/src/StreamReader.php
vendored
Normal file
149
vendor/clue/socks-react/src/StreamReader.php
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use React\Promise\Deferred;
|
||||
use \InvalidArgumentException;
|
||||
use \UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class StreamReader
|
||||
{
|
||||
const RET_DONE = true;
|
||||
const RET_INCOMPLETE = null;
|
||||
|
||||
private $buffer = '';
|
||||
private $queue = array();
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
|
||||
do {
|
||||
$current = reset($this->queue);
|
||||
|
||||
if ($current === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* @var $current Closure */
|
||||
|
||||
$ret = $current($this->buffer);
|
||||
|
||||
if ($ret === self::RET_INCOMPLETE) {
|
||||
// current is incomplete, so wait for further data to arrive
|
||||
break;
|
||||
} else {
|
||||
// current is done, remove from list and continue with next
|
||||
array_shift($this->queue);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public function readBinary($structure)
|
||||
{
|
||||
$length = 0;
|
||||
$unpack = '';
|
||||
foreach ($structure as $name=>$format) {
|
||||
if ($length !== 0) {
|
||||
$unpack .= '/';
|
||||
}
|
||||
$unpack .= $format . $name;
|
||||
|
||||
if ($format === 'C') {
|
||||
++$length;
|
||||
} else if ($format === 'n') {
|
||||
$length += 2;
|
||||
} else if ($format === 'N') {
|
||||
$length += 4;
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid format given');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->readLength($length)->then(function ($response) use ($unpack) {
|
||||
return unpack($unpack, $response);
|
||||
});
|
||||
}
|
||||
|
||||
public function readLength($bytes)
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
$this->readBufferCallback(function (&$buffer) use ($bytes, $deferred) {
|
||||
if (strlen($buffer) >= $bytes) {
|
||||
$deferred->resolve((string)substr($buffer, 0, $bytes));
|
||||
$buffer = (string)substr($buffer, $bytes);
|
||||
|
||||
return StreamReader::RET_DONE;
|
||||
}
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
public function readByte()
|
||||
{
|
||||
return $this->readBinary(array(
|
||||
'byte' => 'C'
|
||||
))->then(function ($data) {
|
||||
return $data['byte'];
|
||||
});
|
||||
}
|
||||
|
||||
public function readByteAssert($expect)
|
||||
{
|
||||
return $this->readByte()->then(function ($byte) use ($expect) {
|
||||
if ($byte !== $expect) {
|
||||
throw new UnexpectedValueException('Unexpected byte encountered');
|
||||
}
|
||||
return $byte;
|
||||
});
|
||||
}
|
||||
|
||||
public function readStringNull()
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
$string = '';
|
||||
|
||||
$that = $this;
|
||||
$readOne = function () use (&$readOne, $that, $deferred, &$string) {
|
||||
$that->readByte()->then(function ($byte) use ($deferred, &$string, $readOne) {
|
||||
if ($byte === 0x00) {
|
||||
$deferred->resolve($string);
|
||||
} else {
|
||||
$string .= chr($byte);
|
||||
$readOne();
|
||||
}
|
||||
});
|
||||
};
|
||||
$readOne();
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
public function readBufferCallback(/* callable */ $callable)
|
||||
{
|
||||
if (!is_callable($callable)) {
|
||||
throw new InvalidArgumentException('Given function must be callable');
|
||||
}
|
||||
|
||||
if ($this->queue) {
|
||||
$this->queue []= $callable;
|
||||
} else {
|
||||
$this->queue = array($callable);
|
||||
|
||||
if ($this->buffer !== '') {
|
||||
// this is the first element in the queue and the buffer is filled => trigger write procedure
|
||||
$this->write('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getBuffer()
|
||||
{
|
||||
return $this->buffer;
|
||||
}
|
||||
}
|
21
vendor/clue/stdio-react/LICENSE
vendored
Normal file
21
vendor/clue/stdio-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
37
vendor/clue/stdio-react/composer.json
vendored
Normal file
37
vendor/clue/stdio-react/composer.json
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "clue/stdio-react",
|
||||
"description": "Async, event-driven console input & output (STDIN, STDOUT) for truly interactive CLI applications, built on top of ReactPHP",
|
||||
"keywords": ["stdio", "stdin", "stdout", "interactive", "CLI", "readline", "autocomplete", "autocompletion", "history", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-stdio",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"clue/term-react": "^1.0 || ^0.1.1",
|
||||
"clue/utf8-react": "^1.0 || ^0.1",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/stream": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/arguments": "^2.0",
|
||||
"clue/commander": "^1.2",
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "Using ext-mbstring should provide slightly better performance for handling I/O"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Stdio\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Stdio\\": "tests/" }
|
||||
}
|
||||
}
|
1017
vendor/clue/stdio-react/src/Readline.php
vendored
Normal file
1017
vendor/clue/stdio-react/src/Readline.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
630
vendor/clue/stdio-react/src/Stdio.php
vendored
Normal file
630
vendor/clue/stdio-react/src/Stdio.php
vendored
Normal file
@ -0,0 +1,630 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Stdio;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
use React\Stream\ReadableResourceStream;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableResourceStream;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
class Stdio extends EventEmitter implements DuplexStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $output;
|
||||
private $readline;
|
||||
|
||||
private $ending = false;
|
||||
private $closed = false;
|
||||
private $incompleteLine = '';
|
||||
private $originalTtyMode = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?ReadableStreamInterface $input
|
||||
* @param ?WritableStreamInterface $output
|
||||
* @param ?Readline $readline
|
||||
*/
|
||||
public function __construct(LoopInterface $loop = null, ReadableStreamInterface $input = null, WritableStreamInterface $output = null, Readline $readline = null)
|
||||
{
|
||||
if ($input === null) {
|
||||
$input = $this->createStdin($loop); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if ($output === null) {
|
||||
$output = $this->createStdout($loop); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if ($readline === null) {
|
||||
$readline = new Readline($input, $output, $this);
|
||||
}
|
||||
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
$this->readline = $readline;
|
||||
|
||||
$that = $this;
|
||||
|
||||
// readline data emits a new line
|
||||
$incomplete =& $this->incompleteLine;
|
||||
$this->readline->on('data', function($line) use ($that, &$incomplete) {
|
||||
// readline emits a new line on enter, so start with a blank line
|
||||
$incomplete = '';
|
||||
$that->emit('data', array($line));
|
||||
});
|
||||
|
||||
// handle all input events (readline forwards all input events)
|
||||
$this->readline->on('error', array($this, 'handleError'));
|
||||
$this->readline->on('end', array($this, 'handleEnd'));
|
||||
$this->readline->on('close', array($this, 'handleCloseInput'));
|
||||
|
||||
// handle all output events
|
||||
$this->output->on('error', array($this, 'handleError'));
|
||||
$this->output->on('close', array($this, 'handleCloseOutput'));
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->restoreTtyMode();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return $this->output->isWritable();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
// return false if already ended, return true if writing empty string
|
||||
if ($this->ending || $data === '') {
|
||||
return !$this->ending;
|
||||
}
|
||||
|
||||
$out = $data;
|
||||
|
||||
$lastNewline = strrpos($data, "\n");
|
||||
|
||||
$restoreReadline = false;
|
||||
|
||||
if ($this->incompleteLine !== '') {
|
||||
// the last write did not end with a newline => append to existing row
|
||||
|
||||
// move one line up and move cursor to last position before writing data
|
||||
$out = "\033[A" . "\r\033[" . $this->width($this->incompleteLine) . "C" . $out;
|
||||
|
||||
// data contains a newline, so this will overwrite the readline prompt
|
||||
if ($lastNewline !== false) {
|
||||
// move cursor to beginning of readline prompt and clear line
|
||||
// clearing is important because $data may not overwrite the whole line
|
||||
$out = "\r\033[K" . $out;
|
||||
|
||||
// make sure to restore readline after this output
|
||||
$restoreReadline = true;
|
||||
}
|
||||
} else {
|
||||
// here, we're writing to a new line => overwrite readline prompt
|
||||
|
||||
// move cursor to beginning of readline prompt and clear line
|
||||
$out = "\r\033[K" . $out;
|
||||
|
||||
// we always overwrite the readline prompt, so restore it on next line
|
||||
$restoreReadline = true;
|
||||
}
|
||||
|
||||
// following write will have have to append to this line if it does not end with a newline
|
||||
$endsWithNewline = substr($data, -1) === "\n";
|
||||
|
||||
if ($endsWithNewline) {
|
||||
// line ends with newline, so this is line is considered complete
|
||||
$this->incompleteLine = '';
|
||||
} else {
|
||||
// always end data with newline in order to append readline on next line
|
||||
$out .= "\n";
|
||||
|
||||
if ($lastNewline === false) {
|
||||
// contains no newline at all, everything is incomplete
|
||||
$this->incompleteLine .= $data;
|
||||
} else {
|
||||
// contains a newline, everything behind it is incomplete
|
||||
$this->incompleteLine = (string)substr($data, $lastNewline + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($restoreReadline) {
|
||||
// write output and restore original readline prompt and line buffer
|
||||
return $this->output->write($out . $this->readline->getDrawString());
|
||||
} else {
|
||||
// restore original cursor position in readline prompt
|
||||
$pos = $this->width($this->readline->getPrompt()) + $this->readline->getCursorCell();
|
||||
if ($pos !== 0) {
|
||||
// we always start at beginning of line, move right by X
|
||||
$out .= "\033[" . $pos . "C";
|
||||
}
|
||||
|
||||
// write to actual output stream
|
||||
return $this->output->write($out);
|
||||
}
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
if ($this->ending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($data !== null) {
|
||||
$this->write($data);
|
||||
}
|
||||
|
||||
$this->ending = true;
|
||||
|
||||
// clear readline output, close input and end output
|
||||
$this->readline->setInput('')->setPrompt('');
|
||||
$this->input->close();
|
||||
$this->output->end();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ending = true;
|
||||
$this->closed = true;
|
||||
|
||||
$this->input->close();
|
||||
$this->output->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @return Readline
|
||||
*/
|
||||
public function getReadline()
|
||||
{
|
||||
return $this->readline;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* prompt to prepend to input line
|
||||
*
|
||||
* Will redraw the current input prompt with the current input buffer.
|
||||
*
|
||||
* @param string $prompt
|
||||
* @return void
|
||||
*/
|
||||
public function setPrompt($prompt)
|
||||
{
|
||||
$this->readline->setPrompt($prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the prompt to prepend to input line
|
||||
*
|
||||
* @return string
|
||||
* @see self::setPrompt()
|
||||
*/
|
||||
public function getPrompt()
|
||||
{
|
||||
return $this->readline->getPrompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* sets whether/how to echo text input
|
||||
*
|
||||
* The default setting is `true`, which means that every character will be
|
||||
* echo'ed as-is, i.e. you can see what you're typing.
|
||||
* For example: Typing "test" shows "test".
|
||||
*
|
||||
* You can turn this off by supplying `false`, which means that *nothing*
|
||||
* will be echo'ed while you're typing. This could be a good idea for
|
||||
* password prompts. Note that this could be confusing for users, so using
|
||||
* a character replacement as following is often preferred.
|
||||
* For example: Typing "test" shows "" (nothing).
|
||||
*
|
||||
* Alternative, you can supply a single character replacement character
|
||||
* that will be echo'ed for each character in the text input. This could
|
||||
* be a good idea for password prompts, where an asterisk character ("*")
|
||||
* is often used to indicate typing activity and password length.
|
||||
* For example: Typing "test" shows "****" (with asterisk replacement)
|
||||
*
|
||||
* Changing this setting will redraw the current prompt and echo the current
|
||||
* input buffer according to the new setting.
|
||||
*
|
||||
* @param boolean|string $echo echo can be turned on (boolean true) or off (boolean true), or you can supply a single character replacement string
|
||||
* @return void
|
||||
*/
|
||||
public function setEcho($echo)
|
||||
{
|
||||
$this->readline->setEcho($echo);
|
||||
}
|
||||
|
||||
/**
|
||||
* whether or not to support moving cursor left and right
|
||||
*
|
||||
* switching cursor support moves the cursor to the end of the current
|
||||
* input buffer (if any).
|
||||
*
|
||||
* @param boolean $move
|
||||
* @return void
|
||||
*/
|
||||
public function setMove($move)
|
||||
{
|
||||
$this->readline->setMove($move);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current cursor position measured in number of text characters.
|
||||
*
|
||||
* Note that the number of text characters doesn't necessarily reflect the
|
||||
* number of monospace cells occupied by the text characters. If you want
|
||||
* to know the latter, use `self::getCursorCell()` instead.
|
||||
*
|
||||
* @return int
|
||||
* @see self::getCursorCell() to get the position measured in monospace cells
|
||||
* @see self::moveCursorTo() to move the cursor to a given character position
|
||||
* @see self::moveCursorBy() to move the cursor by given number of characters
|
||||
* @see self::setMove() to toggle whether the user can move the cursor position
|
||||
*/
|
||||
public function getCursorPosition()
|
||||
{
|
||||
return $this->readline->getCursorPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current cursor position measured in monospace cells.
|
||||
*
|
||||
* Note that the cell position doesn't necessarily reflect the number of
|
||||
* text characters. If you want to know the latter, use
|
||||
* `self::getCursorPosition()` instead.
|
||||
*
|
||||
* Most "normal" characters occupy a single monospace cell, i.e. the ASCII
|
||||
* sequence for "A" requires a single cell, as do most UTF-8 sequences
|
||||
* like "Ä".
|
||||
*
|
||||
* However, there are a number of code points that do not require a cell
|
||||
* (i.e. invisible surrogates) or require two cells (e.g. some asian glyphs).
|
||||
*
|
||||
* Also note that this takes the echo mode into account, i.e. the cursor is
|
||||
* always at position zero if echo is off. If using a custom echo character
|
||||
* (like asterisk), it will take its width into account instead of the actual
|
||||
* input characters.
|
||||
*
|
||||
* @return int
|
||||
* @see self::getCursorPosition() to get current cursor position measured in characters
|
||||
* @see self::moveCursorTo() to move the cursor to a given character position
|
||||
* @see self::moveCursorBy() to move the cursor by given number of characters
|
||||
* @see self::setMove() to toggle whether the user can move the cursor position
|
||||
* @see self::setEcho()
|
||||
*/
|
||||
public function getCursorCell()
|
||||
{
|
||||
return $this->readline->getCursorCell();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves cursor to right by $n chars (or left if $n is negative).
|
||||
*
|
||||
* Zero value or values out of range (exceeding current input buffer) are
|
||||
* simply ignored.
|
||||
*
|
||||
* Will redraw() the readline only if the visible cell position changes,
|
||||
* see `self::getCursorCell()` for more details.
|
||||
*
|
||||
* @param int $n
|
||||
* @return void
|
||||
*/
|
||||
public function moveCursorBy($n)
|
||||
{
|
||||
$this->readline->moveCursorBy($n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves cursor to given position in current line buffer.
|
||||
*
|
||||
* Values out of range (exceeding current input buffer) are simply ignored.
|
||||
*
|
||||
* Will redraw() the readline only if the visible cell position changes,
|
||||
* see `self::getCursorCell()` for more details.
|
||||
*
|
||||
* @param int $n
|
||||
* @return void
|
||||
*/
|
||||
public function moveCursorTo($n)
|
||||
{
|
||||
$this->readline->moveCursorTo($n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given input to the current text input buffer at the current position
|
||||
*
|
||||
* This moves the cursor accordingly to the number of characters added.
|
||||
*
|
||||
* @param string $input
|
||||
* @return void
|
||||
*/
|
||||
public function addInput($input)
|
||||
{
|
||||
$this->readline->addInput($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* set current text input buffer
|
||||
*
|
||||
* this moves the cursor to the end of the current
|
||||
* input buffer (if any).
|
||||
*
|
||||
* @param string $input
|
||||
* @return void
|
||||
*/
|
||||
public function setInput($input)
|
||||
{
|
||||
$this->readline->setInput($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* get current text input buffer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getInput()
|
||||
{
|
||||
return $this->readline->getInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new line to the (bottom position of the) history list
|
||||
*
|
||||
* @param string $line
|
||||
* @return void
|
||||
*/
|
||||
public function addHistory($line)
|
||||
{
|
||||
$this->readline->addHistory($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the complete history list
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearHistory()
|
||||
{
|
||||
$this->readline->clearHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all lines in the history
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function listHistory()
|
||||
{
|
||||
return $this->readline->listHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits the history to a maximum of N entries and truncates the current history list accordingly
|
||||
*
|
||||
* @param int|null $limit
|
||||
* @return void
|
||||
*/
|
||||
public function limitHistory($limit)
|
||||
{
|
||||
$this->readline->limitHistory($limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* set autocompletion handler to use
|
||||
*
|
||||
* The autocomplete handler will be called whenever the user hits the TAB
|
||||
* key.
|
||||
*
|
||||
* @param callable|null $autocomplete
|
||||
* @return void
|
||||
* @throws \InvalidArgumentException if the given callable is invalid
|
||||
*/
|
||||
public function setAutocomplete($autocomplete)
|
||||
{
|
||||
$this->readline->setAutocomplete($autocomplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* whether or not to emit a audible/visible BELL signal when using a disabled function
|
||||
*
|
||||
* By default, this class will emit a BELL signal when using a disable function,
|
||||
* such as using the <kbd>left</kbd> or <kbd>backspace</kbd> keys when
|
||||
* already at the beginning of the line.
|
||||
*
|
||||
* Whether or not the BELL is audible/visible depends on the termin and its
|
||||
* settings, i.e. some terminals may "beep" or flash the screen or emit a
|
||||
* short vibration.
|
||||
*
|
||||
* @param bool $bell
|
||||
* @return void
|
||||
*/
|
||||
public function setBell($bell)
|
||||
{
|
||||
$this->readline->setBell($bell);
|
||||
}
|
||||
|
||||
private function width($str)
|
||||
{
|
||||
return $this->readline->strwidth($str) - 2 * substr_count($str, "\x08");
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->emit('end');
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleCloseInput()
|
||||
{
|
||||
$this->restoreTtyMode();
|
||||
|
||||
if (!$this->output->isWritable()) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleCloseOutput()
|
||||
{
|
||||
if (!$this->input->isReadable()) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore this is covered by functional tests with/without ext-readline
|
||||
*/
|
||||
private function restoreTtyMode()
|
||||
{
|
||||
if (function_exists('readline_callback_handler_remove')) {
|
||||
// remove dummy readline handler to turn to default input mode
|
||||
readline_callback_handler_remove();
|
||||
} elseif ($this->originalTtyMode !== null && is_resource(STDIN) && $this->isTty()) {
|
||||
// Reset stty so it behaves normally again
|
||||
shell_exec('stty ' . escapeshellarg($this->originalTtyMode));
|
||||
$this->originalTtyMode = null;
|
||||
}
|
||||
|
||||
// restore blocking mode so following programs behave normally
|
||||
if (defined('STDIN') && is_resource(STDIN)) {
|
||||
stream_set_blocking(STDIN, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?LoopInterface $loop
|
||||
* @return ReadableStreamInterface
|
||||
* @codeCoverageIgnore this is covered by functional tests with/without ext-readline
|
||||
*/
|
||||
private function createStdin(LoopInterface $loop = null)
|
||||
{
|
||||
// STDIN not defined ("php -a") or already closed (`fclose(STDIN)`)
|
||||
// also support starting program with closed STDIN ("example.php 0<&-")
|
||||
// the stream is a valid resource and is not EOF, but fstat fails
|
||||
if (!defined('STDIN') || !is_resource(STDIN) || fstat(STDIN) === false) {
|
||||
$stream = new ReadableResourceStream(fopen('php://memory', 'r'), $loop);
|
||||
$stream->close();
|
||||
return $stream;
|
||||
}
|
||||
|
||||
$stream = new ReadableResourceStream(STDIN, $loop);
|
||||
|
||||
if (function_exists('readline_callback_handler_install')) {
|
||||
// Prefer `ext-readline` to install dummy handler to turn on raw input mode.
|
||||
// We will never actually feed the readline handler and instead
|
||||
// handle all input in our `Readline` implementation.
|
||||
readline_callback_handler_install('', function () { });
|
||||
return $stream;
|
||||
}
|
||||
|
||||
if ($this->isTty()) {
|
||||
$this->originalTtyMode = rtrim(shell_exec('stty -g'), PHP_EOL);
|
||||
|
||||
// Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
|
||||
shell_exec('stty -icanon -echo');
|
||||
}
|
||||
|
||||
// register shutdown function to restore TTY mode in case of unclean shutdown (uncaught exception)
|
||||
// this will not trigger on SIGKILL etc., but the terminal should take care of this
|
||||
register_shutdown_function(array($this, 'close'));
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?LoopInterface $loop
|
||||
* @return WritableStreamInterface
|
||||
* @codeCoverageIgnore this is covered by functional tests
|
||||
*/
|
||||
private function createStdout(LoopInterface $loop = null)
|
||||
{
|
||||
// STDOUT not defined ("php -a") or already closed (`fclose(STDOUT)`)
|
||||
// also support starting program with closed STDOUT ("example.php >&-")
|
||||
// the stream is a valid resource and is not EOF, but fstat fails
|
||||
if (!defined('STDOUT') || !is_resource(STDOUT) || fstat(STDOUT) === false) {
|
||||
$output = new WritableResourceStream(fopen('php://memory', 'r+'), $loop);
|
||||
$output->close();
|
||||
} else {
|
||||
$output = new WritableResourceStream(STDOUT, $loop);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function isTty()
|
||||
{
|
||||
if (PHP_VERSION_ID >= 70200) {
|
||||
// Prefer `stream_isatty()` (available as of PHP 7.2 only)
|
||||
return stream_isatty(STDIN);
|
||||
} elseif (function_exists('posix_isatty')) {
|
||||
// Otherwise use `posix_isatty` if available (requires `ext-posix`)
|
||||
return posix_isatty(STDIN);
|
||||
}
|
||||
|
||||
// otherwise try to guess based on stat file mode and device major number
|
||||
// Must be special character device: ($mode & S_IFMT) === S_IFCHR
|
||||
// And device major number must be allocated to TTYs (2-5 and 128-143)
|
||||
// For what it's worth, checking for device gid 5 (tty) is less reliable.
|
||||
// @link http://man7.org/linux/man-pages/man7/inode.7.html
|
||||
// @link https://www.kernel.org/doc/html/v4.11/admin-guide/devices.html#terminal-devices
|
||||
$stat = fstat(STDIN);
|
||||
$mode = isset($stat['mode']) ? ($stat['mode'] & 0170000) : 0;
|
||||
$major = isset($stat['dev']) ? (($stat['dev'] >> 8) & 0xff) : 0;
|
||||
|
||||
return ($mode === 0020000 && $major >= 2 && $major <= 143 && ($major <=5 || $major >= 128));
|
||||
}
|
||||
}
|
21
vendor/clue/term-react/LICENSE
vendored
Normal file
21
vendor/clue/term-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
27
vendor/clue/term-react/composer.json
vendored
Normal file
27
vendor/clue/term-react/composer.json
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "clue/term-react",
|
||||
"description": "Streaming terminal emulator, built on top of ReactPHP.",
|
||||
"keywords": ["terminal", "control codes", "xterm", "ANSI", "ASCII", "VT100", "csi", "osc", "apc", "dps", "pm", "C1", "C0", "streaming", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-term",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Term\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Term\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/stream": "^1.0 || ^0.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3"
|
||||
}
|
||||
}
|
223
vendor/clue/term-react/src/ControlCodeParser.php
vendored
Normal file
223
vendor/clue/term-react/src/ControlCodeParser.php
vendored
Normal file
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Term;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
|
||||
class ControlCodeParser extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
private $buffer = '';
|
||||
|
||||
/**
|
||||
* we know about the following C1 types (7 bit only)
|
||||
*
|
||||
* followed by "[" means it's CSI (Control Sequence Introducer)
|
||||
* followed by "]" means it's OSC (Operating System Controls)
|
||||
* followed by "_" means it's APC (Application Program-Control)
|
||||
* followed by "P" means it's DPS (Device-Control string)
|
||||
* followed by "^" means it's PM (Privacy Message)
|
||||
*
|
||||
* Each of these will be parsed until the sequence ends and then emitted
|
||||
* under their respective name.
|
||||
*
|
||||
* All other C1 types will be emitted under the "c1" name without any
|
||||
* further processing.
|
||||
*
|
||||
* C1 types in 8 bit are currently not supported, as they require special
|
||||
* care with regards to whether UTF-8 mode is enabled. So far this has
|
||||
* turned out to be a non-issue because most terminal emulators *accept*
|
||||
* boths formats, but usually *send* in 7 bit mode exclusively.
|
||||
*/
|
||||
private $types = array(
|
||||
'[' => 'csi',
|
||||
']' => 'osc',
|
||||
'_' => 'apc',
|
||||
'P' => 'dps',
|
||||
'^' => 'pm',
|
||||
);
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
if (!$this->input->isReadable()) {
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->buffer = '';
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
|
||||
while ($this->buffer !== '') {
|
||||
// search for first control character (C0 and DEL)
|
||||
$c0 = false;
|
||||
for ($i = 0; isset($this->buffer[$i]); ++$i) {
|
||||
$code = ord($this->buffer[$i]);
|
||||
if ($code < 0x20 || $code === 0x7F) {
|
||||
$c0 = $i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// no C0 found, emit whole buffer as data
|
||||
if ($c0 === false) {
|
||||
$data = $this->buffer;
|
||||
$this->buffer = '';
|
||||
|
||||
$this->emit('data', array($data));
|
||||
return;
|
||||
}
|
||||
|
||||
// C0 found somewhere inbetween, emit everything before C0 as data
|
||||
if ($c0 !== 0) {
|
||||
$data = substr($this->buffer, 0, $c0);
|
||||
$this->buffer = substr($this->buffer, $c0);
|
||||
|
||||
$this->emit('data', array($data));
|
||||
continue;
|
||||
}
|
||||
|
||||
// C0 is now at start of buffer
|
||||
// check if this is a normal C0 code or an ESC (\x1B = \033)
|
||||
// normal C0 will be emitted, ESC will be parsed further
|
||||
if ($this->buffer[0] !== "\x1B") {
|
||||
$data = $this->buffer[0];
|
||||
$this->buffer = (string)substr($this->buffer, 1);
|
||||
|
||||
$this->emit('c0', array($data));
|
||||
continue;
|
||||
}
|
||||
|
||||
// check following byte to determine type
|
||||
if (!isset($this->buffer[1])) {
|
||||
// type currently unknown, wait for next data chunk
|
||||
break;
|
||||
}
|
||||
|
||||
// if this is an unknown type, just emit as "c1" without further parsing
|
||||
if (!isset($this->types[$this->buffer[1]])) {
|
||||
$data = substr($this->buffer, 0, 2);
|
||||
$this->buffer = (string)substr($this->buffer, 2);
|
||||
|
||||
$this->emit('c1', array($data));
|
||||
continue;
|
||||
}
|
||||
|
||||
// this is known type, check for the sequence end
|
||||
$type = $this->types[$this->buffer[1]];
|
||||
$found = false;
|
||||
|
||||
if ($type === 'csi') {
|
||||
// CSI is now at the start of the buffer, search final character
|
||||
for ($i = 2; isset($this->buffer[$i]); ++$i) {
|
||||
$code = ord($this->buffer[$i]);
|
||||
|
||||
// final character between \x40-\x7E
|
||||
if ($code >= 64 && $code <= 126) {
|
||||
$data = substr($this->buffer, 0, $i + 1);
|
||||
$this->buffer = (string)substr($this->buffer, $i + 1);
|
||||
|
||||
$this->emit($type, array($data));
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// all other types are terminated by ST
|
||||
// only OSC can also be terminted by BEL (whichever comes first)
|
||||
$st = strpos($this->buffer, "\x1B\\");
|
||||
$bel = ($type === 'osc') ? strpos($this->buffer, "\x07") : false;
|
||||
|
||||
if ($st !== false && ($bel === false || $bel > $st)) {
|
||||
// ST comes before BEL or no BEL found
|
||||
$data = substr($this->buffer, 0, $st + 2);
|
||||
$this->buffer = (string)substr($this->buffer, $st + 2);
|
||||
|
||||
$this->emit($type, array($data));
|
||||
$found = true;
|
||||
} elseif ($bel !== false) {
|
||||
// BEL comes before ST or no ST found
|
||||
$data = substr($this->buffer, 0, $bel + 1);
|
||||
$this->buffer = (string)substr($this->buffer, $bel + 1);
|
||||
|
||||
$this->emit($type, array($data));
|
||||
$found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// no final character found => wait for next data chunk
|
||||
if (!$found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
if ($this->buffer === '') {
|
||||
$this->emit('end');
|
||||
} else {
|
||||
$this->emit('error', array(new \RuntimeException('Stream ended with incomplete control code sequence in buffer')));
|
||||
}
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
}
|
21
vendor/clue/utf8-react/LICENSE
vendored
Normal file
21
vendor/clue/utf8-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
27
vendor/clue/utf8-react/composer.json
vendored
Normal file
27
vendor/clue/utf8-react/composer.json
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "clue/utf8-react",
|
||||
"description": "Streaming UTF-8 parser, built on top of ReactPHP.",
|
||||
"keywords": ["UTF-8", "utf8", "unicode", "streaming", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-utf8",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Utf8\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Utf8\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4 || ^0.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3 ||^5.7 || ^4.8",
|
||||
"react/stream": "^1.0 || ^0.7"
|
||||
}
|
||||
}
|
174
vendor/clue/utf8-react/src/Sequencer.php
vendored
Normal file
174
vendor/clue/utf8-react/src/Sequencer.php
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Utf8;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
|
||||
/**
|
||||
* forwards only complete UTF-8 sequences
|
||||
*/
|
||||
class Sequencer extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $invalid;
|
||||
|
||||
private $buffer = '';
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input, $replacementCharacter = '?')
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->invalid = $replacementCharacter;
|
||||
|
||||
if (!$input->isReadable()) {
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
$len = strlen($this->buffer);
|
||||
|
||||
$sequence = '';
|
||||
$expect = 0;
|
||||
$out = '';
|
||||
|
||||
for ($i = 0; $i < $len; ++$i) {
|
||||
$char = $this->buffer[$i];
|
||||
$code = ord($char);
|
||||
|
||||
if ($code & 128) {
|
||||
// multi-byte sequence
|
||||
if ($code & 64) {
|
||||
// this is the start of a sequence
|
||||
|
||||
// unexpected start of sequence because already within sequence
|
||||
if ($expect !== 0) {
|
||||
$out .= str_repeat($this->invalid, strlen($sequence));
|
||||
$sequence = '';
|
||||
}
|
||||
|
||||
$sequence = $char;
|
||||
$expect = 2;
|
||||
|
||||
if ($code & 32) {
|
||||
++$expect;
|
||||
if ($code & 16) {
|
||||
++$expect;
|
||||
|
||||
if ($code & 8) {
|
||||
// invalid sequence start length
|
||||
$out .= $this->invalid;
|
||||
$sequence = '';
|
||||
$expect = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// this is a follow-up byte in a sequence
|
||||
if ($expect === 0) {
|
||||
// we're not within a sequence in first place
|
||||
$out .= $this->invalid;
|
||||
} else {
|
||||
// valid following byte in sequence
|
||||
$sequence .= $char;
|
||||
|
||||
// sequence reached expected length => add to output
|
||||
if (strlen($sequence) === $expect) {
|
||||
$out .= $sequence;
|
||||
$sequence = '';
|
||||
$expect = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// simple ASCII character found
|
||||
|
||||
// unexpected because already within sequence
|
||||
if ($expect !== 0) {
|
||||
$out .= str_repeat($this->invalid, strlen($sequence));
|
||||
$sequence = '';
|
||||
$expect = 0;
|
||||
}
|
||||
|
||||
$out .= $char;
|
||||
}
|
||||
}
|
||||
|
||||
if ($out !== '') {
|
||||
$this->buffer = substr($this->buffer, strlen($out));
|
||||
|
||||
$this->emit('data', array($out));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if ($this->buffer !== '' && $this->invalid !== '') {
|
||||
$data = str_repeat($this->invalid, strlen($this->buffer));
|
||||
$this->buffer = '';
|
||||
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $error)
|
||||
{
|
||||
$this->emit('error', array($error));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->buffer = '';
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
}
|
16
vendor/components/jquery/bower.json
vendored
Normal file
16
vendor/components/jquery/bower.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "jquery",
|
||||
"version": "3.5.1",
|
||||
"description": "jQuery component",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"jquery",
|
||||
"component"
|
||||
],
|
||||
"main": "jquery.js",
|
||||
"ignore": [
|
||||
"component.json",
|
||||
"package.json",
|
||||
"composer.json"
|
||||
]
|
||||
}
|
22
vendor/components/jquery/component.json
vendored
Normal file
22
vendor/components/jquery/component.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "jquery",
|
||||
"repo": "components/jquery",
|
||||
"version": "3.5.1",
|
||||
"description": "jQuery component",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"jquery",
|
||||
"component"
|
||||
],
|
||||
"main": "jquery.js",
|
||||
"scripts": [
|
||||
"jquery.js",
|
||||
"jquery.min.js",
|
||||
"jquery.slim.js",
|
||||
"jquery.slim.min.js"
|
||||
],
|
||||
"files": [
|
||||
"jquery.min.map",
|
||||
"jquery.slim.min.map"
|
||||
]
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user