init
This commit is contained in:
2
vendor/clue/socks-react/.gitignore
vendored
Executable file
2
vendor/clue/socks-react/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
/vendor
|
||||
/composer.lock
|
||||
27
vendor/clue/socks-react/.travis.yml
vendored
Executable file
27
vendor/clue/socks-react/.travis.yml
vendored
Executable file
@@ -0,0 +1,27 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
# - 5.3 # requires old distro, see below
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7
|
||||
- hhvm # ignore errors, see below
|
||||
|
||||
# lock distro so new future defaults will not break the build
|
||||
dist: trusty
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.3
|
||||
dist: precise
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
|
||||
sudo: false
|
||||
|
||||
install:
|
||||
- composer install --no-interaction
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-text
|
||||
338
vendor/clue/socks-react/CHANGELOG.md
vendored
Executable file
338
vendor/clue/socks-react/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,338 @@
|
||||
# Changelog
|
||||
|
||||
## 0.8.7 (2017-12-17)
|
||||
|
||||
* Feature: Support SOCKS over TLS (`sockss://` URI scheme)
|
||||
(#70 and #71 by @clue)
|
||||
|
||||
```php
|
||||
// new: now supports SOCKS over TLS
|
||||
$client = new Client('socks5s://localhost', $connector);
|
||||
```
|
||||
|
||||
* Feature: Support communication over Unix domain sockets (UDS)
|
||||
(#69 by @clue)
|
||||
|
||||
```php
|
||||
// new: now supports SOCKS over Unix domain sockets (UDS)
|
||||
$client = new Client('socks5+unix:///tmp/proxy.sock', $connector);
|
||||
```
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit 6
|
||||
(#68 by @clue)
|
||||
|
||||
## 0.8.6 (2017-09-17)
|
||||
|
||||
* Feature: Forward compatibility with Evenement v3.0
|
||||
(#67 by @WyriHaximus)
|
||||
|
||||
## 0.8.5 (2017-09-01)
|
||||
|
||||
* Feature: Use socket error codes for connection rejections
|
||||
(#63 by @clue)
|
||||
|
||||
```php
|
||||
$promise = $proxy->connect('imap.example.com:143');
|
||||
$promise->then(null, function (Exeption $e) {
|
||||
if ($e->getCode() === SOCKET_EACCES) {
|
||||
echo 'Failed to authenticate with proxy!';
|
||||
}
|
||||
throw $e;
|
||||
});
|
||||
```
|
||||
|
||||
* Feature: Report matching SOCKS5 error codes for server side connection errors
|
||||
(#62 by @clue)
|
||||
|
||||
* Fix: Fix SOCKS5 client receiving destination hostnames and
|
||||
fix IPv6 addresses as hostnames for TLS certificates
|
||||
(#64 and #65 by @clue)
|
||||
|
||||
* Improve test suite by locking Travis distro so new defaults will not break the build and
|
||||
optionally exclude tests that rely on working internet connection
|
||||
(#61 and #66 by @clue)
|
||||
|
||||
## 0.8.4 (2017-07-27)
|
||||
|
||||
* Feature: Server now passes client source address to Connector
|
||||
(#60 by @clue)
|
||||
|
||||
## 0.8.3 (2017-07-18)
|
||||
|
||||
* Feature: Pass full remote URI as parameter to authentication callback
|
||||
(#58 by @clue)
|
||||
|
||||
```php
|
||||
// new third parameter passed to authentication callback
|
||||
$server->setAuth(function ($user, $pass, $remote) {
|
||||
$ip = parse_url($remote, PHP_URL_HOST);
|
||||
|
||||
return ($ip === '127.0.0.1');
|
||||
});
|
||||
```
|
||||
|
||||
* Fix: Fix connecting to IPv6 address via SOCKS5 server and validate target
|
||||
URI so hostname can not contain excessive URI components
|
||||
(#59 by @clue)
|
||||
|
||||
* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors
|
||||
(#57 by @clue)
|
||||
|
||||
## 0.8.2 (2017-05-09)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
|
||||
(#56 by @clue)
|
||||
|
||||
## 0.8.1 (2017-04-21)
|
||||
|
||||
* Update examples to use URIs with default port 1080 and accept proxy URI arguments
|
||||
(#54 by @clue)
|
||||
|
||||
* Remove now unneeded dependency on `react/stream`
|
||||
(#55 by @clue)
|
||||
|
||||
## 0.8.0 (2017-04-18)
|
||||
|
||||
* Feature: Merge `Server` class from clue/socks-server
|
||||
(#52 by @clue)
|
||||
|
||||
```php
|
||||
$socket = new React\Socket\Server(1080, $loop);
|
||||
$server = new Clue\React\Socks\Server($loop, $socket);
|
||||
```
|
||||
|
||||
> Upgrading from [clue/socks-server](https://github.com/clue/php-socks-server)?
|
||||
The classes have been moved as-is, so you can simply start using the new
|
||||
class name `Clue\React\Socks\Server` with no other changes required.
|
||||
|
||||
## 0.7.0 (2017-04-14)
|
||||
|
||||
* Feature / BC break: Replace depreacted SocketClient with Socket v0.7 and
|
||||
use `connect($uri)` instead of `create($host, $port)`
|
||||
(#51 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$connector = new React\SocketClient\TcpConnector($loop);
|
||||
$client = new Client(1080, $connector);
|
||||
$client->create('google.com', 80)->then(function (Stream $conn) {
|
||||
$conn->write("…");
|
||||
});
|
||||
|
||||
// new
|
||||
$connector = new React\Socket\TcpConnector($loop);
|
||||
$client = new Client(1080, $connector);
|
||||
$client->connect('google.com:80')->then(function (ConnectionInterface $conn) {
|
||||
$conn->write("…");
|
||||
});
|
||||
```
|
||||
|
||||
* Improve test suite by adding PHPUnit to require-dev
|
||||
(#50 by @clue)
|
||||
|
||||
## 0.6.0 (2016-11-29)
|
||||
|
||||
* Feature / BC break: Pass connector into `Client` instead of loop, remove unneeded deps
|
||||
(#49 by @clue)
|
||||
|
||||
```php
|
||||
// old (connector is create implicitly)
|
||||
$client = new Client('127.0.0.1', $loop);
|
||||
|
||||
// old (connector can optionally be passed)
|
||||
$client = new Client('127.0.0.1', $loop, $connector);
|
||||
|
||||
// new (connector is now mandatory)
|
||||
$connector = new React\SocketClient\TcpConnector($loop);
|
||||
$client = new Client('127.0.0.1', $connector);
|
||||
```
|
||||
|
||||
* Feature / BC break: `Client` now implements `ConnectorInterface`, remove `Connector` adapter
|
||||
(#47 by @clue)
|
||||
|
||||
```php
|
||||
// old (explicit connector functions as an adapter)
|
||||
$connector = $client->createConnector();
|
||||
$promise = $connector->create('google.com', 80);
|
||||
|
||||
// new (client can be used as connector right away)
|
||||
$promise = $client->create('google.com', 80);
|
||||
```
|
||||
|
||||
* Feature / BC break: Remove `createSecureConnector()`, use `SecureConnector` instead
|
||||
(#47 by @clue)
|
||||
|
||||
```php
|
||||
// old (tight coupling and hidden dependency)
|
||||
$tls = $client->createSecureConnector();
|
||||
$promise = $tls->create('google.com', 443);
|
||||
|
||||
// new (more explicit, loose coupling)
|
||||
$tls = new React\SocketClient\SecureConnector($client, $loop);
|
||||
$promise = $tls->create('google.com', 443);
|
||||
```
|
||||
|
||||
* Feature / BC break: Remove `setResolveLocal()` and local DNS resolution and default to remote DNS resolution, use `DnsConnector` instead
|
||||
(#44 by @clue)
|
||||
|
||||
```php
|
||||
// old (implicitly defaults to true, can be disabled)
|
||||
$client->setResolveLocal(false);
|
||||
$tcp = $client->createConnector();
|
||||
$promise = $tcp->create('google.com', 80);
|
||||
|
||||
// new (always disabled, can be re-enabled like this)
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$resolver = $factory->createCached('8.8.8.8', $loop);
|
||||
$tcp = new React\SocketClient\DnsConnector($client, $resolver);
|
||||
$promise = $tcp->create('google.com', 80);
|
||||
```
|
||||
|
||||
* Feature / BC break: Remove `setTimeout()`, use `TimeoutConnector` instead
|
||||
(#45 by @clue)
|
||||
|
||||
```php
|
||||
// old (timeout only applies to TCP/IP connection)
|
||||
$client = new Client('127.0.0.1', …);
|
||||
$client->setTimeout(3.0);
|
||||
$tcp = $client->createConnector();
|
||||
$promise = $tcp->create('google.com', 80);
|
||||
|
||||
// new (timeout can be added to any layer)
|
||||
$client = new Client('127.0.0.1', …);
|
||||
$tcp = new React\SocketClient\TimeoutConnector($client, 3.0, $loop);
|
||||
$promise = $tcp->create('google.com', 80);
|
||||
```
|
||||
|
||||
* Feature / BC break: Remove `setProtocolVersion()` and `setAuth()` mutators, only support SOCKS URI for protocol version and authentication (immutable API)
|
||||
(#46 by @clue)
|
||||
|
||||
```php
|
||||
// old (state can be mutated after instantiation)
|
||||
$client = new Client('127.0.0.1', …);
|
||||
$client->setProtocolVersion('5');
|
||||
$client->setAuth('user', 'pass');
|
||||
|
||||
// new (immutable after construction, already supported as of v0.5.2 - now mandatory)
|
||||
$client = new Client('socks5://user:pass@127.0.0.1', …);
|
||||
```
|
||||
|
||||
## 0.5.2 (2016-11-25)
|
||||
|
||||
* Feature: Apply protocol version and username/password auth from SOCKS URI
|
||||
(#43 by @clue)
|
||||
|
||||
```php
|
||||
// explicitly use SOCKS5
|
||||
$client = new Client('socks5://127.0.0.1', $loop);
|
||||
|
||||
// use authentication (automatically SOCKS5)
|
||||
$client = new Client('user:pass@127.0.0.1', $loop);
|
||||
```
|
||||
|
||||
* More explicit client examples, including proxy chaining
|
||||
(#42 by @clue)
|
||||
|
||||
## 0.5.1 (2016-11-21)
|
||||
|
||||
* Feature: Support Promise cancellation
|
||||
(#39 by @clue)
|
||||
|
||||
```php
|
||||
$promise = $connector->create($host, $port);
|
||||
|
||||
$promise->cancel();
|
||||
```
|
||||
|
||||
* Feature: Timeout now cancels pending connection attempt
|
||||
(#39, #22 by @clue)
|
||||
|
||||
## 0.5.0 (2016-11-07)
|
||||
|
||||
* Remove / BC break: Split off Server to clue/socks-server
|
||||
(#35 by @clue)
|
||||
|
||||
> Upgrading? Check [clue/socks-server](https://github.com/clue/php-socks-server) for details.
|
||||
|
||||
* Improve documentation and project structure
|
||||
|
||||
## 0.4.0 (2016-03-19)
|
||||
|
||||
* Feature: Support proper SSL/TLS connections with additional SSL context options
|
||||
(#31, #33 by @clue)
|
||||
|
||||
* Documentation for advanced Connector setups (bindto, multihop)
|
||||
(#32 by @clue)
|
||||
|
||||
## 0.3.0 (2015-06-20)
|
||||
|
||||
* BC break / Feature: Client ctor now accepts a SOCKS server URI
|
||||
([#24](https://github.com/clue/php-socks-react/pull/24))
|
||||
|
||||
```php
|
||||
// old
|
||||
$client = new Client($loop, 'localhost', 9050);
|
||||
|
||||
// new
|
||||
$client = new Client('localhost:9050', $loop);
|
||||
```
|
||||
|
||||
* Feature: Automatically assume default SOCKS port (1080) if not given explicitly
|
||||
([#26](https://github.com/clue/php-socks-react/pull/26))
|
||||
|
||||
* Improve documentation and test suite
|
||||
|
||||
## 0.2.1 (2014-11-13)
|
||||
|
||||
* Support React PHP v0.4 (while preserving BC with React PHP v0.3)
|
||||
([#16](https://github.com/clue/php-socks-react/pull/16))
|
||||
|
||||
* Improve examples and add first class support for HHVM
|
||||
([#15](https://github.com/clue/php-socks-react/pull/15) and [#17](https://github.com/clue/php-socks-react/pull/17))
|
||||
|
||||
## 0.2.0 (2014-09-27)
|
||||
|
||||
* BC break / Feature: Simplify constructors by making parameters optional.
|
||||
([#10](https://github.com/clue/php-socks-react/pull/10))
|
||||
|
||||
The `Factory` has been removed, you can now create instances of the `Client`
|
||||
and `Server` yourself:
|
||||
|
||||
```php
|
||||
// old
|
||||
$factory = new Factory($loop, $dns);
|
||||
$client = $factory->createClient('localhost', 9050);
|
||||
$server = $factory->createSever($socket);
|
||||
|
||||
// new
|
||||
$client = new Client($loop, 'localhost', 9050);
|
||||
$server = new Server($loop, $socket);
|
||||
```
|
||||
|
||||
* BC break: Remove HTTP support and link to [clue/buzz-react](https://github.com/clue/php-buzz-react) instead.
|
||||
([#9](https://github.com/clue/php-socks-react/pull/9))
|
||||
|
||||
HTTP operates on a different layer than this low-level SOCKS library.
|
||||
Removing this reduces the footprint of this library.
|
||||
|
||||
> Upgrading? Check the [README](https://github.com/clue/php-socks-react#http-requests) for details.
|
||||
|
||||
* Fix: Refactored to support other, faster loops (libev/libevent)
|
||||
([#12](https://github.com/clue/php-socks-react/pull/12))
|
||||
|
||||
* Explicitly list dependencies, clean up examples and extend test suite significantly
|
||||
|
||||
## 0.1.0 (2014-05-19)
|
||||
|
||||
* First stable release
|
||||
* Async SOCKS `Client` and `Server` implementation
|
||||
* Project was originally part of [clue/socks](https://github.com/clue/php-socks)
|
||||
and was split off from its latest releave v0.4.0
|
||||
([#1](https://github.com/clue/reactphp-socks/issues/1))
|
||||
|
||||
> Upgrading from clue/socks v0.4.0? Use namespace `Clue\React\Socks` instead of `Socks` and you're ready to go!
|
||||
|
||||
## 0.0.0 (2011-04-26)
|
||||
|
||||
* Initial concept, originally tracked as part of
|
||||
[clue/socks](https://github.com/clue/php-socks)
|
||||
21
vendor/clue/socks-react/LICENSE
vendored
Executable file
21
vendor/clue/socks-react/LICENSE
vendored
Executable 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.
|
||||
1017
vendor/clue/socks-react/README.md
vendored
Executable file
1017
vendor/clue/socks-react/README.md
vendored
Executable file
File diff suppressed because it is too large
Load Diff
28
vendor/clue/socks-react/composer.json
vendored
Executable file
28
vendor/clue/socks-react/composer.json
vendored
Executable file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "clue/socks-react",
|
||||
"description": "Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of ReactPHP",
|
||||
"keywords": ["socks client", "socks server", "proxy", "tcp tunnel", "socks protocol", "async", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/php-socks-react",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {"Clue\\React\\Socks\\": "src/"}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/socket": "^1.0 || ^0.8.6",
|
||||
"react/promise": "^2.1 || ^1.2",
|
||||
"evenement/evenement": "~3.0|~1.0|~2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.0 || ^5.7 || ^4.8.35",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
|
||||
"clue/connection-manager-extra": "^1.0 || ^0.7",
|
||||
"clue/block-react": "^1.1"
|
||||
}
|
||||
}
|
||||
30
vendor/clue/socks-react/examples/01-http.php
vendored
Executable file
30
vendor/clue/socks-react/examples/01-http.php
vendored
Executable file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$proxy = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$client = new Client($proxy, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $client,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
|
||||
|
||||
$connector->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
|
||||
echo 'connected' . PHP_EOL;
|
||||
$stream->write("GET / HTTP/1.0\r\n\r\n");
|
||||
$stream->on('data', function ($data) {
|
||||
echo $data;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
30
vendor/clue/socks-react/examples/02-https.php
vendored
Executable file
30
vendor/clue/socks-react/examples/02-https.php
vendored
Executable file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$proxy = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$client = new Client($proxy, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $client,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
|
||||
|
||||
$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
echo 'connected' . PHP_EOL;
|
||||
$stream->write("GET / HTTP/1.0\r\n\r\n");
|
||||
$stream->on('data', function ($data) {
|
||||
echo $data;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
46
vendor/clue/socks-react/examples/03-proxy-chaining.php
vendored
Executable file
46
vendor/clue/socks-react/examples/03-proxy-chaining.php
vendored
Executable file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (!isset($argv[1])) {
|
||||
echo 'No arguments given! Run with <proxy1> [<proxyN>...]' . PHP_EOL;
|
||||
echo 'You can add 1..n proxies in the path' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$path = array_slice($argv, 1);
|
||||
|
||||
// Alternatively, you can also hard-code this value like this:
|
||||
//$path = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// set next SOCKS server chain via p1 -> p2 -> p3 -> destination
|
||||
$connector = new Connector($loop);
|
||||
foreach ($path as $proxy) {
|
||||
$connector = new Client($proxy, $connector);
|
||||
}
|
||||
|
||||
// please note how the client uses p3 (not p1!), which in turn then uses the complete chain
|
||||
// this creates a TCP/IP connection to p1, which then connects to p2, then to p3, which then connects to the target
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $connector,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
echo 'Demo SOCKS client connecting to SOCKS proxy server chain ' . implode(' -> ', $path) . PHP_EOL;
|
||||
|
||||
$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
echo 'connected' . PHP_EOL;
|
||||
$stream->write("GET / HTTP/1.0\r\n\r\n");
|
||||
$stream->on('data', function ($data) {
|
||||
echo $data;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
31
vendor/clue/socks-react/examples/04-local-dns.php
vendored
Executable file
31
vendor/clue/socks-react/examples/04-local-dns.php
vendored
Executable file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$proxy = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// set up DNS server to use (Google's public DNS)
|
||||
$client = new Client($proxy, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $client,
|
||||
'timeout' => 3.0,
|
||||
'dns' => '8.8.8.8'
|
||||
));
|
||||
|
||||
echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
|
||||
|
||||
$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
echo 'connected' . PHP_EOL;
|
||||
$stream->write("GET / HTTP/1.0\r\n\r\n");
|
||||
$stream->on('data', function ($data) {
|
||||
echo $data;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
19
vendor/clue/socks-react/examples/11-server.php
vendored
Executable file
19
vendor/clue/socks-react/examples/11-server.php
vendored
Executable file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Server;
|
||||
use React\Socket\Server as Socket;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// listen on 127.0.0.1:1080 or first argument
|
||||
$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
$socket = new Socket($listen, $loop);
|
||||
|
||||
// start a new server listening for incoming connection on the given socket
|
||||
$server = new Server($loop, $socket);
|
||||
|
||||
echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
24
vendor/clue/socks-react/examples/12-server-with-password.php
vendored
Executable file
24
vendor/clue/socks-react/examples/12-server-with-password.php
vendored
Executable file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Server;
|
||||
use React\Socket\Server as Socket;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// listen on 127.0.0.1:1080 or first argument
|
||||
$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
$socket = new Socket($listen, $loop);
|
||||
|
||||
// start a new server listening for incoming connection on the given socket
|
||||
// require authentication and hence make this a SOCKS5-only server
|
||||
$server = new Server($loop, $socket);
|
||||
$server->setAuthArray(array(
|
||||
'tom' => 'god',
|
||||
'user' => 'p@ssw0rd'
|
||||
));
|
||||
|
||||
echo 'SOCKS5 server requiring authentication listening on ' . $socket->getAddress() . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
40
vendor/clue/socks-react/examples/13-server-blacklist.php
vendored
Executable file
40
vendor/clue/socks-react/examples/13-server-blacklist.php
vendored
Executable file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
// A SOCKS server that rejects connections to some domains (blacklist / filtering)
|
||||
|
||||
use React\EventLoop\Factory as LoopFactory;
|
||||
use ConnectionManager\Extra\Multiple\ConnectionManagerSelective;
|
||||
use React\Socket\Server as Socket;
|
||||
use Clue\React\Socks\Server;
|
||||
use ConnectionManager\Extra\ConnectionManagerReject;
|
||||
use React\Socket\Connector;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = LoopFactory::create();
|
||||
|
||||
// create a connector that rejects the connection
|
||||
$reject = new ConnectionManagerReject();
|
||||
|
||||
// create an actual connector that establishes real connections
|
||||
$permit = new Connector($loop);
|
||||
|
||||
// this connector selectively picks one of the the attached connectors depending on the target address
|
||||
// reject youtube.com and unencrypted HTTP for google.com
|
||||
// default connctor: permit everything
|
||||
$connector = new ConnectionManagerSelective(array(
|
||||
'*.youtube.com' => $reject,
|
||||
'www.google.com:80' => $reject,
|
||||
'*' => $permit
|
||||
));
|
||||
|
||||
// listen on 127.0.0.1:1080 or first argument
|
||||
$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
$socket = new Socket($listen, $loop);
|
||||
|
||||
// start the actual socks server on the given server socket and using our connection manager for outgoing connections
|
||||
$server = new Server($loop, $socket, $connector);
|
||||
|
||||
echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
42
vendor/clue/socks-react/examples/21-server-proxy-chaining.php
vendored
Executable file
42
vendor/clue/socks-react/examples/21-server-proxy-chaining.php
vendored
Executable file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
// A SOCKS server that forwards (proxy chaining) to other SOCKS servers
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use Clue\React\Socks\Server;
|
||||
use React\Socket\Server as Socket;
|
||||
use React\Socket\Connector;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (!isset($argv[2])) {
|
||||
echo 'No arguments given! Run with <listen> <proxy1> [<proxyN>...]' . PHP_EOL;
|
||||
echo 'You can add 1..n proxies in the path' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$listen = $argv[1];
|
||||
$path = array_slice($argv, 2);
|
||||
|
||||
// Alternatively, you can also hard-code these values like this:
|
||||
//$listen = '127.0.0.1:9050';
|
||||
//$path = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// set next SOCKS server chain -> p1 -> p2 -> p3 -> destination
|
||||
$connector = new Connector($loop);
|
||||
foreach ($path as $proxy) {
|
||||
$connector = new Client($proxy, $connector);
|
||||
}
|
||||
|
||||
// listen on 127.0.0.1:1080 or first argument
|
||||
$socket = new Socket($listen, $loop);
|
||||
|
||||
// start a new server which forwards all connections to the other SOCKS server
|
||||
$server = new Server($loop, $socket, $connector);
|
||||
|
||||
echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
|
||||
echo 'Forwarding via: ' . implode(' -> ', $path) . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
46
vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php
vendored
Executable file
46
vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php
vendored
Executable file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
// A SOCKS server that randomly forwards (proxy chaining) to a pool of SOCKS servers
|
||||
|
||||
use React\EventLoop\Factory as LoopFactory;
|
||||
use ConnectionManager\Extra\Multiple\ConnectionManagerRandom;
|
||||
use React\Socket\Server as Socket;
|
||||
use Clue\React\Socks\Server;
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (!isset($argv[3])) {
|
||||
echo 'No arguments given! Run with <listen> <proxy1> <proxyN>...' . PHP_EOL;
|
||||
echo 'You can add 2..n proxies in the pool' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$listen = $argv[1];
|
||||
$pool = array_slice($argv, 2);
|
||||
|
||||
// Alternatively, you can also hard-code these values like this:
|
||||
//$listen = '127.0.0.1:9050';
|
||||
//$pool = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
|
||||
|
||||
$loop = LoopFactory::create();
|
||||
|
||||
// forward to socks server listening on 127.0.0.1:9051-9053
|
||||
// this connector randomly picks one of the the attached connectors from the pool
|
||||
$connector = new Connector($loop);
|
||||
$clients = array();
|
||||
foreach ($pool as $proxy) {
|
||||
$clients []= new Client($proxy, $connector);
|
||||
}
|
||||
$connector = new ConnectionManagerRandom($clients);
|
||||
|
||||
$socket = new Socket($listen, $loop);
|
||||
|
||||
// start the actual socks server on the given server socket and using our connection manager for outgoing connections
|
||||
$server = new Server($loop, $socket, $connector);
|
||||
|
||||
echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
|
||||
echo 'Randomly picking from: ' . implode(', ', $pool) . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
21
vendor/clue/socks-react/examples/31-server-secure.php
vendored
Executable file
21
vendor/clue/socks-react/examples/31-server-secure.php
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Server;
|
||||
use React\Socket\Server as Socket;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// listen on tls://127.0.0.1:1080 or first argument
|
||||
$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
$socket = new Socket('tls://' . $listen, $loop, array('tls' => array(
|
||||
'local_cert' => __DIR__ . '/localhost.pem',
|
||||
)));
|
||||
|
||||
// start a new server listening for incoming connection on the given socket
|
||||
$server = new Server($loop, $socket);
|
||||
|
||||
echo 'SOCKS over TLS server listening on ' . str_replace('tls:', 'sockss:', $socket->getAddress()) . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
33
vendor/clue/socks-react/examples/32-http-secure.php
vendored
Executable file
33
vendor/clue/socks-react/examples/32-http-secure.php
vendored
Executable file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$proxy = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$client = new Client('sockss://' . $proxy, new Connector($loop, array('tls' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false
|
||||
))));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $client,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
echo 'Demo SOCKS over TLS client connecting to secure SOCKS server ' . $proxy . PHP_EOL;
|
||||
|
||||
$connector->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
|
||||
echo 'connected' . PHP_EOL;
|
||||
$stream->write("GET / HTTP/1.0\r\n\r\n");
|
||||
$stream->on('data', function ($data) {
|
||||
echo $data;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
49
vendor/clue/socks-react/examples/localhost.pem
vendored
Executable file
49
vendor/clue/socks-react/examples/localhost.pem
vendored
Executable file
@@ -0,0 +1,49 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
|
||||
MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
|
||||
DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
|
||||
MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
|
||||
EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
|
||||
eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
|
||||
W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
|
||||
2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
|
||||
zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
|
||||
UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
|
||||
wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
|
||||
YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
|
||||
BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
|
||||
CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
|
||||
0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
|
||||
7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
|
||||
824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
|
||||
V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
|
||||
IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
|
||||
W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
|
||||
2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
|
||||
zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
|
||||
UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
|
||||
wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
|
||||
YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
|
||||
ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
|
||||
g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
|
||||
tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
|
||||
LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
|
||||
tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
|
||||
9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
|
||||
43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
|
||||
pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
|
||||
OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
|
||||
2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
|
||||
li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
|
||||
b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
|
||||
vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
|
||||
XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
|
||||
Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
|
||||
iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
|
||||
+EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
|
||||
y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
|
||||
81oh1uCH1YPLM29hPyaohxL8
|
||||
-----END PRIVATE KEY-----
|
||||
14
vendor/clue/socks-react/phpunit.xml.dist
vendored
Executable file
14
vendor/clue/socks-react/phpunit.xml.dist
vendored
Executable file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit colors="true" bootstrap="./tests/bootstrap.php">
|
||||
<testsuites>
|
||||
<testsuite name="Socks Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
382
vendor/clue/socks-react/src/Client.php
vendored
Executable file
382
vendor/clue/socks-react/src/Client.php
vendored
Executable file
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\FixedUriConnector;
|
||||
use \Exception;
|
||||
use \InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class Client implements ConnectorInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var ConnectorInterface
|
||||
*/
|
||||
private $connector;
|
||||
|
||||
private $socksUri;
|
||||
|
||||
private $protocolVersion = null;
|
||||
|
||||
private $auth = null;
|
||||
|
||||
public function __construct($socksUri, ConnectorInterface $connector)
|
||||
{
|
||||
// support `sockss://` scheme for SOCKS over TLS
|
||||
// support `socks+unix://` scheme for Unix domain socket (UDS) paths
|
||||
if (preg_match('/^(socks(?:5|4|4a)?)(s|\+unix):\/\/(.*?@)?(.+?)$/', $socksUri, $match)) {
|
||||
// rewrite URI to parse SOCKS scheme, authentication and dummy host
|
||||
$socksUri = $match[1] . '://' . $match[3] . 'localhost';
|
||||
|
||||
// connector uses appropriate transport scheme and explicit host given
|
||||
$connector = new FixedUriConnector(
|
||||
($match[2] === 's' ? 'tls://' : 'unix://') . $match[4],
|
||||
$connector
|
||||
);
|
||||
}
|
||||
|
||||
// assume default scheme if none is given
|
||||
if (strpos($socksUri, '://') === false) {
|
||||
$socksUri = 'socks://' . $socksUri;
|
||||
}
|
||||
|
||||
// parse URI into individual parts
|
||||
$parts = parse_url($socksUri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'])) {
|
||||
throw new \InvalidArgumentException('Invalid SOCKS server URI "' . $socksUri . '"');
|
||||
}
|
||||
|
||||
// assume default port
|
||||
if (!isset($parts['port'])) {
|
||||
$parts['port'] = 1080;
|
||||
}
|
||||
|
||||
// user or password in URI => SOCKS5 authentication
|
||||
if (isset($parts['user']) || isset($parts['pass'])) {
|
||||
if ($parts['scheme'] === 'socks') {
|
||||
// default to using SOCKS5 if not given explicitly
|
||||
$parts['scheme'] = 'socks5';
|
||||
} elseif ($parts['scheme'] !== 'socks5') {
|
||||
// fail if any other protocol version given explicitly
|
||||
throw new InvalidArgumentException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
|
||||
}
|
||||
$parts += array('user' => '', 'pass' => '');
|
||||
$this->setAuth(rawurldecode($parts['user']), rawurldecode($parts['pass']));
|
||||
}
|
||||
|
||||
// check for valid protocol version from URI scheme
|
||||
$this->setProtocolVersionFromScheme($parts['scheme']);
|
||||
|
||||
$this->socksUri = $parts['host'] . ':' . $parts['port'];
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
private function setProtocolVersionFromScheme($scheme)
|
||||
{
|
||||
if ($scheme === 'socks' || $scheme === 'socks4a') {
|
||||
$this->protocolVersion = '4a';
|
||||
} elseif ($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 React'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 ($this->protocolVersion === '4' && false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
return Promise\reject(new InvalidArgumentException('Requires an IPv4 address for SOCKS4'));
|
||||
}
|
||||
|
||||
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'];
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
|
||||
// start TCP/IP connection to SOCKS server and then
|
||||
// handle SOCKS protocol once connection is ready
|
||||
// resolve plain connection once SOCKS protocol is completed
|
||||
return $this->connector->connect($socksUri)->then(
|
||||
function (ConnectionInterface $stream) use ($that, $host, $port) {
|
||||
return $that->handleConnectedSocks($stream, $host, $port);
|
||||
},
|
||||
function (Exception $e) {
|
||||
throw new RuntimeException('Unable to connect to proxy (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, $e);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper used to handle the communication with the SOCKS server
|
||||
*
|
||||
* @param ConnectionInterface $stream
|
||||
* @param string $host
|
||||
* @param int $port
|
||||
* @return Promise Promise<ConnectionInterface, Exception>
|
||||
* @internal
|
||||
*/
|
||||
public function handleConnectedSocks(ConnectionInterface $stream, $host, $port)
|
||||
{
|
||||
$deferred = new Deferred(function ($_, $reject) {
|
||||
$reject(new RuntimeException('Connection canceled while establishing SOCKS session (ECONNABORTED)', defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103));
|
||||
});
|
||||
|
||||
$reader = new StreamReader();
|
||||
$stream->on('data', array($reader, 'write'));
|
||||
|
||||
$stream->on('error', $onError = function (Exception $e) use ($deferred) {
|
||||
$deferred->reject(new RuntimeException('Stream error while waiting for response from proxy (EIO)', defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e));
|
||||
});
|
||||
|
||||
$stream->on('close', $onClose = function () use ($deferred) {
|
||||
$deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
|
||||
});
|
||||
|
||||
if ($this->protocolVersion === '5') {
|
||||
$promise = $this->handleSocks5($stream, $host, $port, $reader);
|
||||
} else {
|
||||
$promise = $this->handleSocks4($stream, $host, $port, $reader);
|
||||
}
|
||||
$promise->then(function () use ($deferred, $stream) {
|
||||
$deferred->resolve($stream);
|
||||
}, function (Exception $error) use ($deferred) {
|
||||
// pass custom RuntimeException through as-is, otherwise wrap in protocol error
|
||||
if (!$error instanceof RuntimeException) {
|
||||
$error = new RuntimeException('Invalid response received from proxy (EBADMSG)', defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71, $error);
|
||||
}
|
||||
|
||||
$deferred->reject($error);
|
||||
});
|
||||
|
||||
return $deferred->promise()->then(
|
||||
function (ConnectionInterface $stream) use ($reader, $onError, $onClose) {
|
||||
$stream->removeListener('data', array($reader, 'write'));
|
||||
$stream->removeListener('error', $onError);
|
||||
$stream->removeListener('close', $onClose);
|
||||
|
||||
return $stream;
|
||||
},
|
||||
function ($error) use ($stream, $onClose) {
|
||||
$stream->removeListener('close', $onClose);
|
||||
$stream->close();
|
||||
|
||||
throw $error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function handleSocks4(ConnectionInterface $stream, $host, $port, StreamReader $reader)
|
||||
{
|
||||
// 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) {
|
||||
if ($data['null'] !== 0x00) {
|
||||
throw new Exception('Invalid SOCKS response');
|
||||
}
|
||||
if ($data['status'] !== 0x5a) {
|
||||
throw new RuntimeException('Proxy refused connection with SOCKS error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamReader $reader)
|
||||
{
|
||||
// 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) {
|
||||
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) {
|
||||
if ($data['version'] !== 0x01 || $data['status'] !== 0x00) {
|
||||
throw new RuntimeException('Username/Password authentication failed (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
|
||||
}
|
||||
});
|
||||
} else if ($data['method'] !== 0x00) {
|
||||
// any other method than "no authentication"
|
||||
throw new RuntimeException('No acceptable authentication method found (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) {
|
||||
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('SOCKS server reported a general server failure (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
|
||||
} elseif ($data['status'] === Server::ERROR_NOT_ALLOWED_BY_RULESET) {
|
||||
throw new RuntimeException('SOCKS server reported connection is not allowed by ruleset (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
|
||||
} elseif ($data['status'] === Server::ERROR_NETWORK_UNREACHABLE) {
|
||||
throw new RuntimeException('SOCKS server reported network unreachable (ENETUNREACH)', defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101);
|
||||
} elseif ($data['status'] === Server::ERROR_HOST_UNREACHABLE) {
|
||||
throw new RuntimeException('SOCKS server reported host unreachable (EHOSTUNREACH)', defined('SOCKET_EHOSTUNREACH') ? SOCKET_EHOSTUNREACH : 113);
|
||||
} elseif ($data['status'] === Server::ERROR_CONNECTION_REFUSED) {
|
||||
throw new RuntimeException('SOCKS server reported connection refused (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
|
||||
} elseif ($data['status'] === Server::ERROR_TTL) {
|
||||
throw new RuntimeException('SOCKS server reported TTL/timeout expired (ETIMEDOUT)', defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110);
|
||||
} elseif ($data['status'] === Server::ERROR_COMMAND_UNSUPPORTED) {
|
||||
throw new RuntimeException('SOCKS server does not support the CONNECT command (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
|
||||
} elseif ($data['status'] === Server::ERROR_ADDRESS_UNSUPPORTED) {
|
||||
throw new RuntimeException('SOCKS server does not support this address type (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
|
||||
}
|
||||
|
||||
throw new RuntimeException('SOCKS server reported an unassigned 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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
422
vendor/clue/socks-react/src/Server.php
vendored
Executable file
422
vendor/clue/socks-react/src/Server.php
vendored
Executable file
@@ -0,0 +1,422 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Socket\ServerInterface;
|
||||
use React\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use \UnexpectedValueException;
|
||||
use \InvalidArgumentException;
|
||||
use \Exception;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
|
||||
class Server extends EventEmitter
|
||||
{
|
||||
// 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;
|
||||
|
||||
protected $loop;
|
||||
|
||||
private $connector;
|
||||
|
||||
private $auth = null;
|
||||
|
||||
private $protocolVersion = null;
|
||||
|
||||
public function __construct(LoopInterface $loop, ServerInterface $serverInterface, ConnectorInterface $connector = null)
|
||||
{
|
||||
if ($connector === null) {
|
||||
$connector = new Connector($loop);
|
||||
}
|
||||
|
||||
$this->loop = $loop;
|
||||
$this->connector = $connector;
|
||||
|
||||
$that = $this;
|
||||
$serverInterface->on('connection', function ($connection) use ($that) {
|
||||
$that->emit('connection', array($connection));
|
||||
$that->onConnection($connection);
|
||||
});
|
||||
}
|
||||
|
||||
public function setProtocolVersion($version)
|
||||
{
|
||||
if ($version !== null) {
|
||||
$version = (string)$version;
|
||||
if (!in_array($version, array('4', '4a', '5'), true)) {
|
||||
throw new InvalidArgumentException('Invalid protocol version given');
|
||||
}
|
||||
if ($version !== '5' && $this->auth !== null){
|
||||
throw new UnexpectedValueException('Unable to change protocol version to anything but SOCKS5 while authentication is used. Consider removing authentication info or sticking to SOCKS5');
|
||||
}
|
||||
}
|
||||
$this->protocolVersion = $version;
|
||||
}
|
||||
|
||||
public function setAuth($auth)
|
||||
{
|
||||
if (!is_callable($auth)) {
|
||||
throw new InvalidArgumentException('Given authenticator is not a valid callable');
|
||||
}
|
||||
if ($this->protocolVersion !== null && $this->protocolVersion !== '5') {
|
||||
throw new UnexpectedValueException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
|
||||
}
|
||||
// wrap authentication callback in order to cast its return value to a promise
|
||||
$this->auth = function($username, $password, $remote) use ($auth) {
|
||||
$ret = call_user_func($auth, $username, $password, $remote);
|
||||
if ($ret instanceof PromiseInterface) {
|
||||
return $ret;
|
||||
}
|
||||
$deferred = new Deferred();
|
||||
$ret ? $deferred->resolve() : $deferred->reject();
|
||||
return $deferred->promise();
|
||||
};
|
||||
}
|
||||
|
||||
public function setAuthArray(array $login)
|
||||
{
|
||||
$this->setAuth(function ($username, $password) use ($login) {
|
||||
return (isset($login[$username]) && (string)$login[$username] === $password);
|
||||
});
|
||||
}
|
||||
|
||||
public function unsetAuth()
|
||||
{
|
||||
$this->auth = null;
|
||||
}
|
||||
|
||||
public function onConnection(ConnectionInterface $connection)
|
||||
{
|
||||
$that = $this;
|
||||
$handling = $this->handleSocks($connection)->then(function($remote) use ($connection){
|
||||
$connection->emit('ready',array($remote));
|
||||
}, function ($error) use ($connection, $that) {
|
||||
if (!($error instanceof \Exception)) {
|
||||
$error = new \Exception($error);
|
||||
}
|
||||
$connection->emit('error', array($error));
|
||||
$that->endConnection($connection);
|
||||
});
|
||||
|
||||
$connection->on('close', function () use ($handling) {
|
||||
$handling->cancel();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* gracefully shutdown connection by flushing all remaining data and closing stream
|
||||
*/
|
||||
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;
|
||||
$that = $this;
|
||||
|
||||
$auth = $this->auth;
|
||||
$protocolVersion = $this->protocolVersion;
|
||||
|
||||
// authentication requires SOCKS5
|
||||
if ($auth !== null) {
|
||||
$protocolVersion = '5';
|
||||
}
|
||||
|
||||
return $reader->readByte()->then(function ($version) use ($stream, $that, $protocolVersion, $auth, $reader){
|
||||
if ($version === 0x04) {
|
||||
if ($protocolVersion === '5') {
|
||||
throw new UnexpectedValueException('SOCKS4 not allowed due to configuration');
|
||||
}
|
||||
return $that->handleSocks4($stream, $protocolVersion, $reader);
|
||||
} else if ($version === 0x05) {
|
||||
if ($protocolVersion !== null && $protocolVersion !== '5') {
|
||||
throw new UnexpectedValueException('SOCKS5 not allowed due to configuration');
|
||||
}
|
||||
return $that->handleSocks5($stream, $auth, $reader);
|
||||
}
|
||||
throw new UnexpectedValueException('Unexpected/unknown version number');
|
||||
});
|
||||
}
|
||||
|
||||
public function handleSocks4(ConnectionInterface $stream, $protocolVersion, StreamReader $reader)
|
||||
{
|
||||
// suppliying hostnames is only allowed for SOCKS4a (or automatically detected version)
|
||||
$supportsHostname = ($protocolVersion === null || $protocolVersion === '4a');
|
||||
|
||||
$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, $supportsHostname, $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 && $supportsHostname) {
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
public function handleSocks5(ConnectionInterface $stream, $auth=null, 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 = 'socks5' . ($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 () use ($stream, $username) {
|
||||
// accept
|
||||
$stream->emit('auth', array($username));
|
||||
$stream->write(pack('C2', 0x01, 0x00));
|
||||
}, function() use ($stream) {
|
||||
// reject => send any code but 0x00
|
||||
$stream->end(pack('C2', 0x01, 0xFF));
|
||||
throw new UnexpectedValueException('Unable to authenticate');
|
||||
});
|
||||
});
|
||||
});
|
||||
} 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, $stream) {
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
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 Promise\reject(new InvalidArgumentException('Invalid target URI given'));
|
||||
}
|
||||
|
||||
if (isset($target[2])) {
|
||||
$uri .= '?source=' . rawurlencode($target[2]);
|
||||
}
|
||||
|
||||
$stream->emit('target', $target);
|
||||
$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) {
|
||||
$stream->emit('shutdown', array('remote', null));
|
||||
$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
Executable file
149
vendor/clue/socks-react/src/StreamReader.php
vendored
Executable file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use React\Promise\Deferred;
|
||||
use \InvalidArgumentException;
|
||||
use \UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
403
vendor/clue/socks-react/tests/ClientTest.php
vendored
Executable file
403
vendor/clue/socks-react/tests/ClientTest.php
vendored
Executable file
@@ -0,0 +1,403 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Promise\Promise;
|
||||
use Clue\React\Socks\Server;
|
||||
|
||||
class ClientTest extends TestCase
|
||||
{
|
||||
private $loop;
|
||||
|
||||
private $connector;
|
||||
|
||||
/** @var Client */
|
||||
private $client;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = React\EventLoop\Factory::create();
|
||||
$this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
|
||||
$this->client = new Client('127.0.0.1:1080', $this->connector);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithHostAndPort()
|
||||
{
|
||||
$client = new Client('127.0.0.1:9050', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithScheme()
|
||||
{
|
||||
$client = new Client('socks://127.0.0.1:9050', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithHostOnlyAssumesDefaultPort()
|
||||
{
|
||||
$client = new Client('127.0.0.1', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithSecureScheme()
|
||||
{
|
||||
$client = new Client('sockss://127.0.0.1:9050', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithSecureVersionScheme()
|
||||
{
|
||||
$client = new Client('socks5s://127.0.0.1:9050', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithSocksUnixScheme()
|
||||
{
|
||||
$client = new Client('socks+unix:///tmp/socks.socket', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithSocks5UnixScheme()
|
||||
{
|
||||
$client = new Client('socks5+unix:///tmp/socks.socket', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testCtorThrowsForInvalidUri()
|
||||
{
|
||||
new Client('////', $this->connector);
|
||||
}
|
||||
|
||||
public function testValidAuthFromUri()
|
||||
{
|
||||
$this->client = new Client('username:password@127.0.0.1', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidAuthInformation()
|
||||
{
|
||||
new Client(str_repeat('a', 256) . ':test@127.0.0.1', $this->connector);
|
||||
}
|
||||
|
||||
public function testValidAuthAndVersionFromUri()
|
||||
{
|
||||
$this->client = new Client('socks5://username:password@127.0.0.1:9050', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidCanNotSetAuthenticationForSocks4Uri()
|
||||
{
|
||||
$this->client = new Client('socks4://username:password@127.0.0.1:9050', $this->connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidProtocolVersion()
|
||||
{
|
||||
$this->client = new Client('socks3://127.0.0.1:9050', $this->connector);
|
||||
}
|
||||
|
||||
public function testCreateWillConnectToProxy()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=localhost')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('localhost:80');
|
||||
|
||||
$this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testCreateWillConnectToProxyWithFullUri()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080/?hostname=test#fragment')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('localhost:80/?hostname=test#fragment');
|
||||
|
||||
$this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testCreateWithInvalidHostDoesNotConnect()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$promise = $this->client->connect(str_repeat('a', '256') . ':80');
|
||||
|
||||
$this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testCreateWithInvalidPortDoesNotConnect()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$promise = $this->client->connect('some-random-site:some-random-port');
|
||||
|
||||
$this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testConnectorRejectsWillRejectConnection()
|
||||
{
|
||||
$promise = \React\Promise\reject(new RuntimeException());
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
|
||||
}
|
||||
|
||||
public function testCancelConnectionDuringConnectionWillCancelConnection()
|
||||
{
|
||||
$promise = new Promise(function () { }, function () {
|
||||
throw new \RuntimeException();
|
||||
});
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
$promise->cancel();
|
||||
|
||||
$this->expectPromiseReject($promise);
|
||||
}
|
||||
|
||||
public function testCancelConnectionDuringSessionWillCloseStream()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = new Promise(function ($resolve) use ($stream) { $resolve($stream); });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
$promise->cancel();
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNABORTED));
|
||||
}
|
||||
|
||||
public function testEmitConnectionCloseDuringSessionWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('close');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNRESET));
|
||||
}
|
||||
|
||||
public function testEmitConnectionErrorDuringSessionWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('error', array(new RuntimeException()));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EIO));
|
||||
}
|
||||
|
||||
public function testEmitInvalidSocks4DataDuringSessionWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("HTTP/1.1 400 Bad Request\r\n\r\n"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
|
||||
}
|
||||
|
||||
public function testEmitInvalidSocks5DataDuringSessionWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("HTTP/1.1 400 Bad Request\r\n\r\n"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
|
||||
}
|
||||
|
||||
public function testEmitSocks5DataErrorDuringSessionWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("\x05\x00" . "\x05\x01\x00\x00"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
|
||||
}
|
||||
|
||||
public function testEmitSocks5DataInvalidAddressTypeWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x00"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
|
||||
}
|
||||
|
||||
public function testEmitSocks5DataIpv6AddressWillResolveConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->never())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=%3A%3A1')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('[::1]:80');
|
||||
|
||||
$stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x04" . inet_pton('::1') . "\x00\x50"));
|
||||
|
||||
$promise->then($this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testEmitSocks5DataHostnameAddressWillResolveConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->never())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x03\x0Agoogle.com\x00\x50"));
|
||||
|
||||
$promise->then($this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function provideConnectionErrors()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
Server::ERROR_GENERAL,
|
||||
SOCKET_ECONNREFUSED
|
||||
),
|
||||
array(
|
||||
Server::ERROR_NOT_ALLOWED_BY_RULESET,
|
||||
SOCKET_EACCES
|
||||
),
|
||||
array(
|
||||
Server::ERROR_NETWORK_UNREACHABLE,
|
||||
SOCKET_ENETUNREACH
|
||||
),
|
||||
array(
|
||||
Server::ERROR_HOST_UNREACHABLE,
|
||||
SOCKET_EHOSTUNREACH
|
||||
),
|
||||
array(
|
||||
Server::ERROR_CONNECTION_REFUSED,
|
||||
SOCKET_ECONNREFUSED
|
||||
),
|
||||
array(
|
||||
Server::ERROR_TTL,
|
||||
SOCKET_ETIMEDOUT
|
||||
),
|
||||
array(
|
||||
Server::ERROR_COMMAND_UNSUPPORTED,
|
||||
SOCKET_EPROTO
|
||||
),
|
||||
array(
|
||||
Server::ERROR_ADDRESS_UNSUPPORTED,
|
||||
SOCKET_EPROTO
|
||||
),
|
||||
array(
|
||||
200,
|
||||
SOCKET_ECONNREFUSED
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideConnectionErrors
|
||||
* @param int $error
|
||||
* @param int $expectedCode
|
||||
*/
|
||||
public function testEmitSocks5DataErrorMapsToExceptionCode($error, $expectedCode)
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("\x05\x00" . "\x05" . chr($error) . "\x00\x00"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode($expectedCode));
|
||||
}
|
||||
}
|
||||
437
vendor/clue/socks-react/tests/FunctionalTest.php
vendored
Executable file
437
vendor/clue/socks-react/tests/FunctionalTest.php
vendored
Executable file
@@ -0,0 +1,437 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use Clue\React\Socks\Server;
|
||||
use Clue\React\Block;
|
||||
use React\Socket\TimeoutConnector;
|
||||
use React\Socket\SecureConnector;
|
||||
use React\Socket\TcpConnector;
|
||||
use React\Socket\UnixServer;
|
||||
use React\Socket\Connector;
|
||||
|
||||
class FunctionalTest extends TestCase
|
||||
{
|
||||
private $loop;
|
||||
private $connector;
|
||||
private $client;
|
||||
|
||||
private $port;
|
||||
private $server;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = React\EventLoop\Factory::create();
|
||||
|
||||
$socket = new React\Socket\Server(0, $this->loop);
|
||||
$address = $socket->getAddress();
|
||||
if (strpos($address, '://') === false) {
|
||||
$address = 'tcp://' . $address;
|
||||
}
|
||||
$this->port = parse_url($address, PHP_URL_PORT);
|
||||
$this->assertNotEquals(0, $this->port);
|
||||
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
$this->connector = new TcpConnector($this->loop);
|
||||
$this->client = new Client('127.0.0.1:' . $this->port, $this->connector);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnection()
|
||||
{
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionInvalid()
|
||||
{
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com.invalid:80'));
|
||||
}
|
||||
|
||||
public function testConnectionWithIpViaSocks4()
|
||||
{
|
||||
$this->server->setProtocolVersion('4');
|
||||
|
||||
$this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('127.0.0.1:' . $this->port));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionWithHostnameViaSocks4Fails()
|
||||
{
|
||||
$this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionWithInvalidPortFails()
|
||||
{
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:100000'));
|
||||
}
|
||||
|
||||
public function testConnectionWithIpv6ViaSocks4Fails()
|
||||
{
|
||||
$this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('[::1]:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocks4a()
|
||||
{
|
||||
$this->server->setProtocolVersion('4a');
|
||||
$this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocks5()
|
||||
{
|
||||
$this->server->setProtocolVersion(5);
|
||||
$this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocksOverTls()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
|
||||
'local_cert' => __DIR__ . '/../examples/localhost.pem',
|
||||
)));
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
|
||||
$this->connector = new Connector($this->loop, array('tls' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false
|
||||
)));
|
||||
$this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group internet
|
||||
* @requires PHP 5.6
|
||||
*/
|
||||
public function testConnectionSocksOverTlsUsesPeerNameFromSocksUri()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
|
||||
'local_cert' => __DIR__ . '/../examples/localhost.pem',
|
||||
)));
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
|
||||
$this->connector = new Connector($this->loop, array('tls' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => true
|
||||
)));
|
||||
$this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocksOverUnix()
|
||||
{
|
||||
if (!in_array('unix', stream_get_transports())) {
|
||||
$this->markTestSkipped('System does not support unix:// scheme');
|
||||
}
|
||||
|
||||
$path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
|
||||
$socket = new UnixServer($path, $this->loop);
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
|
||||
$this->connector = new Connector($this->loop);
|
||||
$this->client = new Client('socks+unix://' . $path, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
|
||||
unlink($path);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocks5OverUnix()
|
||||
{
|
||||
if (!in_array('unix', stream_get_transports())) {
|
||||
$this->markTestSkipped('System does not support unix:// scheme');
|
||||
}
|
||||
|
||||
$path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
|
||||
$socket = new UnixServer($path, $this->loop);
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
$this->server->setProtocolVersion(5);
|
||||
|
||||
$this->connector = new Connector($this->loop);
|
||||
$this->client = new Client('socks5+unix://' . $path, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
|
||||
unlink($path);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocksWithAuthenticationOverUnix()
|
||||
{
|
||||
if (!in_array('unix', stream_get_transports())) {
|
||||
$this->markTestSkipped('System does not support unix:// scheme');
|
||||
}
|
||||
|
||||
$path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
|
||||
$socket = new UnixServer($path, $this->loop);
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
$this->server->setAuthArray(array('name' => 'pass'));
|
||||
|
||||
$this->connector = new Connector($this->loop);
|
||||
$this->client = new Client('socks+unix://name:pass@' . $path, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
|
||||
unlink($path);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationFromUri()
|
||||
{
|
||||
$this->server->setAuthArray(array('name' => 'pass'));
|
||||
|
||||
$this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationCallback()
|
||||
{
|
||||
$called = 0;
|
||||
$that = $this;
|
||||
$this->server->setAuth(function ($name, $pass, $remote) use ($that, &$called) {
|
||||
++$called;
|
||||
$that->assertEquals('name', $name);
|
||||
$that->assertEquals('pass', $pass);
|
||||
$that->assertStringStartsWith('socks5://name:pass@127.0.0.1:', $remote);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
$this->assertEquals(1, $called);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationCallbackWillNotBeInvokedIfClientsSendsNoAuth()
|
||||
{
|
||||
$called = 0;
|
||||
$this->server->setAuth(function () use (&$called) {
|
||||
++$called;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$this->client = new Client('127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'));
|
||||
$this->assertEquals(0, $called);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationFromUriEncoded()
|
||||
{
|
||||
$this->server->setAuthArray(array('name' => 'p@ss:w0rd'));
|
||||
|
||||
$this->client = new Client(rawurlencode('name') . ':' . rawurlencode('p@ss:w0rd') . '@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationFromUriWithOnlyUserAndNoPassword()
|
||||
{
|
||||
$this->server->setAuthArray(array('empty' => ''));
|
||||
|
||||
$this->client = new Client('empty@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationEmptyPassword()
|
||||
{
|
||||
$this->server->setAuthArray(array('user' => ''));
|
||||
$this->client = new Client('user@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationUnused()
|
||||
{
|
||||
$this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
public function testConnectionInvalidProtocolDoesNotMatchSocks5()
|
||||
{
|
||||
$this->server->setProtocolVersion(5);
|
||||
$this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
|
||||
}
|
||||
|
||||
public function testConnectionInvalidProtocolDoesNotMatchSocks4()
|
||||
{
|
||||
$this->server->setProtocolVersion(4);
|
||||
$this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
|
||||
}
|
||||
|
||||
public function testConnectionInvalidNoAuthentication()
|
||||
{
|
||||
$this->server->setAuthArray(array('name' => 'pass'));
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
|
||||
}
|
||||
|
||||
public function testConnectionInvalidAuthenticationMismatch()
|
||||
{
|
||||
$this->server->setAuthArray(array('name' => 'pass'));
|
||||
|
||||
$this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectorOkay()
|
||||
{
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectorInvalidDomain()
|
||||
{
|
||||
$this->assertRejectPromise($this->client->connect('www.google.commm:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectorCancelConnection()
|
||||
{
|
||||
$promise = $this->client->connect('www.google.com:80');
|
||||
$promise->cancel();
|
||||
|
||||
$this->assertRejectPromise($promise);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectorInvalidUnboundPortTimeout()
|
||||
{
|
||||
// time out the connection attempt in 0.1s (as expected)
|
||||
$tcp = new TimeoutConnector($this->client, 0.1, $this->loop);
|
||||
|
||||
$this->assertRejectPromise($tcp->connect('www.google.com:8080'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testSecureConnectorOkay()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$ssl = new SecureConnector($this->client, $this->loop);
|
||||
|
||||
$this->assertResolveStream($ssl->connect('www.google.com:443'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testSecureConnectorToBadSslWithVerifyFails()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$ssl = new SecureConnector($this->client, $this->loop, array('verify_peer' => true));
|
||||
|
||||
$this->assertRejectPromise($ssl->connect('self-signed.badssl.com:443'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testSecureConnectorToBadSslWithoutVerifyWorks()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$ssl = new SecureConnector($this->client, $this->loop, array('verify_peer' => false));
|
||||
|
||||
$this->assertResolveStream($ssl->connect('self-signed.badssl.com:443'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testSecureConnectorInvalidPlaintextIsNotSsl()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$ssl = new SecureConnector($this->client, $this->loop);
|
||||
|
||||
$this->assertRejectPromise($ssl->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testSecureConnectorInvalidUnboundPortTimeout()
|
||||
{
|
||||
$ssl = new SecureConnector($this->client, $this->loop);
|
||||
|
||||
// time out the connection attempt in 0.1s (as expected)
|
||||
$ssl = new TimeoutConnector($ssl, 0.1, $this->loop);
|
||||
|
||||
$this->assertRejectPromise($ssl->connect('www.google.com:8080'));
|
||||
}
|
||||
|
||||
private function assertResolveStream($promise)
|
||||
{
|
||||
$this->expectPromiseResolve($promise);
|
||||
|
||||
$promise->then(function ($stream) {
|
||||
$stream->close();
|
||||
});
|
||||
|
||||
Block\await($promise, $this->loop, 2.0);
|
||||
}
|
||||
|
||||
private function assertRejectPromise($promise, $message = null, $code = null)
|
||||
{
|
||||
$this->expectPromiseReject($promise);
|
||||
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException('Exception');
|
||||
if ($message !== null) {
|
||||
$this->expectExceptionMessage($message);
|
||||
}
|
||||
if ($code !== null) {
|
||||
$this->expectExceptionCode($code);
|
||||
}
|
||||
} else {
|
||||
$this->setExpectedException('Exception', $message, $code);
|
||||
}
|
||||
|
||||
Block\await($promise, $this->loop, 2.0);
|
||||
}
|
||||
}
|
||||
428
vendor/clue/socks-react/tests/ServerTest.php
vendored
Executable file
428
vendor/clue/socks-react/tests/ServerTest.php
vendored
Executable file
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Server;
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
|
||||
class ServerTest extends TestCase
|
||||
{
|
||||
/** @var Server */
|
||||
private $server;
|
||||
private $connector;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$socket = $this->getMockBuilder('React\Socket\ServerInterface')
|
||||
->getMock();
|
||||
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')
|
||||
->getMock();
|
||||
|
||||
$this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')
|
||||
->getMock();
|
||||
|
||||
$this->server = new Server($loop, $socket, $this->connector);
|
||||
}
|
||||
|
||||
public function testSetProtocolVersion()
|
||||
{
|
||||
$this->server->setProtocolVersion(4);
|
||||
$this->server->setProtocolVersion('4a');
|
||||
$this->server->setProtocolVersion(5);
|
||||
$this->server->setProtocolVersion(null);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testSetInvalidProtocolVersion()
|
||||
{
|
||||
$this->server->setProtocolVersion(6);
|
||||
}
|
||||
|
||||
public function testSetAuthArray()
|
||||
{
|
||||
$this->server->setAuthArray(array());
|
||||
|
||||
$this->server->setAuthArray(array(
|
||||
'name1' => 'password1',
|
||||
'name2' => 'password2'
|
||||
));
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testSetAuthInvalid()
|
||||
{
|
||||
$this->server->setAuth(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException UnexpectedValueException
|
||||
*/
|
||||
public function testUnableToSetAuthIfProtocolDoesNotSupportAuth()
|
||||
{
|
||||
$this->server->setProtocolVersion(4);
|
||||
|
||||
$this->server->setAuthArray(array());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException UnexpectedValueException
|
||||
*/
|
||||
public function testUnableToSetProtocolWhichDoesNotSupportAuth()
|
||||
{
|
||||
$this->server->setAuthArray(array());
|
||||
|
||||
// this is okay
|
||||
$this->server->setProtocolVersion(5);
|
||||
|
||||
$this->server->setProtocolVersion(4);
|
||||
}
|
||||
|
||||
public function testConnectWillCreateConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80));
|
||||
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testConnectWillCreateConnectionWithSourceUri()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('google.com:80?source=socks5%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80, 'socks5://10.20.30.40:5060'));
|
||||
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testConnectWillRejectIfConnectionFails()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
|
||||
|
||||
$promise = new Promise(function ($_, $reject) { $reject(new \RuntimeException()); });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testConnectWillCancelConnectionIfStreamCloses()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { }, function () {
|
||||
throw new \RuntimeException();
|
||||
});
|
||||
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80));
|
||||
|
||||
$stream->emit('close');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testConnectWillAbortIfPromiseIsCanceled()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
|
||||
|
||||
$promise = new Promise(function () { }, function () {
|
||||
throw new \RuntimeException();
|
||||
});
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80));
|
||||
|
||||
$promise->cancel();
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function provideConnectionErrors()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
new RuntimeException('', SOCKET_EACCES),
|
||||
Server::ERROR_NOT_ALLOWED_BY_RULESET
|
||||
),
|
||||
array(
|
||||
new RuntimeException('', SOCKET_ENETUNREACH),
|
||||
Server::ERROR_NETWORK_UNREACHABLE
|
||||
),
|
||||
array(
|
||||
new RuntimeException('', SOCKET_EHOSTUNREACH),
|
||||
Server::ERROR_HOST_UNREACHABLE,
|
||||
),
|
||||
array(
|
||||
new RuntimeException('', SOCKET_ECONNREFUSED),
|
||||
Server::ERROR_CONNECTION_REFUSED
|
||||
),
|
||||
array(
|
||||
new RuntimeException('Connection refused'),
|
||||
Server::ERROR_CONNECTION_REFUSED
|
||||
),
|
||||
array(
|
||||
new RuntimeException('', SOCKET_ETIMEDOUT),
|
||||
Server::ERROR_TTL
|
||||
),
|
||||
array(
|
||||
new TimeoutException(1.0),
|
||||
Server::ERROR_TTL
|
||||
),
|
||||
array(
|
||||
new RuntimeException(),
|
||||
Server::ERROR_GENERAL
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideConnectionErrors
|
||||
* @param Exception $error
|
||||
* @param int $expectedCode
|
||||
*/
|
||||
public function testConnectWillReturnMappedSocks5ErrorCodeFromConnector($error, $expectedCode)
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
|
||||
|
||||
$promise = \React\Promise\reject($error);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80));
|
||||
|
||||
$code = null;
|
||||
$promise->then(null, function ($error) use (&$code) {
|
||||
$code = $error->getCode();
|
||||
});
|
||||
|
||||
$this->assertEquals($expectedCode, $code);
|
||||
}
|
||||
|
||||
public function testHandleSocksConnectionWillEndOnInvalidData()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
|
||||
$connection->expects($this->once())->method('pause');
|
||||
$connection->expects($this->once())->method('end');
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array('asdasdasdasdasd'));
|
||||
}
|
||||
|
||||
public function testHandleSocks4ConnectionWithIpv4WillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00"));
|
||||
}
|
||||
|
||||
public function testHandleSocks4aConnectionWithHostnameWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
|
||||
}
|
||||
|
||||
public function testHandleSocks4aConnectionWithHostnameAndSourceAddressWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'getRemoteAddress'))->getMock();
|
||||
$connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://10.20.30.40:5060');
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('example.com:80?source=socks4%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
|
||||
}
|
||||
|
||||
public function testHandleSocks4aConnectionWithSecureTlsSourceAddressWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'getRemoteAddress'))->getMock();
|
||||
$connection->expects($this->once())->method('getRemoteAddress')->willReturn('tls://10.20.30.40:5060');
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('example.com:80?source=socks4s%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
|
||||
}
|
||||
|
||||
public function testHandleSocks4aConnectionWithInvalidHostnameWillNotEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
|
||||
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "tls://example.com:80?" . "\x00"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithIpv4WillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithIpv4AndSourceAddressWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'getRemoteAddress'))->getMock();
|
||||
$connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://10.20.30.40:5060');
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80?source=socks5%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithSecureTlsIpv4AndSourceAddressWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'getRemoteAddress'))->getMock();
|
||||
$connection->expects($this->once())->method('getRemoteAddress')->willReturn('tls://10.20.30.40:5060');
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80?source=socks5s%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithIpv6WillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('[::1]:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x04" . inet_pton('::1') . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithHostnameWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x0B" . "example.com" . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithConnectorRefusedWillReturnReturnRefusedError()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\reject(new RuntimeException('Connection refused'));
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x05" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x0B" . "example.com" . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5UdpCommandWillNotEstablishOutgoingConnectionAndReturnCommandError()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x07" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x03\x00\x03\x0B" . "example.com" . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithInvalidHostnameWillNotEstablishOutgoingConnectionAndReturnGeneralError()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x01" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x15" . "tls://example.com:80?" . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocksConnectionWillCancelOutputConnectionIfIncomingCloses()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { }, $this->expectCallableOnce());
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00"));
|
||||
$connection->emit('close');
|
||||
}
|
||||
|
||||
public function testUnsetAuth()
|
||||
{
|
||||
$this->server->unsetAuth();
|
||||
$this->server->unsetAuth();
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
82
vendor/clue/socks-react/tests/StreamReaderTest.php
vendored
Executable file
82
vendor/clue/socks-react/tests/StreamReaderTest.php
vendored
Executable file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\StreamReader;
|
||||
|
||||
class StreamReaderTest extends TestCase
|
||||
{
|
||||
private $reader;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->reader = new StreamReader();
|
||||
}
|
||||
|
||||
public function testReadByteAssertCorrect()
|
||||
{
|
||||
$this->reader->readByteAssert(0x01)->then($this->expectCallableOnce(0x01));
|
||||
|
||||
$this->reader->write("\x01");
|
||||
}
|
||||
|
||||
public function testReadByteAssertInvalid()
|
||||
{
|
||||
$this->reader->readByteAssert(0x02)->then(null, $this->expectCallableOnce());
|
||||
|
||||
$this->reader->write("\x03");
|
||||
}
|
||||
|
||||
public function testReadStringNull()
|
||||
{
|
||||
$this->reader->readStringNull()->then($this->expectCallableOnce('hello'));
|
||||
|
||||
$this->reader->write("hello\x00");
|
||||
}
|
||||
|
||||
public function testReadStringLength()
|
||||
{
|
||||
$this->reader->readLength(5)->then($this->expectCallableOnce('hello'));
|
||||
|
||||
$this->reader->write('he');
|
||||
$this->reader->write('ll');
|
||||
$this->reader->write('o ');
|
||||
|
||||
$this->assertEquals(' ', $this->reader->getBuffer());
|
||||
}
|
||||
|
||||
public function testReadBuffered()
|
||||
{
|
||||
$this->reader->write('hello');
|
||||
|
||||
$this->reader->readLength(5)->then($this->expectCallableOnce('hello'));
|
||||
|
||||
$this->assertEquals('', $this->reader->getBuffer());
|
||||
}
|
||||
|
||||
public function testSequence()
|
||||
{
|
||||
$this->reader->readByte()->then($this->expectCallableOnce(ord('h')));
|
||||
$this->reader->readByteAssert(ord('e'))->then($this->expectCallableOnce(ord('e')));
|
||||
$this->reader->readLength(4)->then($this->expectCallableOnce('llo '));
|
||||
$this->reader->readBinary(array('w'=>'C', 'o' => 'C'))->then($this->expectCallableOnce(array('w' => ord('w'), 'o' => ord('o'))));
|
||||
|
||||
$this->reader->write('hello world');
|
||||
|
||||
$this->assertEquals('rld', $this->reader->getBuffer());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidStructure()
|
||||
{
|
||||
$this->reader->readBinary(array('invalid' => 'y'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidCallback()
|
||||
{
|
||||
$this->reader->readBufferCallback(array());
|
||||
}
|
||||
}
|
||||
103
vendor/clue/socks-react/tests/bootstrap.php
vendored
Executable file
103
vendor/clue/socks-react/tests/bootstrap.php
vendored
Executable file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
(include_once __DIR__.'/../vendor/autoload.php') OR die(PHP_EOL.'ERROR: composer autoloader not found, run "composer install" or see README for instructions'.PHP_EOL);
|
||||
|
||||
class TestCase extends PHPUnit\Framework\TestCase
|
||||
{
|
||||
protected function expectCallableOnce()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
|
||||
|
||||
if (func_num_args() > 0) {
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->equalTo(func_get_arg(0)));
|
||||
} else {
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke');
|
||||
}
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableNever()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->never())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnceWithExceptionCode($code)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->callback(function ($e) use ($code) {
|
||||
return $e->getCode() === $code;
|
||||
}));
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnceParameter($type)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->isInstanceOf($type));
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
|
||||
*/
|
||||
protected function createCallableMock()
|
||||
{
|
||||
return $this->getMockBuilder('CallableStub')->getMock();
|
||||
}
|
||||
|
||||
protected function expectPromiseResolve($promise)
|
||||
{
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
|
||||
$that = $this;
|
||||
$promise->then(null, function($error) use ($that) {
|
||||
$that->assertNull($error);
|
||||
$that->fail('promise rejected');
|
||||
});
|
||||
$promise->then($this->expectCallableOnce(), $this->expectCallableNever());
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
protected function expectPromiseReject($promise)
|
||||
{
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
|
||||
$that = $this;
|
||||
$promise->then(function($value) use ($that) {
|
||||
$that->assertNull($value);
|
||||
$that->fail('promise resolved');
|
||||
});
|
||||
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
|
||||
|
||||
return $promise;
|
||||
}
|
||||
}
|
||||
|
||||
class CallableStub
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user