This commit is contained in:
2022-10-23 01:39:27 +02:00
parent 8c17aab483
commit 1929b84685
4130 changed files with 479334 additions and 0 deletions

2
vendor/clue/socks-react/.gitignore vendored Executable file
View File

@@ -0,0 +1,2 @@
/vendor
/composer.lock

27
vendor/clue/socks-react/.travis.yml vendored Executable file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

28
vendor/clue/socks-react/composer.json vendored Executable file
View 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
View 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
View 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();

View 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();

View 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();

View 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();

View 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();

View 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();

View 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();

View 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();

View 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();

View 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();

View 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
View 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
View 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
View 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
View 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
View 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));
}
}

View 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
View 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);
}
}

View 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
View 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()
{
}
}