init
This commit is contained in:
2
vendor/clue/http-proxy-react/.gitignore
vendored
Executable file
2
vendor/clue/http-proxy-react/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
/vendor/
|
||||
/composer.lock
|
||||
27
vendor/clue/http-proxy-react/.travis.yml
vendored
Executable file
27
vendor/clue/http-proxy-react/.travis.yml
vendored
Executable file
@@ -0,0 +1,27 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
# - 5.3 # requires old distro, see below
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7
|
||||
- hhvm # ignore errors, see below
|
||||
|
||||
# lock distro so new future defaults will not break the build
|
||||
dist: trusty
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.3
|
||||
dist: precise
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
|
||||
sudo: false
|
||||
|
||||
install:
|
||||
- composer install --no-interaction
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-text
|
||||
103
vendor/clue/http-proxy-react/CHANGELOG.md
vendored
Executable file
103
vendor/clue/http-proxy-react/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,103 @@
|
||||
# Changelog
|
||||
|
||||
## 1.3.0 (2018-02-13)
|
||||
|
||||
* Feature: Support communication over Unix domain sockets (UDS)
|
||||
(#20 by @clue)
|
||||
|
||||
```php
|
||||
// new: now supports communication over Unix domain sockets (UDS)
|
||||
$proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector);
|
||||
```
|
||||
|
||||
* Reduce memory consumption by avoiding circular reference from stream reader
|
||||
(#18 by @valga)
|
||||
|
||||
* Improve documentation
|
||||
(#19 by @clue)
|
||||
|
||||
## 1.2.0 (2017-08-30)
|
||||
|
||||
* Feature: Use socket error codes for connection rejections
|
||||
(#17 by @clue)
|
||||
|
||||
```php
|
||||
$promise = $proxy->connect('imap.example.com:143');
|
||||
$promise->then(null, function (Exeption $e) {
|
||||
if ($e->getCode() === SOCKET_EACCES) {
|
||||
echo 'Failed to authenticate with proxy!';
|
||||
}
|
||||
throw $e;
|
||||
});
|
||||
```
|
||||
|
||||
* Improve test suite by locking Travis distro so new defaults will not break the build and
|
||||
optionally exclude tests that rely on working internet connection
|
||||
(#15 and #16 by @clue)
|
||||
|
||||
## 1.1.0 (2017-06-11)
|
||||
|
||||
* Feature: Support proxy authentication if proxy URL contains username/password
|
||||
(#14 by @clue)
|
||||
|
||||
```php
|
||||
// new: username/password will now be passed to HTTP proxy server
|
||||
$proxy = new ProxyConnector('user:pass@127.0.0.1:8080', $connector);
|
||||
```
|
||||
|
||||
## 1.0.0 (2017-06-10)
|
||||
|
||||
* First stable release, now following SemVer
|
||||
|
||||
> Contains no other changes, so it's actually fully compatible with the v0.3.2 release.
|
||||
|
||||
## 0.3.2 (2017-06-10)
|
||||
|
||||
* Fix: Fix rejecting invalid URIs and unexpected URI schemes
|
||||
(#13 by @clue)
|
||||
|
||||
* Fix HHVM build for now again and ignore future HHVM build errors
|
||||
(#12 by @clue)
|
||||
|
||||
* Documentation for Connector concepts (TCP/TLS, timeouts, DNS resolution)
|
||||
(#11 by @clue)
|
||||
|
||||
## 0.3.1 (2017-05-10)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
|
||||
(#10 by @clue)
|
||||
|
||||
## 0.3.0 (2017-04-10)
|
||||
|
||||
* Feature / BC break: Replace deprecated SocketClient with new Socket component
|
||||
(#9 by @clue)
|
||||
|
||||
This implies that the `ProxyConnector` from this package now implements the
|
||||
`React\Socket\ConnectorInterface` instead of the legacy
|
||||
`React\SocketClient\ConnectorInterface`.
|
||||
|
||||
## 0.2.0 (2017-04-10)
|
||||
|
||||
* Feature / BC break: Update SocketClient to v0.7 or v0.6 and
|
||||
use `connect($uri)` instead of `create($host, $port)`
|
||||
(#8 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$connector->create($host, $port)->then(function (Stream $conn) {
|
||||
$conn->write("…");
|
||||
});
|
||||
|
||||
// new
|
||||
$connector->connect($uri)->then(function (ConnectionInterface $conn) {
|
||||
$conn->write("…");
|
||||
});
|
||||
```
|
||||
|
||||
* Improve test suite by adding PHPUnit to require-dev
|
||||
(#7 by @clue)
|
||||
|
||||
|
||||
## 0.1.0 (2016-11-01)
|
||||
|
||||
* First tagged release
|
||||
21
vendor/clue/http-proxy-react/LICENSE
vendored
Executable file
21
vendor/clue/http-proxy-react/LICENSE
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
422
vendor/clue/http-proxy-react/README.md
vendored
Executable file
422
vendor/clue/http-proxy-react/README.md
vendored
Executable file
@@ -0,0 +1,422 @@
|
||||
# clue/http-proxy-react [](https://travis-ci.org/clue/php-http-proxy-react)
|
||||
|
||||
Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP
|
||||
CONNECT proxy server, built on top of [ReactPHP](https://reactphp.org).
|
||||
|
||||
HTTP CONNECT proxy servers (also commonly known as "HTTPS proxy" or "SSL proxy")
|
||||
are commonly used to tunnel HTTPS traffic through an intermediary ("proxy"), to
|
||||
conceal the origin address (anonymity) or to circumvent address blocking
|
||||
(geoblocking). While many (public) HTTP CONNECT proxy servers often limit this
|
||||
to HTTPS port `443` only, this can technically be used to tunnel any
|
||||
TCP/IP-based protocol (HTTP, SMTP, IMAP etc.).
|
||||
This library provides a simple API to create these tunneled connection for you.
|
||||
Because it implements ReactPHP's standard
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
|
||||
it can simply be used in place of a normal connector.
|
||||
This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
|
||||
existing higher-level protocol implementation.
|
||||
|
||||
* **Async execution of connections** -
|
||||
Send any number of HTTP CONNECT requests in parallel and process their
|
||||
responses as soon as results come in.
|
||||
The Promise-based design provides a *sane* interface to working with out of
|
||||
bound responses and possible connection errors.
|
||||
* **Standard interfaces** -
|
||||
Allows easy integration with existing higher-level components by implementing
|
||||
ReactPHP's standard
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface).
|
||||
* **Lightweight, SOLID design** -
|
||||
Provides a thin abstraction that is [*just good enough*](http://en.wikipedia.org/wiki/Principle_of_good_enough)
|
||||
and does not get in your way.
|
||||
Builds on top of well-tested components and well-established concepts instead of reinventing the wheel.
|
||||
* **Good test coverage** -
|
||||
Comes with an automated tests suite and is regularly tested against actual proxy servers in the wild
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Quickstart example](#quickstart-example)
|
||||
* [Usage](#usage)
|
||||
* [ProxyConnector](#proxyconnector)
|
||||
* [Plain TCP connections](#plain-tcp-connections)
|
||||
* [Secure TLS connections](#secure-tls-connections)
|
||||
* [Connection timeout](#connection-timeout)
|
||||
* [DNS resolution](#dns-resolution)
|
||||
* [Authentication](#authentication)
|
||||
* [Advanced secure proxy connections](#advanced-secure-proxy-connections)
|
||||
* [Advanced Unix domain sockets](#advanced-unix-domain-sockets)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
* [More](#more)
|
||||
|
||||
### Quickstart example
|
||||
|
||||
The following example code demonstrates how this library can be used to send a
|
||||
secure HTTPS request to google.com through a local HTTP proxy server:
|
||||
|
||||
```php
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$proxy = new ProxyConnector('127.0.0.1:8080', new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
|
||||
$stream->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
```
|
||||
|
||||
See also the [examples](examples).
|
||||
|
||||
## Usage
|
||||
|
||||
### ProxyConnector
|
||||
|
||||
The `ProxyConnector` is responsible for creating plain TCP/IP connections to
|
||||
any destination by using an intermediary HTTP CONNECT proxy.
|
||||
|
||||
```
|
||||
[you] -> [proxy] -> [destination]
|
||||
```
|
||||
|
||||
Its constructor simply accepts an HTTP proxy URL and a connector used to connect
|
||||
to the proxy server address:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop);
|
||||
$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
|
||||
```
|
||||
|
||||
The proxy URL may or may not contain a scheme and port definition. The default
|
||||
port will be `80` for HTTP (or `443` for HTTPS), but many common HTTP proxy
|
||||
servers use custom ports (often the alternative HTTP port `8080`).
|
||||
In its most simple form, the given connector will be a
|
||||
[`\React\Socket\Connector`](https://github.com/reactphp/socket#connector) if you
|
||||
want to connect to a given IP address as above.
|
||||
|
||||
This is the main class in this package.
|
||||
Because it implements ReactPHP's standard
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
|
||||
it can simply be used in place of a normal connector.
|
||||
Accordingly, it provides only a single public method, the
|
||||
[`connect()`](https://github.com/reactphp/socket#connect) method.
|
||||
The `connect(string $uri): PromiseInterface<ConnectionInterface, Exception>`
|
||||
method can be used to establish a streaming connection.
|
||||
It returns a [Promise](https://github.com/reactphp/promise) which either
|
||||
fulfills with a [ConnectionInterface](https://github.com/reactphp/socket#connectioninterface)
|
||||
on success or rejects with an `Exception` on error.
|
||||
|
||||
This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
|
||||
higher-level component:
|
||||
|
||||
```diff
|
||||
- $client = new SomeClient($connector);
|
||||
+ $proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
|
||||
+ $client = new SomeClient($proxy);
|
||||
```
|
||||
|
||||
#### Plain TCP connections
|
||||
|
||||
HTTP CONNECT proxies are most frequently used to issue HTTPS requests to your destination.
|
||||
However, this is actually performed on a higher protocol layer and this
|
||||
connector is actually inherently a general-purpose plain TCP/IP connector.
|
||||
As documented above, you can simply invoke its `connect()` method to establish
|
||||
a streaming plain TCP/IP connection and use any higher level protocol like so:
|
||||
|
||||
```php
|
||||
$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
|
||||
|
||||
$proxy->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("EHLO local\r\n");
|
||||
$stream->on('data', function ($chunk) use ($stream) {
|
||||
echo $chunk;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
You can either use the `ProxyConnector` directly or you may want to wrap this connector
|
||||
in ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector):
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("EHLO local\r\n");
|
||||
$stream->on('data', function ($chunk) use ($stream) {
|
||||
echo $chunk;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Note that HTTP CONNECT proxies often restrict which ports one may connect to.
|
||||
Many (public) proxy servers do in fact limit this to HTTPS (443) only.
|
||||
|
||||
#### Secure TLS connections
|
||||
|
||||
This class can also be used if you want to establish a secure TLS connection
|
||||
(formerly known as SSL) between you and your destination, such as when using
|
||||
secure HTTPS to your destination site. You can simply wrap this connector in
|
||||
ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector) or the
|
||||
low-level [`SecureConnector`](https://github.com/reactphp/socket#secureconnector):
|
||||
|
||||
```php
|
||||
$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("EHLO local\r\n");
|
||||
$stream->on('data', function ($chunk) use ($stream) {
|
||||
echo $chunk;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
> Note how secure TLS connections are in fact entirely handled outside of
|
||||
this HTTP CONNECT client implementation.
|
||||
|
||||
#### Connection timeout
|
||||
|
||||
By default, the `ProxyConnector` does not implement any timeouts for establishing remote
|
||||
connections.
|
||||
Your underlying operating system may impose limits on pending and/or idle TCP/IP
|
||||
connections, anywhere in a range of a few minutes to several hours.
|
||||
|
||||
Many use cases require more control over the timeout and likely values much
|
||||
smaller, usually in the range of a few seconds only.
|
||||
|
||||
You can use ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector)
|
||||
or the low-level
|
||||
[`TimeoutConnector`](https://github.com/reactphp/socket#timeoutconnector)
|
||||
to decorate any given `ConnectorInterface` instance.
|
||||
It provides the same `connect()` method, but will automatically reject the
|
||||
underlying connection attempt if it takes too long:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false,
|
||||
'timeout' => 3.0
|
||||
));
|
||||
|
||||
$connector->connect('tcp://google.com:80')->then(function ($stream) {
|
||||
// connection succeeded within 3.0 seconds
|
||||
});
|
||||
```
|
||||
|
||||
See also any of the [examples](examples).
|
||||
|
||||
> Note how connection timeout is in fact entirely handled outside of this
|
||||
HTTP CONNECT client implementation.
|
||||
|
||||
#### DNS resolution
|
||||
|
||||
By default, the `ProxyConnector` does not perform any DNS resolution at all and simply
|
||||
forwards any hostname you're trying to connect to the remote proxy server.
|
||||
The remote proxy server is thus responsible for looking up any hostnames via DNS
|
||||
(this default mode is thus called *remote DNS resolution*).
|
||||
|
||||
As an alternative, you can also send the destination IP to the remote proxy
|
||||
server.
|
||||
In this mode you either have to stick to using IPs only (which is ofen unfeasable)
|
||||
or perform any DNS lookups locally and only transmit the resolved destination IPs
|
||||
(this mode is thus called *local DNS resolution*).
|
||||
|
||||
The default *remote DNS resolution* is useful if your local `ProxyConnector` either can
|
||||
not resolve target hostnames because it has no direct access to the internet or
|
||||
if it should not resolve target hostnames because its outgoing DNS traffic might
|
||||
be intercepted.
|
||||
|
||||
As noted above, the `ProxyConnector` defaults to using remote DNS resolution.
|
||||
However, wrapping the `ProxyConnector` in ReactPHP's
|
||||
[`Connector`](https://github.com/reactphp/socket#connector) actually
|
||||
performs local DNS resolution unless explicitly defined otherwise.
|
||||
Given that remote DNS resolution is assumed to be the preferred mode, all
|
||||
other examples explicitly disable DNS resoltion like this:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false
|
||||
));
|
||||
```
|
||||
|
||||
If you want to explicitly use *local DNS resolution*, you can use the following code:
|
||||
|
||||
```php
|
||||
// set up Connector which uses Google's public DNS (8.8.8.8)
|
||||
$connector = Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => '8.8.8.8'
|
||||
));
|
||||
```
|
||||
|
||||
> Note how local DNS resolution is in fact entirely handled outside of this
|
||||
HTTP CONNECT client implementation.
|
||||
|
||||
#### Authentication
|
||||
|
||||
If your HTTP proxy server requires authentication, you may pass the username and
|
||||
password as part of the HTTP proxy URL like this:
|
||||
|
||||
```php
|
||||
$proxy = new ProxyConnector('http://user:pass@127.0.0.1:8080', $connector);
|
||||
```
|
||||
|
||||
Note that both the username and password must be percent-encoded if they contain
|
||||
special characters:
|
||||
|
||||
```php
|
||||
$user = 'he:llo';
|
||||
$pass = 'p@ss';
|
||||
|
||||
$proxy = new ProxyConnector(
|
||||
rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1:8080',
|
||||
$connector
|
||||
);
|
||||
```
|
||||
|
||||
> The authentication details will be used for basic authentication and will be
|
||||
transferred in the `Proxy-Authorization` HTTP request header for each
|
||||
connection attempt.
|
||||
If the authentication details are missing or not accepted by the remote HTTP
|
||||
proxy server, it is expected to reject each connection attempt with a
|
||||
`407` (Proxy Authentication Required) response status code and an exception
|
||||
error code of `SOCKET_EACCES` (13).
|
||||
|
||||
#### Advanced secure proxy connections
|
||||
|
||||
Note that communication between the client and the proxy is usually via an
|
||||
unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
|
||||
setup, because you can still establish a TLS connection between you and the
|
||||
destination host as above.
|
||||
|
||||
If you want to connect to a (rather rare) HTTPS proxy, you may want use the
|
||||
`https://` scheme (HTTPS default port 443) and use ReactPHP's
|
||||
[`Connector`](https://github.com/reactphp/socket#connector) or the low-level
|
||||
[`SecureConnector`](https://github.com/reactphp/socket#secureconnector)
|
||||
instance to create a secure connection to the proxy:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop);
|
||||
$proxy = new ProxyConnector('https://127.0.0.1:443', $connector);
|
||||
|
||||
$proxy->connect('tcp://smtp.googlemail.com:587');
|
||||
```
|
||||
|
||||
#### Advanced Unix domain sockets
|
||||
|
||||
HTTP CONNECT proxy servers support forwarding TCP/IP based connections and
|
||||
higher level protocols.
|
||||
In some advanced cases, it may be useful to let your HTTP CONNECT proxy server
|
||||
listen on a Unix domain socket (UDS) path instead of a IP:port combination.
|
||||
For example, this allows you to rely on file system permissions instead of
|
||||
having to rely on explicit [authentication](#authentication).
|
||||
|
||||
You can simply use the `http+unix://` URI scheme like this:
|
||||
|
||||
```php
|
||||
$proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector);
|
||||
|
||||
$proxy->connect('tcp://google.com:80')->then(function (ConnectionInterface $stream) {
|
||||
// connected…
|
||||
});
|
||||
```
|
||||
|
||||
Similarly, you can also combine this with [authentication](#authentication)
|
||||
like this:
|
||||
|
||||
```php
|
||||
$proxy = new ProxyConnector('http+unix://user:pass@/tmp/proxy.sock', $connector);
|
||||
```
|
||||
|
||||
> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
|
||||
has limited support for this.
|
||||
In particular, enabling [secure TLS](#secure-tls-connections) may not be
|
||||
supported.
|
||||
|
||||
> Note that the HTTP CONNECT protocol does not support the notion of UDS paths.
|
||||
The above works reasonably well because UDS is only used for the connection between
|
||||
client and proxy server and the path will not actually passed over the protocol.
|
||||
This implies that this does not support connecting to UDS destination paths.
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This project follows [SemVer](http://semver.org/).
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
$ composer require clue/http-proxy-react:^1.3
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
|
||||
HHVM.
|
||||
It's *highly recommended to use PHP 7+* for this project.
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org):
|
||||
|
||||
```bash
|
||||
$ composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
$ php vendor/bin/phpunit
|
||||
```
|
||||
|
||||
The test suite contains tests that rely on a working internet connection,
|
||||
alternatively you can also run it like this:
|
||||
|
||||
```bash
|
||||
$ php vendor/bin/phpunit --exclude-group internet
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## More
|
||||
|
||||
* If you want to learn more about how the
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
|
||||
and its usual implementations look like, refer to the documentation of the underlying
|
||||
[react/socket](https://github.com/reactphp/socket) component.
|
||||
* If you want to learn more about processing streams of data, refer to the
|
||||
documentation of the underlying
|
||||
[react/stream](https://github.com/reactphp/stream) component.
|
||||
* As an alternative to an HTTP CONNECT proxy, you may also want to look into
|
||||
using a SOCKS (SOCKS4/SOCKS5) proxy instead.
|
||||
You may want to use [clue/socks-react](https://github.com/clue/php-socks-react)
|
||||
which also provides an implementation of the same
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
|
||||
so that supporting either proxy protocol should be fairly trivial.
|
||||
* If you're dealing with public proxies, you'll likely have to work with mixed
|
||||
quality and unreliable proxies. You may want to look into using
|
||||
[clue/connection-manager-extra](https://github.com/clue/php-connection-manager-extra)
|
||||
which allows retrying unreliable ones, implying connection timeouts,
|
||||
concurrently working with multiple connectors and more.
|
||||
* If you're looking for an end-user HTTP CONNECT proxy server daemon, you may
|
||||
want to use [LeProxy](https://leproxy.org/).
|
||||
30
vendor/clue/http-proxy-react/composer.json
vendored
Executable file
30
vendor/clue/http-proxy-react/composer.json
vendored
Executable file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "clue/http-proxy-react",
|
||||
"description": "Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP",
|
||||
"keywords": ["HTTP", "CONNECT", "proxy", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/php-http-proxy-react",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\HttpProxy\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Tests\\Clue\\React\\HttpProxy\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/promise": " ^2.1 || ^1.2.1",
|
||||
"react/socket": "^1.0 || ^0.8.4",
|
||||
"ringcentral/psr7": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.0 || ^4.8",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
|
||||
"clue/block-react": "^1.1"
|
||||
}
|
||||
}
|
||||
30
vendor/clue/http-proxy-react/examples/01-proxy-https.php
vendored
Executable file
30
vendor/clue/http-proxy-react/examples/01-proxy-https.php
vendored
Executable file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
// A simple example which requests https://google.com/ through an HTTP CONNECT proxy.
|
||||
// The proxy can be given as first argument and defaults to localhost:8080 otherwise.
|
||||
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$url = isset($argv[1]) ? $argv[1] : '127.0.0.1:8080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$proxy = new ProxyConnector($url, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
|
||||
$stream->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
37
vendor/clue/http-proxy-react/examples/02-optional-proxy-https.php
vendored
Executable file
37
vendor/clue/http-proxy-react/examples/02-optional-proxy-https.php
vendored
Executable file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
// A simple example which requests https://google.com/ either directly or through
|
||||
// an HTTP CONNECT proxy.
|
||||
// The Proxy can be given as first argument or does not use a proxy otherwise.
|
||||
// This example highlights how changing from direct connection to using a proxy
|
||||
// actually adds very little complexity and does not mess with your actual
|
||||
// network protocol otherwise.
|
||||
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$connector = new Connector($loop);
|
||||
|
||||
// first argument given? use this as the proxy URL
|
||||
if (isset($argv[1])) {
|
||||
$proxy = new ProxyConnector($argv[1], $connector);
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
}
|
||||
|
||||
$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
|
||||
$stream->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
32
vendor/clue/http-proxy-react/examples/11-proxy-smtp.php
vendored
Executable file
32
vendor/clue/http-proxy-react/examples/11-proxy-smtp.php
vendored
Executable file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
// A simple example which uses a plain SMTP connection to Googlemail through a HTTP CONNECT proxy.
|
||||
// Proxy can be given as first argument and defaults to localhost:8080 otherwise.
|
||||
// Please note that MANY public proxies do not allow SMTP connections, YMMV.
|
||||
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$url = isset($argv[1]) ? $argv[1] : '127.0.0.1:8080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$proxy = new ProxyConnector($url, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("EHLO local\r\n");
|
||||
$stream->on('data', function ($chunk) use ($stream) {
|
||||
echo $chunk;
|
||||
$stream->write("QUIT\r\n");
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
35
vendor/clue/http-proxy-react/examples/12-proxy-smtps.php
vendored
Executable file
35
vendor/clue/http-proxy-react/examples/12-proxy-smtps.php
vendored
Executable file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
// A simple example which uses a secure SMTP connection to Googlemail through a HTTP CONNECT proxy.
|
||||
// Proxy can be given as first argument and defaults to localhost:8080 otherwise.
|
||||
// This example highlights how changing from plain connections (see previous
|
||||
// example) to using a secure connection actually adds very little complexity
|
||||
// and does not mess with your actual network protocol otherwise.
|
||||
// Please note that MANY public proxies do not allow SMTP connections, YMMV.
|
||||
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$url = isset($argv[1]) ? $argv[1] : '127.0.0.1:8080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$proxy = new ProxyConnector($url, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("EHLO local\r\n");
|
||||
$stream->on('data', function ($chunk) use ($stream) {
|
||||
echo $chunk;
|
||||
$stream->write("QUIT\r\n");
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
14
vendor/clue/http-proxy-react/phpunit.xml.dist
vendored
Executable file
14
vendor/clue/http-proxy-react/phpunit.xml.dist
vendored
Executable file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit bootstrap="vendor/autoload.php" colors="true">
|
||||
<testsuites>
|
||||
<testsuite>
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
213
vendor/clue/http-proxy-react/src/ProxyConnector.php
vendored
Executable file
213
vendor/clue/http-proxy-react/src/ProxyConnector.php
vendored
Executable file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\HttpProxy;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use RingCentral\Psr7;
|
||||
use React\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\FixedUriConnector;
|
||||
|
||||
/**
|
||||
* A simple Connector that uses an HTTP CONNECT proxy to create plain TCP/IP connections to any destination
|
||||
*
|
||||
* [you] -> [proxy] -> [destination]
|
||||
*
|
||||
* This is most frequently used to issue HTTPS requests to your destination.
|
||||
* However, this is actually performed on a higher protocol layer and this
|
||||
* connector is actually inherently a general-purpose plain TCP/IP connector.
|
||||
*
|
||||
* Note that HTTP CONNECT proxies often restrict which ports one may connect to.
|
||||
* Many (public) proxy servers do in fact limit this to HTTPS (443) only.
|
||||
*
|
||||
* If you want to establish a TLS connection (such as HTTPS) between you and
|
||||
* your destination, you may want to wrap this connector in a SecureConnector
|
||||
* instance.
|
||||
*
|
||||
* Note that communication between the client and the proxy is usually via an
|
||||
* unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
|
||||
* setup, because you can still establish a TLS connection between you and the
|
||||
* destination host as above.
|
||||
*
|
||||
* If you want to connect to a (rather rare) HTTPS proxy, you may want use its
|
||||
* HTTPS port (443) and use a SecureConnector instance to create a secure
|
||||
* connection to the proxy.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc7231#section-4.3.6
|
||||
*/
|
||||
class ProxyConnector implements ConnectorInterface
|
||||
{
|
||||
private $connector;
|
||||
private $proxyUri;
|
||||
private $proxyAuth = '';
|
||||
|
||||
/**
|
||||
* Instantiate a new ProxyConnector which uses the given $proxyUrl
|
||||
*
|
||||
* @param string $proxyUrl The proxy URL may or may not contain a scheme and
|
||||
* port definition. The default port will be `80` for HTTP (or `443` for
|
||||
* HTTPS), but many common HTTP proxy servers use custom ports.
|
||||
* @param ConnectorInterface $connector In its most simple form, the given
|
||||
* connector will be a \React\Socket\Connector if you want to connect to
|
||||
* a given IP address.
|
||||
* @throws InvalidArgumentException if the proxy URL is invalid
|
||||
*/
|
||||
public function __construct($proxyUrl, ConnectorInterface $connector)
|
||||
{
|
||||
// support `http+unix://` scheme for Unix domain socket (UDS) paths
|
||||
if (preg_match('/^http\+unix:\/\/(.*?@)?(.+?)$/', $proxyUrl, $match)) {
|
||||
// rewrite URI to parse authentication from dummy host
|
||||
$proxyUrl = 'http://' . $match[1] . 'localhost';
|
||||
|
||||
// connector uses Unix transport scheme and explicit path given
|
||||
$connector = new FixedUriConnector(
|
||||
'unix://' . $match[2],
|
||||
$connector
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($proxyUrl, '://') === false) {
|
||||
$proxyUrl = 'http://' . $proxyUrl;
|
||||
}
|
||||
|
||||
$parts = parse_url($proxyUrl);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host']) || ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')) {
|
||||
throw new InvalidArgumentException('Invalid proxy URL "' . $proxyUrl . '"');
|
||||
}
|
||||
|
||||
// apply default port and TCP/TLS transport for given scheme
|
||||
if (!isset($parts['port'])) {
|
||||
$parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
|
||||
}
|
||||
$parts['scheme'] = $parts['scheme'] === 'https' ? 'tls' : 'tcp';
|
||||
|
||||
$this->connector = $connector;
|
||||
$this->proxyUri = $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port'];
|
||||
|
||||
// prepare Proxy-Authorization header if URI contains username/password
|
||||
if (isset($parts['user']) || isset($parts['pass'])) {
|
||||
$this->proxyAuth = 'Proxy-Authorization: Basic ' . base64_encode(
|
||||
rawurldecode($parts['user'] . ':' . (isset($parts['pass']) ? $parts['pass'] : ''))
|
||||
) . "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
if (strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
}
|
||||
|
||||
$parts = parse_url($uri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
|
||||
}
|
||||
|
||||
$host = trim($parts['host'], '[]');
|
||||
$port = $parts['port'];
|
||||
|
||||
// construct URI to HTTP CONNECT proxy server to connect to
|
||||
$proxyUri = $this->proxyUri;
|
||||
|
||||
// append path from URI if given
|
||||
if (isset($parts['path'])) {
|
||||
$proxyUri .= $parts['path'];
|
||||
}
|
||||
|
||||
// parse query args
|
||||
$args = array();
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $args);
|
||||
}
|
||||
|
||||
// append hostname from URI to query string unless explicitly given
|
||||
if (!isset($args['hostname'])) {
|
||||
$args['hostname'] = $parts['host'];
|
||||
}
|
||||
|
||||
// append query string
|
||||
$proxyUri .= '?' . http_build_query($args, '', '&');;
|
||||
|
||||
// append fragment from URI if given
|
||||
if (isset($parts['fragment'])) {
|
||||
$proxyUri .= '#' . $parts['fragment'];
|
||||
}
|
||||
|
||||
$auth = $this->proxyAuth;
|
||||
|
||||
return $this->connector->connect($proxyUri)->then(function (ConnectionInterface $stream) use ($host, $port, $auth) {
|
||||
$deferred = new Deferred(function ($_, $reject) use ($stream) {
|
||||
$reject(new RuntimeException('Connection canceled while waiting for response from proxy (ECONNABORTED)', defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103));
|
||||
$stream->close();
|
||||
});
|
||||
|
||||
// keep buffering data until headers are complete
|
||||
$buffer = '';
|
||||
$fn = function ($chunk) use (&$buffer, $deferred, $stream) {
|
||||
$buffer .= $chunk;
|
||||
|
||||
$pos = strpos($buffer, "\r\n\r\n");
|
||||
if ($pos !== false) {
|
||||
// try to parse headers as response message
|
||||
try {
|
||||
$response = Psr7\parse_response(substr($buffer, 0, $pos));
|
||||
} catch (Exception $e) {
|
||||
$deferred->reject(new RuntimeException('Invalid response received from proxy (EBADMSG)', defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71, $e));
|
||||
$stream->close();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($response->getStatusCode() === 407) {
|
||||
// map status code 407 (Proxy Authentication Required) to EACCES
|
||||
$deferred->reject(new RuntimeException('Proxy denied connection due to invalid authentication ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13));
|
||||
return $stream->close();
|
||||
} elseif ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
|
||||
// map non-2xx status code to ECONNREFUSED
|
||||
$deferred->reject(new RuntimeException('Proxy refused connection with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111));
|
||||
return $stream->close();
|
||||
}
|
||||
|
||||
// all okay, resolve with stream instance
|
||||
$deferred->resolve($stream);
|
||||
|
||||
// emit remaining incoming as data event
|
||||
$buffer = (string)substr($buffer, $pos + 4);
|
||||
if ($buffer !== '') {
|
||||
$stream->emit('data', array($buffer));
|
||||
$buffer = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// stop buffering when 8 KiB have been read
|
||||
if (isset($buffer[8192])) {
|
||||
$deferred->reject(new RuntimeException('Proxy must not send more than 8 KiB of headers (EMSGSIZE)', defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90));
|
||||
$stream->close();
|
||||
}
|
||||
};
|
||||
$stream->on('data', $fn);
|
||||
|
||||
$stream->on('error', 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', function () use ($deferred) {
|
||||
$deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
|
||||
});
|
||||
|
||||
$stream->write("CONNECT " . $host . ":" . $port . " HTTP/1.1\r\nHost: " . $host . ":" . $port . "\r\n" . $auth . "\r\n");
|
||||
|
||||
return $deferred->promise()->then(function (ConnectionInterface $stream) use ($fn) {
|
||||
// Stop buffering when connection has been established.
|
||||
$stream->removeListener('data', $fn);
|
||||
return new Promise\FulfilledPromise($stream);
|
||||
});
|
||||
}, function (Exception $e) use ($proxyUri) {
|
||||
throw new RuntimeException('Unable to connect to proxy (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, $e);
|
||||
});
|
||||
}
|
||||
}
|
||||
80
vendor/clue/http-proxy-react/tests/AbstractTestCase.php
vendored
Executable file
80
vendor/clue/http-proxy-react/tests/AbstractTestCase.php
vendored
Executable file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Clue\React\HttpProxy;
|
||||
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
abstract class AbstractTestCase extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected function expectCallableNever()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->never())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnce()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnceWith($value)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->equalTo($value));
|
||||
|
||||
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('Tests\\Clue\\React\\HttpProxy\\CallableStub')->getMock();
|
||||
}
|
||||
}
|
||||
|
||||
class CallableStub
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
75
vendor/clue/http-proxy-react/tests/FunctionalTest.php
vendored
Executable file
75
vendor/clue/http-proxy-react/tests/FunctionalTest.php
vendored
Executable file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Clue\React\HttpProxy;
|
||||
|
||||
use React\EventLoop\Factory;
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Socket\TcpConnector;
|
||||
use React\Socket\DnsConnector;
|
||||
use Clue\React\Block;
|
||||
use React\Socket\SecureConnector;
|
||||
|
||||
/** @group internet */
|
||||
class FunctionalTest extends AbstractTestCase
|
||||
{
|
||||
private $loop;
|
||||
private $tcpConnector;
|
||||
private $dnsConnector;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = Factory::create();
|
||||
|
||||
$this->tcpConnector = new TcpConnector($this->loop);
|
||||
|
||||
$f = new \React\Dns\Resolver\Factory();
|
||||
$resolver = $f->create('8.8.8.8', $this->loop);
|
||||
|
||||
$this->dnsConnector = new DnsConnector($this->tcpConnector, $resolver);
|
||||
}
|
||||
|
||||
public function testNonListeningSocketRejectsConnection()
|
||||
{
|
||||
$proxy = new ProxyConnector('127.0.0.1:9999', $this->dnsConnector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->setExpectedException('RuntimeException', 'Unable to connect to proxy', SOCKET_ECONNREFUSED);
|
||||
Block\await($promise, $this->loop, 3.0);
|
||||
}
|
||||
|
||||
public function testPlainGoogleDoesNotAcceptConnectMethod()
|
||||
{
|
||||
$proxy = new ProxyConnector('google.com', $this->dnsConnector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->setExpectedException('RuntimeException', '405 (Method Not Allowed)', SOCKET_ECONNREFUSED);
|
||||
Block\await($promise, $this->loop, 3.0);
|
||||
}
|
||||
|
||||
public function testSecureGoogleDoesNotAcceptConnectMethod()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('TLS not supported on really old platforms (HHVM < 3.8)');
|
||||
}
|
||||
|
||||
$secure = new SecureConnector($this->dnsConnector, $this->loop);
|
||||
$proxy = new ProxyConnector('https://google.com:443', $secure);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->setExpectedException('RuntimeException', '405 (Method Not Allowed)', SOCKET_ECONNREFUSED);
|
||||
Block\await($promise, $this->loop, 3.0);
|
||||
}
|
||||
|
||||
public function testSecureGoogleDoesNotAcceptPlainStream()
|
||||
{
|
||||
$proxy = new ProxyConnector('google.com:443', $this->dnsConnector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->setExpectedException('RuntimeException', 'Connection to proxy lost', SOCKET_ECONNRESET);
|
||||
Block\await($promise, $this->loop, 3.0);
|
||||
}
|
||||
}
|
||||
333
vendor/clue/http-proxy-react/tests/ProxyConnectorTest.php
vendored
Executable file
333
vendor/clue/http-proxy-react/tests/ProxyConnectorTest.php
vendored
Executable file
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Clue\React\HttpProxy;
|
||||
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Promise\Promise;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
class ProxyConnectorTest extends AbstractTestCase
|
||||
{
|
||||
private $connector;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidProxy()
|
||||
{
|
||||
new ProxyConnector('///', $this->connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidProxyScheme()
|
||||
{
|
||||
new ProxyConnector('ftp://example.com', $this->connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidHttpsUnixScheme()
|
||||
{
|
||||
new ProxyConnector('https+unix:///tmp/proxy.sock', $this->connector);
|
||||
}
|
||||
|
||||
public function testCreatesConnectionToHttpPort()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
$this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testCreatesConnectionToHttpPortAndPassesThroughUriComponents()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
$this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80/path?foo=bar&hostname=google.com#segment')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80/path?foo=bar#segment');
|
||||
}
|
||||
|
||||
public function testCreatesConnectionToHttpPortAndObeysExplicitHostname()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
$this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80?hostname=www.google.com')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80?hostname=www.google.com');
|
||||
}
|
||||
|
||||
public function testCreatesConnectionToHttpsPort()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
$this->connector->expects($this->once())->method('connect')->with('tls://proxy.example.com:443?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('https://proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testCreatesConnectionToUnixPath()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
$this->connector->expects($this->once())->method('connect')->with('unix:///tmp/proxy.sock')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testCancelPromiseWillCancelPendingConnection()
|
||||
{
|
||||
$promise = new Promise(function () { }, $this->expectCallableOnce());
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->assertInstanceOf('React\Promise\CancellablePromiseInterface', $promise);
|
||||
|
||||
$promise->cancel();
|
||||
}
|
||||
|
||||
public function testWillWriteToOpenConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\n\r\n");
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testWillProxyAuthorizationHeaderIfProxyUriContainsAuthentication()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n");
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('user:pass@proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testWillProxyAuthorizationHeaderIfProxyUriContainsOnlyUsernameWithoutPassword()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjo=\r\n\r\n");
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('user@proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testWillProxyAuthorizationHeaderIfProxyUriContainsAuthenticationWithPercentEncoding()
|
||||
{
|
||||
$user = 'h@llÖ';
|
||||
$pass = '%secret?';
|
||||
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic " . base64_encode($user . ':' . $pass) . "\r\n\r\n");
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector(rawurlencode($user) . ':' . rawurlencode($pass) . '@proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testWillProxyAuthorizationHeaderIfUnixProxyUriContainsAuthentication()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n");
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->with('unix:///tmp/proxy.sock')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('http+unix://user:pass@/tmp/proxy.sock', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testRejectsInvalidUri()
|
||||
{
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('///');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testRejectsUriWithNonTcpScheme()
|
||||
{
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('tls://google.com:80');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testRejectsIfConnectorRejects()
|
||||
{
|
||||
$promise = \React\Promise\reject(new \RuntimeException());
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testRejectsAndClosesIfStreamWritesNonHttp()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$stream->expects($this->once())->method('close');
|
||||
$stream->emit('data', array("invalid\r\n\r\n"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
|
||||
}
|
||||
|
||||
public function testRejectsAndClosesIfStreamWritesTooMuchData()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$stream->expects($this->once())->method('close');
|
||||
$stream->emit('data', array(str_repeat('*', 100000)));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EMSGSIZE));
|
||||
}
|
||||
|
||||
public function testRejectsAndClosesIfStreamReturnsProyAuthenticationRequired()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$stream->expects($this->once())->method('close');
|
||||
$stream->emit('data', array("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EACCES));
|
||||
}
|
||||
|
||||
public function testRejectsAndClosesIfStreamReturnsNonSuccess()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$stream->expects($this->once())->method('close');
|
||||
$stream->emit('data', array("HTTP/1.1 403 Not allowed\r\n\r\n"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
|
||||
}
|
||||
|
||||
public function testResolvesIfStreamReturnsSuccess()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$promise->then($this->expectCallableOnce('React\Stream\Stream'));
|
||||
$never = $this->expectCallableNever();
|
||||
$promise->then(function (ConnectionInterface $stream) use ($never) {
|
||||
$stream->on('data', $never);
|
||||
});
|
||||
|
||||
$stream->emit('data', array("HTTP/1.1 200 OK\r\n\r\n"));
|
||||
}
|
||||
|
||||
public function testResolvesIfStreamReturnsSuccessAndEmitsExcessiveData()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$once = $this->expectCallableOnceWith('hello!');
|
||||
$promise->then(function (ConnectionInterface $stream) use ($once) {
|
||||
$stream->on('data', $once);
|
||||
});
|
||||
|
||||
$stream->emit('data', array("HTTP/1.1 200 OK\r\n\r\nhello!"));
|
||||
}
|
||||
|
||||
public function testCancelPromiseWillCloseOpenConnectionAndReject()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->assertInstanceOf('React\Promise\CancellablePromiseInterface', $promise);
|
||||
|
||||
$promise->cancel();
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNABORTED));
|
||||
}
|
||||
}
|
||||
2
vendor/clue/socks-react/.gitignore
vendored
Executable file
2
vendor/clue/socks-react/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
/vendor
|
||||
/composer.lock
|
||||
27
vendor/clue/socks-react/.travis.yml
vendored
Executable file
27
vendor/clue/socks-react/.travis.yml
vendored
Executable file
@@ -0,0 +1,27 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
# - 5.3 # requires old distro, see below
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7
|
||||
- hhvm # ignore errors, see below
|
||||
|
||||
# lock distro so new future defaults will not break the build
|
||||
dist: trusty
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.3
|
||||
dist: precise
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
|
||||
sudo: false
|
||||
|
||||
install:
|
||||
- composer install --no-interaction
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-text
|
||||
338
vendor/clue/socks-react/CHANGELOG.md
vendored
Executable file
338
vendor/clue/socks-react/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,338 @@
|
||||
# Changelog
|
||||
|
||||
## 0.8.7 (2017-12-17)
|
||||
|
||||
* Feature: Support SOCKS over TLS (`sockss://` URI scheme)
|
||||
(#70 and #71 by @clue)
|
||||
|
||||
```php
|
||||
// new: now supports SOCKS over TLS
|
||||
$client = new Client('socks5s://localhost', $connector);
|
||||
```
|
||||
|
||||
* Feature: Support communication over Unix domain sockets (UDS)
|
||||
(#69 by @clue)
|
||||
|
||||
```php
|
||||
// new: now supports SOCKS over Unix domain sockets (UDS)
|
||||
$client = new Client('socks5+unix:///tmp/proxy.sock', $connector);
|
||||
```
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit 6
|
||||
(#68 by @clue)
|
||||
|
||||
## 0.8.6 (2017-09-17)
|
||||
|
||||
* Feature: Forward compatibility with Evenement v3.0
|
||||
(#67 by @WyriHaximus)
|
||||
|
||||
## 0.8.5 (2017-09-01)
|
||||
|
||||
* Feature: Use socket error codes for connection rejections
|
||||
(#63 by @clue)
|
||||
|
||||
```php
|
||||
$promise = $proxy->connect('imap.example.com:143');
|
||||
$promise->then(null, function (Exeption $e) {
|
||||
if ($e->getCode() === SOCKET_EACCES) {
|
||||
echo 'Failed to authenticate with proxy!';
|
||||
}
|
||||
throw $e;
|
||||
});
|
||||
```
|
||||
|
||||
* Feature: Report matching SOCKS5 error codes for server side connection errors
|
||||
(#62 by @clue)
|
||||
|
||||
* Fix: Fix SOCKS5 client receiving destination hostnames and
|
||||
fix IPv6 addresses as hostnames for TLS certificates
|
||||
(#64 and #65 by @clue)
|
||||
|
||||
* Improve test suite by locking Travis distro so new defaults will not break the build and
|
||||
optionally exclude tests that rely on working internet connection
|
||||
(#61 and #66 by @clue)
|
||||
|
||||
## 0.8.4 (2017-07-27)
|
||||
|
||||
* Feature: Server now passes client source address to Connector
|
||||
(#60 by @clue)
|
||||
|
||||
## 0.8.3 (2017-07-18)
|
||||
|
||||
* Feature: Pass full remote URI as parameter to authentication callback
|
||||
(#58 by @clue)
|
||||
|
||||
```php
|
||||
// new third parameter passed to authentication callback
|
||||
$server->setAuth(function ($user, $pass, $remote) {
|
||||
$ip = parse_url($remote, PHP_URL_HOST);
|
||||
|
||||
return ($ip === '127.0.0.1');
|
||||
});
|
||||
```
|
||||
|
||||
* Fix: Fix connecting to IPv6 address via SOCKS5 server and validate target
|
||||
URI so hostname can not contain excessive URI components
|
||||
(#59 by @clue)
|
||||
|
||||
* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors
|
||||
(#57 by @clue)
|
||||
|
||||
## 0.8.2 (2017-05-09)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
|
||||
(#56 by @clue)
|
||||
|
||||
## 0.8.1 (2017-04-21)
|
||||
|
||||
* Update examples to use URIs with default port 1080 and accept proxy URI arguments
|
||||
(#54 by @clue)
|
||||
|
||||
* Remove now unneeded dependency on `react/stream`
|
||||
(#55 by @clue)
|
||||
|
||||
## 0.8.0 (2017-04-18)
|
||||
|
||||
* Feature: Merge `Server` class from clue/socks-server
|
||||
(#52 by @clue)
|
||||
|
||||
```php
|
||||
$socket = new React\Socket\Server(1080, $loop);
|
||||
$server = new Clue\React\Socks\Server($loop, $socket);
|
||||
```
|
||||
|
||||
> Upgrading from [clue/socks-server](https://github.com/clue/php-socks-server)?
|
||||
The classes have been moved as-is, so you can simply start using the new
|
||||
class name `Clue\React\Socks\Server` with no other changes required.
|
||||
|
||||
## 0.7.0 (2017-04-14)
|
||||
|
||||
* Feature / BC break: Replace depreacted SocketClient with Socket v0.7 and
|
||||
use `connect($uri)` instead of `create($host, $port)`
|
||||
(#51 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$connector = new React\SocketClient\TcpConnector($loop);
|
||||
$client = new Client(1080, $connector);
|
||||
$client->create('google.com', 80)->then(function (Stream $conn) {
|
||||
$conn->write("…");
|
||||
});
|
||||
|
||||
// new
|
||||
$connector = new React\Socket\TcpConnector($loop);
|
||||
$client = new Client(1080, $connector);
|
||||
$client->connect('google.com:80')->then(function (ConnectionInterface $conn) {
|
||||
$conn->write("…");
|
||||
});
|
||||
```
|
||||
|
||||
* Improve test suite by adding PHPUnit to require-dev
|
||||
(#50 by @clue)
|
||||
|
||||
## 0.6.0 (2016-11-29)
|
||||
|
||||
* Feature / BC break: Pass connector into `Client` instead of loop, remove unneeded deps
|
||||
(#49 by @clue)
|
||||
|
||||
```php
|
||||
// old (connector is create implicitly)
|
||||
$client = new Client('127.0.0.1', $loop);
|
||||
|
||||
// old (connector can optionally be passed)
|
||||
$client = new Client('127.0.0.1', $loop, $connector);
|
||||
|
||||
// new (connector is now mandatory)
|
||||
$connector = new React\SocketClient\TcpConnector($loop);
|
||||
$client = new Client('127.0.0.1', $connector);
|
||||
```
|
||||
|
||||
* Feature / BC break: `Client` now implements `ConnectorInterface`, remove `Connector` adapter
|
||||
(#47 by @clue)
|
||||
|
||||
```php
|
||||
// old (explicit connector functions as an adapter)
|
||||
$connector = $client->createConnector();
|
||||
$promise = $connector->create('google.com', 80);
|
||||
|
||||
// new (client can be used as connector right away)
|
||||
$promise = $client->create('google.com', 80);
|
||||
```
|
||||
|
||||
* Feature / BC break: Remove `createSecureConnector()`, use `SecureConnector` instead
|
||||
(#47 by @clue)
|
||||
|
||||
```php
|
||||
// old (tight coupling and hidden dependency)
|
||||
$tls = $client->createSecureConnector();
|
||||
$promise = $tls->create('google.com', 443);
|
||||
|
||||
// new (more explicit, loose coupling)
|
||||
$tls = new React\SocketClient\SecureConnector($client, $loop);
|
||||
$promise = $tls->create('google.com', 443);
|
||||
```
|
||||
|
||||
* Feature / BC break: Remove `setResolveLocal()` and local DNS resolution and default to remote DNS resolution, use `DnsConnector` instead
|
||||
(#44 by @clue)
|
||||
|
||||
```php
|
||||
// old (implicitly defaults to true, can be disabled)
|
||||
$client->setResolveLocal(false);
|
||||
$tcp = $client->createConnector();
|
||||
$promise = $tcp->create('google.com', 80);
|
||||
|
||||
// new (always disabled, can be re-enabled like this)
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$resolver = $factory->createCached('8.8.8.8', $loop);
|
||||
$tcp = new React\SocketClient\DnsConnector($client, $resolver);
|
||||
$promise = $tcp->create('google.com', 80);
|
||||
```
|
||||
|
||||
* Feature / BC break: Remove `setTimeout()`, use `TimeoutConnector` instead
|
||||
(#45 by @clue)
|
||||
|
||||
```php
|
||||
// old (timeout only applies to TCP/IP connection)
|
||||
$client = new Client('127.0.0.1', …);
|
||||
$client->setTimeout(3.0);
|
||||
$tcp = $client->createConnector();
|
||||
$promise = $tcp->create('google.com', 80);
|
||||
|
||||
// new (timeout can be added to any layer)
|
||||
$client = new Client('127.0.0.1', …);
|
||||
$tcp = new React\SocketClient\TimeoutConnector($client, 3.0, $loop);
|
||||
$promise = $tcp->create('google.com', 80);
|
||||
```
|
||||
|
||||
* Feature / BC break: Remove `setProtocolVersion()` and `setAuth()` mutators, only support SOCKS URI for protocol version and authentication (immutable API)
|
||||
(#46 by @clue)
|
||||
|
||||
```php
|
||||
// old (state can be mutated after instantiation)
|
||||
$client = new Client('127.0.0.1', …);
|
||||
$client->setProtocolVersion('5');
|
||||
$client->setAuth('user', 'pass');
|
||||
|
||||
// new (immutable after construction, already supported as of v0.5.2 - now mandatory)
|
||||
$client = new Client('socks5://user:pass@127.0.0.1', …);
|
||||
```
|
||||
|
||||
## 0.5.2 (2016-11-25)
|
||||
|
||||
* Feature: Apply protocol version and username/password auth from SOCKS URI
|
||||
(#43 by @clue)
|
||||
|
||||
```php
|
||||
// explicitly use SOCKS5
|
||||
$client = new Client('socks5://127.0.0.1', $loop);
|
||||
|
||||
// use authentication (automatically SOCKS5)
|
||||
$client = new Client('user:pass@127.0.0.1', $loop);
|
||||
```
|
||||
|
||||
* More explicit client examples, including proxy chaining
|
||||
(#42 by @clue)
|
||||
|
||||
## 0.5.1 (2016-11-21)
|
||||
|
||||
* Feature: Support Promise cancellation
|
||||
(#39 by @clue)
|
||||
|
||||
```php
|
||||
$promise = $connector->create($host, $port);
|
||||
|
||||
$promise->cancel();
|
||||
```
|
||||
|
||||
* Feature: Timeout now cancels pending connection attempt
|
||||
(#39, #22 by @clue)
|
||||
|
||||
## 0.5.0 (2016-11-07)
|
||||
|
||||
* Remove / BC break: Split off Server to clue/socks-server
|
||||
(#35 by @clue)
|
||||
|
||||
> Upgrading? Check [clue/socks-server](https://github.com/clue/php-socks-server) for details.
|
||||
|
||||
* Improve documentation and project structure
|
||||
|
||||
## 0.4.0 (2016-03-19)
|
||||
|
||||
* Feature: Support proper SSL/TLS connections with additional SSL context options
|
||||
(#31, #33 by @clue)
|
||||
|
||||
* Documentation for advanced Connector setups (bindto, multihop)
|
||||
(#32 by @clue)
|
||||
|
||||
## 0.3.0 (2015-06-20)
|
||||
|
||||
* BC break / Feature: Client ctor now accepts a SOCKS server URI
|
||||
([#24](https://github.com/clue/php-socks-react/pull/24))
|
||||
|
||||
```php
|
||||
// old
|
||||
$client = new Client($loop, 'localhost', 9050);
|
||||
|
||||
// new
|
||||
$client = new Client('localhost:9050', $loop);
|
||||
```
|
||||
|
||||
* Feature: Automatically assume default SOCKS port (1080) if not given explicitly
|
||||
([#26](https://github.com/clue/php-socks-react/pull/26))
|
||||
|
||||
* Improve documentation and test suite
|
||||
|
||||
## 0.2.1 (2014-11-13)
|
||||
|
||||
* Support React PHP v0.4 (while preserving BC with React PHP v0.3)
|
||||
([#16](https://github.com/clue/php-socks-react/pull/16))
|
||||
|
||||
* Improve examples and add first class support for HHVM
|
||||
([#15](https://github.com/clue/php-socks-react/pull/15) and [#17](https://github.com/clue/php-socks-react/pull/17))
|
||||
|
||||
## 0.2.0 (2014-09-27)
|
||||
|
||||
* BC break / Feature: Simplify constructors by making parameters optional.
|
||||
([#10](https://github.com/clue/php-socks-react/pull/10))
|
||||
|
||||
The `Factory` has been removed, you can now create instances of the `Client`
|
||||
and `Server` yourself:
|
||||
|
||||
```php
|
||||
// old
|
||||
$factory = new Factory($loop, $dns);
|
||||
$client = $factory->createClient('localhost', 9050);
|
||||
$server = $factory->createSever($socket);
|
||||
|
||||
// new
|
||||
$client = new Client($loop, 'localhost', 9050);
|
||||
$server = new Server($loop, $socket);
|
||||
```
|
||||
|
||||
* BC break: Remove HTTP support and link to [clue/buzz-react](https://github.com/clue/php-buzz-react) instead.
|
||||
([#9](https://github.com/clue/php-socks-react/pull/9))
|
||||
|
||||
HTTP operates on a different layer than this low-level SOCKS library.
|
||||
Removing this reduces the footprint of this library.
|
||||
|
||||
> Upgrading? Check the [README](https://github.com/clue/php-socks-react#http-requests) for details.
|
||||
|
||||
* Fix: Refactored to support other, faster loops (libev/libevent)
|
||||
([#12](https://github.com/clue/php-socks-react/pull/12))
|
||||
|
||||
* Explicitly list dependencies, clean up examples and extend test suite significantly
|
||||
|
||||
## 0.1.0 (2014-05-19)
|
||||
|
||||
* First stable release
|
||||
* Async SOCKS `Client` and `Server` implementation
|
||||
* Project was originally part of [clue/socks](https://github.com/clue/php-socks)
|
||||
and was split off from its latest releave v0.4.0
|
||||
([#1](https://github.com/clue/reactphp-socks/issues/1))
|
||||
|
||||
> Upgrading from clue/socks v0.4.0? Use namespace `Clue\React\Socks` instead of `Socks` and you're ready to go!
|
||||
|
||||
## 0.0.0 (2011-04-26)
|
||||
|
||||
* Initial concept, originally tracked as part of
|
||||
[clue/socks](https://github.com/clue/php-socks)
|
||||
21
vendor/clue/socks-react/LICENSE
vendored
Executable file
21
vendor/clue/socks-react/LICENSE
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
1017
vendor/clue/socks-react/README.md
vendored
Executable file
1017
vendor/clue/socks-react/README.md
vendored
Executable file
File diff suppressed because it is too large
Load Diff
28
vendor/clue/socks-react/composer.json
vendored
Executable file
28
vendor/clue/socks-react/composer.json
vendored
Executable file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "clue/socks-react",
|
||||
"description": "Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of ReactPHP",
|
||||
"keywords": ["socks client", "socks server", "proxy", "tcp tunnel", "socks protocol", "async", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/php-socks-react",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {"Clue\\React\\Socks\\": "src/"}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/socket": "^1.0 || ^0.8.6",
|
||||
"react/promise": "^2.1 || ^1.2",
|
||||
"evenement/evenement": "~3.0|~1.0|~2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.0 || ^5.7 || ^4.8.35",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
|
||||
"clue/connection-manager-extra": "^1.0 || ^0.7",
|
||||
"clue/block-react": "^1.1"
|
||||
}
|
||||
}
|
||||
30
vendor/clue/socks-react/examples/01-http.php
vendored
Executable file
30
vendor/clue/socks-react/examples/01-http.php
vendored
Executable file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$proxy = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$client = new Client($proxy, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $client,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
|
||||
|
||||
$connector->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
|
||||
echo 'connected' . PHP_EOL;
|
||||
$stream->write("GET / HTTP/1.0\r\n\r\n");
|
||||
$stream->on('data', function ($data) {
|
||||
echo $data;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
30
vendor/clue/socks-react/examples/02-https.php
vendored
Executable file
30
vendor/clue/socks-react/examples/02-https.php
vendored
Executable file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$proxy = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$client = new Client($proxy, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $client,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
|
||||
|
||||
$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
echo 'connected' . PHP_EOL;
|
||||
$stream->write("GET / HTTP/1.0\r\n\r\n");
|
||||
$stream->on('data', function ($data) {
|
||||
echo $data;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
46
vendor/clue/socks-react/examples/03-proxy-chaining.php
vendored
Executable file
46
vendor/clue/socks-react/examples/03-proxy-chaining.php
vendored
Executable file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (!isset($argv[1])) {
|
||||
echo 'No arguments given! Run with <proxy1> [<proxyN>...]' . PHP_EOL;
|
||||
echo 'You can add 1..n proxies in the path' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$path = array_slice($argv, 1);
|
||||
|
||||
// Alternatively, you can also hard-code this value like this:
|
||||
//$path = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// set next SOCKS server chain via p1 -> p2 -> p3 -> destination
|
||||
$connector = new Connector($loop);
|
||||
foreach ($path as $proxy) {
|
||||
$connector = new Client($proxy, $connector);
|
||||
}
|
||||
|
||||
// please note how the client uses p3 (not p1!), which in turn then uses the complete chain
|
||||
// this creates a TCP/IP connection to p1, which then connects to p2, then to p3, which then connects to the target
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $connector,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
echo 'Demo SOCKS client connecting to SOCKS proxy server chain ' . implode(' -> ', $path) . PHP_EOL;
|
||||
|
||||
$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
echo 'connected' . PHP_EOL;
|
||||
$stream->write("GET / HTTP/1.0\r\n\r\n");
|
||||
$stream->on('data', function ($data) {
|
||||
echo $data;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
31
vendor/clue/socks-react/examples/04-local-dns.php
vendored
Executable file
31
vendor/clue/socks-react/examples/04-local-dns.php
vendored
Executable file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$proxy = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// set up DNS server to use (Google's public DNS)
|
||||
$client = new Client($proxy, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $client,
|
||||
'timeout' => 3.0,
|
||||
'dns' => '8.8.8.8'
|
||||
));
|
||||
|
||||
echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
|
||||
|
||||
$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
echo 'connected' . PHP_EOL;
|
||||
$stream->write("GET / HTTP/1.0\r\n\r\n");
|
||||
$stream->on('data', function ($data) {
|
||||
echo $data;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
19
vendor/clue/socks-react/examples/11-server.php
vendored
Executable file
19
vendor/clue/socks-react/examples/11-server.php
vendored
Executable file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Server;
|
||||
use React\Socket\Server as Socket;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// listen on 127.0.0.1:1080 or first argument
|
||||
$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
$socket = new Socket($listen, $loop);
|
||||
|
||||
// start a new server listening for incoming connection on the given socket
|
||||
$server = new Server($loop, $socket);
|
||||
|
||||
echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
24
vendor/clue/socks-react/examples/12-server-with-password.php
vendored
Executable file
24
vendor/clue/socks-react/examples/12-server-with-password.php
vendored
Executable file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Server;
|
||||
use React\Socket\Server as Socket;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// listen on 127.0.0.1:1080 or first argument
|
||||
$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
$socket = new Socket($listen, $loop);
|
||||
|
||||
// start a new server listening for incoming connection on the given socket
|
||||
// require authentication and hence make this a SOCKS5-only server
|
||||
$server = new Server($loop, $socket);
|
||||
$server->setAuthArray(array(
|
||||
'tom' => 'god',
|
||||
'user' => 'p@ssw0rd'
|
||||
));
|
||||
|
||||
echo 'SOCKS5 server requiring authentication listening on ' . $socket->getAddress() . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
40
vendor/clue/socks-react/examples/13-server-blacklist.php
vendored
Executable file
40
vendor/clue/socks-react/examples/13-server-blacklist.php
vendored
Executable file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
// A SOCKS server that rejects connections to some domains (blacklist / filtering)
|
||||
|
||||
use React\EventLoop\Factory as LoopFactory;
|
||||
use ConnectionManager\Extra\Multiple\ConnectionManagerSelective;
|
||||
use React\Socket\Server as Socket;
|
||||
use Clue\React\Socks\Server;
|
||||
use ConnectionManager\Extra\ConnectionManagerReject;
|
||||
use React\Socket\Connector;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = LoopFactory::create();
|
||||
|
||||
// create a connector that rejects the connection
|
||||
$reject = new ConnectionManagerReject();
|
||||
|
||||
// create an actual connector that establishes real connections
|
||||
$permit = new Connector($loop);
|
||||
|
||||
// this connector selectively picks one of the the attached connectors depending on the target address
|
||||
// reject youtube.com and unencrypted HTTP for google.com
|
||||
// default connctor: permit everything
|
||||
$connector = new ConnectionManagerSelective(array(
|
||||
'*.youtube.com' => $reject,
|
||||
'www.google.com:80' => $reject,
|
||||
'*' => $permit
|
||||
));
|
||||
|
||||
// listen on 127.0.0.1:1080 or first argument
|
||||
$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
$socket = new Socket($listen, $loop);
|
||||
|
||||
// start the actual socks server on the given server socket and using our connection manager for outgoing connections
|
||||
$server = new Server($loop, $socket, $connector);
|
||||
|
||||
echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
42
vendor/clue/socks-react/examples/21-server-proxy-chaining.php
vendored
Executable file
42
vendor/clue/socks-react/examples/21-server-proxy-chaining.php
vendored
Executable file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
// A SOCKS server that forwards (proxy chaining) to other SOCKS servers
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use Clue\React\Socks\Server;
|
||||
use React\Socket\Server as Socket;
|
||||
use React\Socket\Connector;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (!isset($argv[2])) {
|
||||
echo 'No arguments given! Run with <listen> <proxy1> [<proxyN>...]' . PHP_EOL;
|
||||
echo 'You can add 1..n proxies in the path' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$listen = $argv[1];
|
||||
$path = array_slice($argv, 2);
|
||||
|
||||
// Alternatively, you can also hard-code these values like this:
|
||||
//$listen = '127.0.0.1:9050';
|
||||
//$path = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// set next SOCKS server chain -> p1 -> p2 -> p3 -> destination
|
||||
$connector = new Connector($loop);
|
||||
foreach ($path as $proxy) {
|
||||
$connector = new Client($proxy, $connector);
|
||||
}
|
||||
|
||||
// listen on 127.0.0.1:1080 or first argument
|
||||
$socket = new Socket($listen, $loop);
|
||||
|
||||
// start a new server which forwards all connections to the other SOCKS server
|
||||
$server = new Server($loop, $socket, $connector);
|
||||
|
||||
echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
|
||||
echo 'Forwarding via: ' . implode(' -> ', $path) . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
46
vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php
vendored
Executable file
46
vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php
vendored
Executable file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
// A SOCKS server that randomly forwards (proxy chaining) to a pool of SOCKS servers
|
||||
|
||||
use React\EventLoop\Factory as LoopFactory;
|
||||
use ConnectionManager\Extra\Multiple\ConnectionManagerRandom;
|
||||
use React\Socket\Server as Socket;
|
||||
use Clue\React\Socks\Server;
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (!isset($argv[3])) {
|
||||
echo 'No arguments given! Run with <listen> <proxy1> <proxyN>...' . PHP_EOL;
|
||||
echo 'You can add 2..n proxies in the pool' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$listen = $argv[1];
|
||||
$pool = array_slice($argv, 2);
|
||||
|
||||
// Alternatively, you can also hard-code these values like this:
|
||||
//$listen = '127.0.0.1:9050';
|
||||
//$pool = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
|
||||
|
||||
$loop = LoopFactory::create();
|
||||
|
||||
// forward to socks server listening on 127.0.0.1:9051-9053
|
||||
// this connector randomly picks one of the the attached connectors from the pool
|
||||
$connector = new Connector($loop);
|
||||
$clients = array();
|
||||
foreach ($pool as $proxy) {
|
||||
$clients []= new Client($proxy, $connector);
|
||||
}
|
||||
$connector = new ConnectionManagerRandom($clients);
|
||||
|
||||
$socket = new Socket($listen, $loop);
|
||||
|
||||
// start the actual socks server on the given server socket and using our connection manager for outgoing connections
|
||||
$server = new Server($loop, $socket, $connector);
|
||||
|
||||
echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
|
||||
echo 'Randomly picking from: ' . implode(', ', $pool) . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
21
vendor/clue/socks-react/examples/31-server-secure.php
vendored
Executable file
21
vendor/clue/socks-react/examples/31-server-secure.php
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Server;
|
||||
use React\Socket\Server as Socket;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// listen on tls://127.0.0.1:1080 or first argument
|
||||
$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
$socket = new Socket('tls://' . $listen, $loop, array('tls' => array(
|
||||
'local_cert' => __DIR__ . '/localhost.pem',
|
||||
)));
|
||||
|
||||
// start a new server listening for incoming connection on the given socket
|
||||
$server = new Server($loop, $socket);
|
||||
|
||||
echo 'SOCKS over TLS server listening on ' . str_replace('tls:', 'sockss:', $socket->getAddress()) . PHP_EOL;
|
||||
|
||||
$loop->run();
|
||||
33
vendor/clue/socks-react/examples/32-http-secure.php
vendored
Executable file
33
vendor/clue/socks-react/examples/32-http-secure.php
vendored
Executable file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$proxy = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$client = new Client('sockss://' . $proxy, new Connector($loop, array('tls' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false
|
||||
))));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $client,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
echo 'Demo SOCKS over TLS client connecting to secure SOCKS server ' . $proxy . PHP_EOL;
|
||||
|
||||
$connector->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
|
||||
echo 'connected' . PHP_EOL;
|
||||
$stream->write("GET / HTTP/1.0\r\n\r\n");
|
||||
$stream->on('data', function ($data) {
|
||||
echo $data;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
49
vendor/clue/socks-react/examples/localhost.pem
vendored
Executable file
49
vendor/clue/socks-react/examples/localhost.pem
vendored
Executable file
@@ -0,0 +1,49 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
|
||||
MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
|
||||
DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
|
||||
MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
|
||||
EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
|
||||
eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
|
||||
W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
|
||||
2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
|
||||
zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
|
||||
UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
|
||||
wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
|
||||
YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
|
||||
BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
|
||||
CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
|
||||
0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
|
||||
7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
|
||||
824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
|
||||
V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
|
||||
IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
|
||||
W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
|
||||
2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
|
||||
zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
|
||||
UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
|
||||
wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
|
||||
YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
|
||||
ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
|
||||
g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
|
||||
tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
|
||||
LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
|
||||
tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
|
||||
9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
|
||||
43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
|
||||
pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
|
||||
OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
|
||||
2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
|
||||
li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
|
||||
b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
|
||||
vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
|
||||
XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
|
||||
Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
|
||||
iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
|
||||
+EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
|
||||
y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
|
||||
81oh1uCH1YPLM29hPyaohxL8
|
||||
-----END PRIVATE KEY-----
|
||||
14
vendor/clue/socks-react/phpunit.xml.dist
vendored
Executable file
14
vendor/clue/socks-react/phpunit.xml.dist
vendored
Executable file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit colors="true" bootstrap="./tests/bootstrap.php">
|
||||
<testsuites>
|
||||
<testsuite name="Socks Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
382
vendor/clue/socks-react/src/Client.php
vendored
Executable file
382
vendor/clue/socks-react/src/Client.php
vendored
Executable file
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\FixedUriConnector;
|
||||
use \Exception;
|
||||
use \InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class Client implements ConnectorInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var ConnectorInterface
|
||||
*/
|
||||
private $connector;
|
||||
|
||||
private $socksUri;
|
||||
|
||||
private $protocolVersion = null;
|
||||
|
||||
private $auth = null;
|
||||
|
||||
public function __construct($socksUri, ConnectorInterface $connector)
|
||||
{
|
||||
// support `sockss://` scheme for SOCKS over TLS
|
||||
// support `socks+unix://` scheme for Unix domain socket (UDS) paths
|
||||
if (preg_match('/^(socks(?:5|4|4a)?)(s|\+unix):\/\/(.*?@)?(.+?)$/', $socksUri, $match)) {
|
||||
// rewrite URI to parse SOCKS scheme, authentication and dummy host
|
||||
$socksUri = $match[1] . '://' . $match[3] . 'localhost';
|
||||
|
||||
// connector uses appropriate transport scheme and explicit host given
|
||||
$connector = new FixedUriConnector(
|
||||
($match[2] === 's' ? 'tls://' : 'unix://') . $match[4],
|
||||
$connector
|
||||
);
|
||||
}
|
||||
|
||||
// assume default scheme if none is given
|
||||
if (strpos($socksUri, '://') === false) {
|
||||
$socksUri = 'socks://' . $socksUri;
|
||||
}
|
||||
|
||||
// parse URI into individual parts
|
||||
$parts = parse_url($socksUri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'])) {
|
||||
throw new \InvalidArgumentException('Invalid SOCKS server URI "' . $socksUri . '"');
|
||||
}
|
||||
|
||||
// assume default port
|
||||
if (!isset($parts['port'])) {
|
||||
$parts['port'] = 1080;
|
||||
}
|
||||
|
||||
// user or password in URI => SOCKS5 authentication
|
||||
if (isset($parts['user']) || isset($parts['pass'])) {
|
||||
if ($parts['scheme'] === 'socks') {
|
||||
// default to using SOCKS5 if not given explicitly
|
||||
$parts['scheme'] = 'socks5';
|
||||
} elseif ($parts['scheme'] !== 'socks5') {
|
||||
// fail if any other protocol version given explicitly
|
||||
throw new InvalidArgumentException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
|
||||
}
|
||||
$parts += array('user' => '', 'pass' => '');
|
||||
$this->setAuth(rawurldecode($parts['user']), rawurldecode($parts['pass']));
|
||||
}
|
||||
|
||||
// check for valid protocol version from URI scheme
|
||||
$this->setProtocolVersionFromScheme($parts['scheme']);
|
||||
|
||||
$this->socksUri = $parts['host'] . ':' . $parts['port'];
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
private function setProtocolVersionFromScheme($scheme)
|
||||
{
|
||||
if ($scheme === 'socks' || $scheme === 'socks4a') {
|
||||
$this->protocolVersion = '4a';
|
||||
} elseif ($scheme === 'socks5') {
|
||||
$this->protocolVersion = '5';
|
||||
} elseif ($scheme === 'socks4') {
|
||||
$this->protocolVersion = '4';
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid protocol version given "' . $scheme . '://"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set login data for username/password authentication method (RFC1929)
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @link http://tools.ietf.org/html/rfc1929
|
||||
*/
|
||||
private function setAuth($username, $password)
|
||||
{
|
||||
if (strlen($username) > 255 || strlen($password) > 255) {
|
||||
throw new InvalidArgumentException('Both username and password MUST NOT exceed a length of 255 bytes each');
|
||||
}
|
||||
$this->auth = pack('C2', 0x01, strlen($username)) . $username . pack('C', strlen($password)) . $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish a TCP/IP connection to the given target URI through the SOCKS server
|
||||
*
|
||||
* Many higher-level networking protocols build on top of TCP. It you're dealing
|
||||
* with one such client implementation, it probably uses/accepts an instance
|
||||
* implementing React's `ConnectorInterface` (and usually its default `Connector`
|
||||
* instance). In this case you can also pass this `Connector` instance instead
|
||||
* to make this client implementation SOCKS-aware. That's it.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return PromiseInterface Promise<ConnectionInterface,Exception>
|
||||
*/
|
||||
public function connect($uri)
|
||||
{
|
||||
if (strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
}
|
||||
|
||||
$parts = parse_url($uri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
|
||||
}
|
||||
|
||||
$host = trim($parts['host'], '[]');
|
||||
$port = $parts['port'];
|
||||
|
||||
if ($this->protocolVersion === '4' && false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
return Promise\reject(new InvalidArgumentException('Requires an IPv4 address for SOCKS4'));
|
||||
}
|
||||
|
||||
if (strlen($host) > 255 || $port > 65535 || $port < 0 || (string)$port !== (string)(int)$port) {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid target specified'));
|
||||
}
|
||||
|
||||
// construct URI to SOCKS server to connect to
|
||||
$socksUri = $this->socksUri;
|
||||
|
||||
// append path from URI if given
|
||||
if (isset($parts['path'])) {
|
||||
$socksUri .= $parts['path'];
|
||||
}
|
||||
|
||||
// parse query args
|
||||
$args = array();
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $args);
|
||||
}
|
||||
|
||||
// append hostname from URI to query string unless explicitly given
|
||||
if (!isset($args['hostname'])) {
|
||||
$args['hostname'] = $host;
|
||||
}
|
||||
|
||||
// append query string
|
||||
$socksUri .= '?' . http_build_query($args, '', '&');
|
||||
|
||||
// append fragment from URI if given
|
||||
if (isset($parts['fragment'])) {
|
||||
$socksUri .= '#' . $parts['fragment'];
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
|
||||
// start TCP/IP connection to SOCKS server and then
|
||||
// handle SOCKS protocol once connection is ready
|
||||
// resolve plain connection once SOCKS protocol is completed
|
||||
return $this->connector->connect($socksUri)->then(
|
||||
function (ConnectionInterface $stream) use ($that, $host, $port) {
|
||||
return $that->handleConnectedSocks($stream, $host, $port);
|
||||
},
|
||||
function (Exception $e) {
|
||||
throw new RuntimeException('Unable to connect to proxy (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, $e);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper used to handle the communication with the SOCKS server
|
||||
*
|
||||
* @param ConnectionInterface $stream
|
||||
* @param string $host
|
||||
* @param int $port
|
||||
* @return Promise Promise<ConnectionInterface, Exception>
|
||||
* @internal
|
||||
*/
|
||||
public function handleConnectedSocks(ConnectionInterface $stream, $host, $port)
|
||||
{
|
||||
$deferred = new Deferred(function ($_, $reject) {
|
||||
$reject(new RuntimeException('Connection canceled while establishing SOCKS session (ECONNABORTED)', defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103));
|
||||
});
|
||||
|
||||
$reader = new StreamReader();
|
||||
$stream->on('data', array($reader, 'write'));
|
||||
|
||||
$stream->on('error', $onError = function (Exception $e) use ($deferred) {
|
||||
$deferred->reject(new RuntimeException('Stream error while waiting for response from proxy (EIO)', defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e));
|
||||
});
|
||||
|
||||
$stream->on('close', $onClose = function () use ($deferred) {
|
||||
$deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
|
||||
});
|
||||
|
||||
if ($this->protocolVersion === '5') {
|
||||
$promise = $this->handleSocks5($stream, $host, $port, $reader);
|
||||
} else {
|
||||
$promise = $this->handleSocks4($stream, $host, $port, $reader);
|
||||
}
|
||||
$promise->then(function () use ($deferred, $stream) {
|
||||
$deferred->resolve($stream);
|
||||
}, function (Exception $error) use ($deferred) {
|
||||
// pass custom RuntimeException through as-is, otherwise wrap in protocol error
|
||||
if (!$error instanceof RuntimeException) {
|
||||
$error = new RuntimeException('Invalid response received from proxy (EBADMSG)', defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71, $error);
|
||||
}
|
||||
|
||||
$deferred->reject($error);
|
||||
});
|
||||
|
||||
return $deferred->promise()->then(
|
||||
function (ConnectionInterface $stream) use ($reader, $onError, $onClose) {
|
||||
$stream->removeListener('data', array($reader, 'write'));
|
||||
$stream->removeListener('error', $onError);
|
||||
$stream->removeListener('close', $onClose);
|
||||
|
||||
return $stream;
|
||||
},
|
||||
function ($error) use ($stream, $onClose) {
|
||||
$stream->removeListener('close', $onClose);
|
||||
$stream->close();
|
||||
|
||||
throw $error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function handleSocks4(ConnectionInterface $stream, $host, $port, StreamReader $reader)
|
||||
{
|
||||
// do not resolve hostname. only try to convert to IP
|
||||
$ip = ip2long($host);
|
||||
|
||||
// send IP or (0.0.0.1) if invalid
|
||||
$data = pack('C2nNC', 0x04, 0x01, $port, $ip === false ? 1 : $ip, 0x00);
|
||||
|
||||
if ($ip === false) {
|
||||
// host is not a valid IP => send along hostname (SOCKS4a)
|
||||
$data .= $host . pack('C', 0x00);
|
||||
}
|
||||
|
||||
$stream->write($data);
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'null' => 'C',
|
||||
'status' => 'C',
|
||||
'port' => 'n',
|
||||
'ip' => 'N'
|
||||
))->then(function ($data) {
|
||||
if ($data['null'] !== 0x00) {
|
||||
throw new Exception('Invalid SOCKS response');
|
||||
}
|
||||
if ($data['status'] !== 0x5a) {
|
||||
throw new RuntimeException('Proxy refused connection with SOCKS error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamReader $reader)
|
||||
{
|
||||
// protocol version 5
|
||||
$data = pack('C', 0x05);
|
||||
|
||||
$auth = $this->auth;
|
||||
if ($auth === null) {
|
||||
// one method, no authentication
|
||||
$data .= pack('C2', 0x01, 0x00);
|
||||
} else {
|
||||
// two methods, username/password and no authentication
|
||||
$data .= pack('C3', 0x02, 0x02, 0x00);
|
||||
}
|
||||
$stream->write($data);
|
||||
|
||||
$that = $this;
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'method' => 'C'
|
||||
))->then(function ($data) use ($auth, $stream, $reader) {
|
||||
if ($data['version'] !== 0x05) {
|
||||
throw new Exception('Version/Protocol mismatch');
|
||||
}
|
||||
|
||||
if ($data['method'] === 0x02 && $auth !== null) {
|
||||
// username/password authentication requested and provided
|
||||
$stream->write($auth);
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'status' => 'C'
|
||||
))->then(function ($data) {
|
||||
if ($data['version'] !== 0x01 || $data['status'] !== 0x00) {
|
||||
throw new RuntimeException('Username/Password authentication failed (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
|
||||
}
|
||||
});
|
||||
} else if ($data['method'] !== 0x00) {
|
||||
// any other method than "no authentication"
|
||||
throw new RuntimeException('No acceptable authentication method found (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
|
||||
}
|
||||
})->then(function () use ($stream, $reader, $host, $port) {
|
||||
// do not resolve hostname. only try to convert to (binary/packed) IP
|
||||
$ip = @inet_pton($host);
|
||||
|
||||
$data = pack('C3', 0x05, 0x01, 0x00);
|
||||
if ($ip === false) {
|
||||
// not an IP, send as hostname
|
||||
$data .= pack('C2', 0x03, strlen($host)) . $host;
|
||||
} else {
|
||||
// send as IPv4 / IPv6
|
||||
$data .= pack('C', (strpos($host, ':') === false) ? 0x01 : 0x04) . $ip;
|
||||
}
|
||||
$data .= pack('n', $port);
|
||||
|
||||
$stream->write($data);
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'status' => 'C',
|
||||
'null' => 'C',
|
||||
'type' => 'C'
|
||||
));
|
||||
})->then(function ($data) use ($reader) {
|
||||
if ($data['version'] !== 0x05 || $data['null'] !== 0x00) {
|
||||
throw new Exception('Invalid SOCKS response');
|
||||
}
|
||||
if ($data['status'] !== 0x00) {
|
||||
// map limited list of SOCKS error codes to common socket error conditions
|
||||
// @link https://tools.ietf.org/html/rfc1928#section-6
|
||||
if ($data['status'] === Server::ERROR_GENERAL) {
|
||||
throw new RuntimeException('SOCKS server reported a general server failure (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
|
||||
} elseif ($data['status'] === Server::ERROR_NOT_ALLOWED_BY_RULESET) {
|
||||
throw new RuntimeException('SOCKS server reported connection is not allowed by ruleset (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
|
||||
} elseif ($data['status'] === Server::ERROR_NETWORK_UNREACHABLE) {
|
||||
throw new RuntimeException('SOCKS server reported network unreachable (ENETUNREACH)', defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101);
|
||||
} elseif ($data['status'] === Server::ERROR_HOST_UNREACHABLE) {
|
||||
throw new RuntimeException('SOCKS server reported host unreachable (EHOSTUNREACH)', defined('SOCKET_EHOSTUNREACH') ? SOCKET_EHOSTUNREACH : 113);
|
||||
} elseif ($data['status'] === Server::ERROR_CONNECTION_REFUSED) {
|
||||
throw new RuntimeException('SOCKS server reported connection refused (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
|
||||
} elseif ($data['status'] === Server::ERROR_TTL) {
|
||||
throw new RuntimeException('SOCKS server reported TTL/timeout expired (ETIMEDOUT)', defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110);
|
||||
} elseif ($data['status'] === Server::ERROR_COMMAND_UNSUPPORTED) {
|
||||
throw new RuntimeException('SOCKS server does not support the CONNECT command (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
|
||||
} elseif ($data['status'] === Server::ERROR_ADDRESS_UNSUPPORTED) {
|
||||
throw new RuntimeException('SOCKS server does not support this address type (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
|
||||
}
|
||||
|
||||
throw new RuntimeException('SOCKS server reported an unassigned error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
|
||||
}
|
||||
if ($data['type'] === 0x01) {
|
||||
// IPv4 address => skip IP and port
|
||||
return $reader->readLength(6);
|
||||
} elseif ($data['type'] === 0x03) {
|
||||
// domain name => read domain name length
|
||||
return $reader->readBinary(array(
|
||||
'length' => 'C'
|
||||
))->then(function ($data) use ($reader) {
|
||||
// skip domain name and port
|
||||
return $reader->readLength($data['length'] + 2);
|
||||
});
|
||||
} elseif ($data['type'] === 0x04) {
|
||||
// IPv6 address => skip IP and port
|
||||
return $reader->readLength(18);
|
||||
} else {
|
||||
throw new Exception('Invalid SOCKS reponse: Invalid address type');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
422
vendor/clue/socks-react/src/Server.php
vendored
Executable file
422
vendor/clue/socks-react/src/Server.php
vendored
Executable file
@@ -0,0 +1,422 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Socket\ServerInterface;
|
||||
use React\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use \UnexpectedValueException;
|
||||
use \InvalidArgumentException;
|
||||
use \Exception;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
|
||||
class Server extends EventEmitter
|
||||
{
|
||||
// the following error codes are only used for SOCKS5 only
|
||||
/** @internal */
|
||||
const ERROR_GENERAL = 0x01;
|
||||
/** @internal */
|
||||
const ERROR_NOT_ALLOWED_BY_RULESET = 0x02;
|
||||
/** @internal */
|
||||
const ERROR_NETWORK_UNREACHABLE = 0x03;
|
||||
/** @internal */
|
||||
const ERROR_HOST_UNREACHABLE = 0x04;
|
||||
/** @internal */
|
||||
const ERROR_CONNECTION_REFUSED = 0x05;
|
||||
/** @internal */
|
||||
const ERROR_TTL = 0x06;
|
||||
/** @internal */
|
||||
const ERROR_COMMAND_UNSUPPORTED = 0x07;
|
||||
/** @internal */
|
||||
const ERROR_ADDRESS_UNSUPPORTED = 0x08;
|
||||
|
||||
protected $loop;
|
||||
|
||||
private $connector;
|
||||
|
||||
private $auth = null;
|
||||
|
||||
private $protocolVersion = null;
|
||||
|
||||
public function __construct(LoopInterface $loop, ServerInterface $serverInterface, ConnectorInterface $connector = null)
|
||||
{
|
||||
if ($connector === null) {
|
||||
$connector = new Connector($loop);
|
||||
}
|
||||
|
||||
$this->loop = $loop;
|
||||
$this->connector = $connector;
|
||||
|
||||
$that = $this;
|
||||
$serverInterface->on('connection', function ($connection) use ($that) {
|
||||
$that->emit('connection', array($connection));
|
||||
$that->onConnection($connection);
|
||||
});
|
||||
}
|
||||
|
||||
public function setProtocolVersion($version)
|
||||
{
|
||||
if ($version !== null) {
|
||||
$version = (string)$version;
|
||||
if (!in_array($version, array('4', '4a', '5'), true)) {
|
||||
throw new InvalidArgumentException('Invalid protocol version given');
|
||||
}
|
||||
if ($version !== '5' && $this->auth !== null){
|
||||
throw new UnexpectedValueException('Unable to change protocol version to anything but SOCKS5 while authentication is used. Consider removing authentication info or sticking to SOCKS5');
|
||||
}
|
||||
}
|
||||
$this->protocolVersion = $version;
|
||||
}
|
||||
|
||||
public function setAuth($auth)
|
||||
{
|
||||
if (!is_callable($auth)) {
|
||||
throw new InvalidArgumentException('Given authenticator is not a valid callable');
|
||||
}
|
||||
if ($this->protocolVersion !== null && $this->protocolVersion !== '5') {
|
||||
throw new UnexpectedValueException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
|
||||
}
|
||||
// wrap authentication callback in order to cast its return value to a promise
|
||||
$this->auth = function($username, $password, $remote) use ($auth) {
|
||||
$ret = call_user_func($auth, $username, $password, $remote);
|
||||
if ($ret instanceof PromiseInterface) {
|
||||
return $ret;
|
||||
}
|
||||
$deferred = new Deferred();
|
||||
$ret ? $deferred->resolve() : $deferred->reject();
|
||||
return $deferred->promise();
|
||||
};
|
||||
}
|
||||
|
||||
public function setAuthArray(array $login)
|
||||
{
|
||||
$this->setAuth(function ($username, $password) use ($login) {
|
||||
return (isset($login[$username]) && (string)$login[$username] === $password);
|
||||
});
|
||||
}
|
||||
|
||||
public function unsetAuth()
|
||||
{
|
||||
$this->auth = null;
|
||||
}
|
||||
|
||||
public function onConnection(ConnectionInterface $connection)
|
||||
{
|
||||
$that = $this;
|
||||
$handling = $this->handleSocks($connection)->then(function($remote) use ($connection){
|
||||
$connection->emit('ready',array($remote));
|
||||
}, function ($error) use ($connection, $that) {
|
||||
if (!($error instanceof \Exception)) {
|
||||
$error = new \Exception($error);
|
||||
}
|
||||
$connection->emit('error', array($error));
|
||||
$that->endConnection($connection);
|
||||
});
|
||||
|
||||
$connection->on('close', function () use ($handling) {
|
||||
$handling->cancel();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* gracefully shutdown connection by flushing all remaining data and closing stream
|
||||
*/
|
||||
public function endConnection(ConnectionInterface $stream)
|
||||
{
|
||||
$tid = true;
|
||||
$loop = $this->loop;
|
||||
|
||||
// cancel below timer in case connection is closed in time
|
||||
$stream->once('close', function () use (&$tid, $loop) {
|
||||
// close event called before the timer was set up, so everything is okay
|
||||
if ($tid === true) {
|
||||
// make sure to not start a useless timer
|
||||
$tid = false;
|
||||
} else {
|
||||
$loop->cancelTimer($tid);
|
||||
}
|
||||
});
|
||||
|
||||
// shut down connection by pausing input data, flushing outgoing buffer and then exit
|
||||
$stream->pause();
|
||||
$stream->end();
|
||||
|
||||
// check if connection is not already closed
|
||||
if ($tid === true) {
|
||||
// fall back to forcefully close connection in 3 seconds if buffer can not be flushed
|
||||
$tid = $loop->addTimer(3.0, array($stream,'close'));
|
||||
}
|
||||
}
|
||||
|
||||
private function handleSocks(ConnectionInterface $stream)
|
||||
{
|
||||
$reader = new StreamReader();
|
||||
$stream->on('data', array($reader, 'write'));
|
||||
|
||||
$that = $this;
|
||||
$that = $this;
|
||||
|
||||
$auth = $this->auth;
|
||||
$protocolVersion = $this->protocolVersion;
|
||||
|
||||
// authentication requires SOCKS5
|
||||
if ($auth !== null) {
|
||||
$protocolVersion = '5';
|
||||
}
|
||||
|
||||
return $reader->readByte()->then(function ($version) use ($stream, $that, $protocolVersion, $auth, $reader){
|
||||
if ($version === 0x04) {
|
||||
if ($protocolVersion === '5') {
|
||||
throw new UnexpectedValueException('SOCKS4 not allowed due to configuration');
|
||||
}
|
||||
return $that->handleSocks4($stream, $protocolVersion, $reader);
|
||||
} else if ($version === 0x05) {
|
||||
if ($protocolVersion !== null && $protocolVersion !== '5') {
|
||||
throw new UnexpectedValueException('SOCKS5 not allowed due to configuration');
|
||||
}
|
||||
return $that->handleSocks5($stream, $auth, $reader);
|
||||
}
|
||||
throw new UnexpectedValueException('Unexpected/unknown version number');
|
||||
});
|
||||
}
|
||||
|
||||
public function handleSocks4(ConnectionInterface $stream, $protocolVersion, StreamReader $reader)
|
||||
{
|
||||
// suppliying hostnames is only allowed for SOCKS4a (or automatically detected version)
|
||||
$supportsHostname = ($protocolVersion === null || $protocolVersion === '4a');
|
||||
|
||||
$remote = $stream->getRemoteAddress();
|
||||
if ($remote !== null) {
|
||||
// remove transport scheme and prefix socks4:// instead
|
||||
$secure = strpos($remote, 'tls://') === 0;
|
||||
if (($pos = strpos($remote, '://')) !== false) {
|
||||
$remote = substr($remote, $pos + 3);
|
||||
}
|
||||
$remote = 'socks4' . ($secure ? 's' : '') . '://' . $remote;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
return $reader->readByteAssert(0x01)->then(function () use ($reader) {
|
||||
return $reader->readBinary(array(
|
||||
'port' => 'n',
|
||||
'ipLong' => 'N',
|
||||
'null' => 'C'
|
||||
));
|
||||
})->then(function ($data) use ($reader, $supportsHostname, $remote) {
|
||||
if ($data['null'] !== 0x00) {
|
||||
throw new Exception('Not a null byte');
|
||||
}
|
||||
if ($data['ipLong'] === 0) {
|
||||
throw new Exception('Invalid IP');
|
||||
}
|
||||
if ($data['port'] === 0) {
|
||||
throw new Exception('Invalid port');
|
||||
}
|
||||
if ($data['ipLong'] < 256 && $supportsHostname) {
|
||||
// invalid IP => probably a SOCKS4a request which appends the hostname
|
||||
return $reader->readStringNull()->then(function ($string) use ($data, $remote){
|
||||
return array($string, $data['port'], $remote);
|
||||
});
|
||||
} else {
|
||||
$ip = long2ip($data['ipLong']);
|
||||
return array($ip, $data['port'], $remote);
|
||||
}
|
||||
})->then(function ($target) use ($stream, $that) {
|
||||
return $that->connectTarget($stream, $target)->then(function (ConnectionInterface $remote) use ($stream){
|
||||
$stream->write(pack('C8', 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
|
||||
|
||||
return $remote;
|
||||
}, function($error) use ($stream){
|
||||
$stream->end(pack('C8', 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
|
||||
|
||||
throw $error;
|
||||
});
|
||||
}, function($error) {
|
||||
throw new UnexpectedValueException('SOCKS4 protocol error',0,$error);
|
||||
});
|
||||
}
|
||||
|
||||
public function handleSocks5(ConnectionInterface $stream, $auth=null, StreamReader $reader)
|
||||
{
|
||||
$remote = $stream->getRemoteAddress();
|
||||
if ($remote !== null) {
|
||||
// remove transport scheme and prefix socks5:// instead
|
||||
$secure = strpos($remote, 'tls://') === 0;
|
||||
if (($pos = strpos($remote, '://')) !== false) {
|
||||
$remote = substr($remote, $pos + 3);
|
||||
}
|
||||
$remote = 'socks5' . ($secure ? 's' : '') . '://' . $remote;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
return $reader->readByte()->then(function ($num) use ($reader) {
|
||||
// $num different authentication mechanisms offered
|
||||
return $reader->readLength($num);
|
||||
})->then(function ($methods) use ($reader, $stream, $auth, &$remote) {
|
||||
if ($auth === null && strpos($methods,"\x00") !== false) {
|
||||
// accept "no authentication"
|
||||
$stream->write(pack('C2', 0x05, 0x00));
|
||||
|
||||
return 0x00;
|
||||
} else if ($auth !== null && strpos($methods,"\x02") !== false) {
|
||||
// username/password authentication (RFC 1929) sub negotiation
|
||||
$stream->write(pack('C2', 0x05, 0x02));
|
||||
return $reader->readByteAssert(0x01)->then(function () use ($reader) {
|
||||
return $reader->readByte();
|
||||
})->then(function ($length) use ($reader) {
|
||||
return $reader->readLength($length);
|
||||
})->then(function ($username) use ($reader, $auth, $stream, &$remote) {
|
||||
return $reader->readByte()->then(function ($length) use ($reader) {
|
||||
return $reader->readLength($length);
|
||||
})->then(function ($password) use ($username, $auth, $stream, &$remote) {
|
||||
// username and password given => authenticate
|
||||
|
||||
// prefix username/password to remote URI
|
||||
if ($remote !== null) {
|
||||
$remote = str_replace('://', '://' . rawurlencode($username) . ':' . rawurlencode($password) . '@', $remote);
|
||||
}
|
||||
|
||||
return $auth($username, $password, $remote)->then(function () use ($stream, $username) {
|
||||
// accept
|
||||
$stream->emit('auth', array($username));
|
||||
$stream->write(pack('C2', 0x01, 0x00));
|
||||
}, function() use ($stream) {
|
||||
// reject => send any code but 0x00
|
||||
$stream->end(pack('C2', 0x01, 0xFF));
|
||||
throw new UnexpectedValueException('Unable to authenticate');
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// reject all offered authentication methods
|
||||
$stream->write(pack('C2', 0x05, 0xFF));
|
||||
throw new UnexpectedValueException('No acceptable authentication mechanism found');
|
||||
}
|
||||
})->then(function ($method) use ($reader, $stream) {
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'command' => 'C',
|
||||
'null' => 'C',
|
||||
'type' => 'C'
|
||||
));
|
||||
})->then(function ($data) use ($reader) {
|
||||
if ($data['version'] !== 0x05) {
|
||||
throw new UnexpectedValueException('Invalid SOCKS version');
|
||||
}
|
||||
if ($data['command'] !== 0x01) {
|
||||
throw new UnexpectedValueException('Only CONNECT requests supported', Server::ERROR_COMMAND_UNSUPPORTED);
|
||||
}
|
||||
// if ($data['null'] !== 0x00) {
|
||||
// throw new UnexpectedValueException('Reserved byte has to be NULL');
|
||||
// }
|
||||
if ($data['type'] === 0x03) {
|
||||
// target hostname string
|
||||
return $reader->readByte()->then(function ($len) use ($reader) {
|
||||
return $reader->readLength($len);
|
||||
});
|
||||
} else if ($data['type'] === 0x01) {
|
||||
// target IPv4
|
||||
return $reader->readLength(4)->then(function ($addr) {
|
||||
return inet_ntop($addr);
|
||||
});
|
||||
} else if ($data['type'] === 0x04) {
|
||||
// target IPv6
|
||||
return $reader->readLength(16)->then(function ($addr) {
|
||||
return inet_ntop($addr);
|
||||
});
|
||||
} else {
|
||||
throw new UnexpectedValueException('Invalid address type', Server::ERROR_ADDRESS_UNSUPPORTED);
|
||||
}
|
||||
})->then(function ($host) use ($reader, &$remote) {
|
||||
return $reader->readBinary(array('port'=>'n'))->then(function ($data) use ($host, &$remote) {
|
||||
return array($host, $data['port'], $remote);
|
||||
});
|
||||
})->then(function ($target) use ($that, $stream) {
|
||||
return $that->connectTarget($stream, $target);
|
||||
}, function($error) use ($stream) {
|
||||
throw new UnexpectedValueException('SOCKS5 protocol error', $error->getCode(), $error);
|
||||
})->then(function (ConnectionInterface $remote) use ($stream) {
|
||||
$stream->write(pack('C4Nn', 0x05, 0x00, 0x00, 0x01, 0, 0));
|
||||
|
||||
return $remote;
|
||||
}, function(Exception $error) use ($stream){
|
||||
$stream->write(pack('C4Nn', 0x05, $error->getCode() === 0 ? Server::ERROR_GENERAL : $error->getCode(), 0x00, 0x01, 0, 0));
|
||||
|
||||
throw $error;
|
||||
});
|
||||
}
|
||||
|
||||
public function connectTarget(ConnectionInterface $stream, array $target)
|
||||
{
|
||||
$uri = $target[0];
|
||||
if (strpos($uri, ':') !== false) {
|
||||
$uri = '[' . $uri . ']';
|
||||
}
|
||||
$uri .= ':' . $target[1];
|
||||
|
||||
// validate URI so a string hostname can not pass excessive URI parts
|
||||
$parts = parse_url('tcp://' . $uri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || count($parts) !== 3) {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid target URI given'));
|
||||
}
|
||||
|
||||
if (isset($target[2])) {
|
||||
$uri .= '?source=' . rawurlencode($target[2]);
|
||||
}
|
||||
|
||||
$stream->emit('target', $target);
|
||||
$that = $this;
|
||||
$connecting = $this->connector->connect($uri);
|
||||
|
||||
$stream->on('close', function () use ($connecting) {
|
||||
$connecting->cancel();
|
||||
});
|
||||
|
||||
return $connecting->then(function (ConnectionInterface $remote) use ($stream, $that) {
|
||||
$stream->pipe($remote, array('end'=>false));
|
||||
$remote->pipe($stream, array('end'=>false));
|
||||
|
||||
// remote end closes connection => stop reading from local end, try to flush buffer to local and disconnect local
|
||||
$remote->on('end', function() use ($stream, $that) {
|
||||
$stream->emit('shutdown', array('remote', null));
|
||||
$that->endConnection($stream);
|
||||
});
|
||||
|
||||
// local end closes connection => stop reading from remote end, try to flush buffer to remote and disconnect remote
|
||||
$stream->on('end', function() use ($remote, $that) {
|
||||
$that->endConnection($remote);
|
||||
});
|
||||
|
||||
// set bigger buffer size of 100k to improve performance
|
||||
$stream->bufferSize = $remote->bufferSize = 100 * 1024 * 1024;
|
||||
|
||||
return $remote;
|
||||
}, function(Exception $error) {
|
||||
// default to general/unknown error
|
||||
$code = Server::ERROR_GENERAL;
|
||||
|
||||
// map common socket error conditions to limited list of SOCKS error codes
|
||||
if ((defined('SOCKET_EACCES') && $error->getCode() === SOCKET_EACCES) || $error->getCode() === 13) {
|
||||
$code = Server::ERROR_NOT_ALLOWED_BY_RULESET;
|
||||
} elseif ((defined('SOCKET_EHOSTUNREACH') && $error->getCode() === SOCKET_EHOSTUNREACH) || $error->getCode() === 113) {
|
||||
$code = Server::ERROR_HOST_UNREACHABLE;
|
||||
} elseif ((defined('SOCKET_ENETUNREACH') && $error->getCode() === SOCKET_ENETUNREACH) || $error->getCode() === 101) {
|
||||
$code = Server::ERROR_NETWORK_UNREACHABLE;
|
||||
} elseif ((defined('SOCKET_ECONNREFUSED') && $error->getCode() === SOCKET_ECONNREFUSED) || $error->getCode() === 111 || $error->getMessage() === 'Connection refused') {
|
||||
// Socket component does not currently assign an error code for this, so we have to resort to checking the exception message
|
||||
$code = Server::ERROR_CONNECTION_REFUSED;
|
||||
} elseif ((defined('SOCKET_ETIMEDOUT') && $error->getCode() === SOCKET_ETIMEDOUT) || $error->getCode() === 110 || $error instanceof TimeoutException) {
|
||||
// Socket component does not currently assign an error code for this, but we can rely on the TimeoutException
|
||||
$code = Server::ERROR_TTL;
|
||||
}
|
||||
|
||||
throw new UnexpectedValueException('Unable to connect to remote target', $code, $error);
|
||||
});
|
||||
}
|
||||
}
|
||||
149
vendor/clue/socks-react/src/StreamReader.php
vendored
Executable file
149
vendor/clue/socks-react/src/StreamReader.php
vendored
Executable file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use React\Promise\Deferred;
|
||||
use \InvalidArgumentException;
|
||||
use \UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class StreamReader
|
||||
{
|
||||
const RET_DONE = true;
|
||||
const RET_INCOMPLETE = null;
|
||||
|
||||
private $buffer = '';
|
||||
private $queue = array();
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
|
||||
do {
|
||||
$current = reset($this->queue);
|
||||
|
||||
if ($current === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* @var $current Closure */
|
||||
|
||||
$ret = $current($this->buffer);
|
||||
|
||||
if ($ret === self::RET_INCOMPLETE) {
|
||||
// current is incomplete, so wait for further data to arrive
|
||||
break;
|
||||
} else {
|
||||
// current is done, remove from list and continue with next
|
||||
array_shift($this->queue);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public function readBinary($structure)
|
||||
{
|
||||
$length = 0;
|
||||
$unpack = '';
|
||||
foreach ($structure as $name=>$format) {
|
||||
if ($length !== 0) {
|
||||
$unpack .= '/';
|
||||
}
|
||||
$unpack .= $format . $name;
|
||||
|
||||
if ($format === 'C') {
|
||||
++$length;
|
||||
} else if ($format === 'n') {
|
||||
$length += 2;
|
||||
} else if ($format === 'N') {
|
||||
$length += 4;
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid format given');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->readLength($length)->then(function ($response) use ($unpack) {
|
||||
return unpack($unpack, $response);
|
||||
});
|
||||
}
|
||||
|
||||
public function readLength($bytes)
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
$this->readBufferCallback(function (&$buffer) use ($bytes, $deferred) {
|
||||
if (strlen($buffer) >= $bytes) {
|
||||
$deferred->resolve((string)substr($buffer, 0, $bytes));
|
||||
$buffer = (string)substr($buffer, $bytes);
|
||||
|
||||
return StreamReader::RET_DONE;
|
||||
}
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
public function readByte()
|
||||
{
|
||||
return $this->readBinary(array(
|
||||
'byte' => 'C'
|
||||
))->then(function ($data) {
|
||||
return $data['byte'];
|
||||
});
|
||||
}
|
||||
|
||||
public function readByteAssert($expect)
|
||||
{
|
||||
return $this->readByte()->then(function ($byte) use ($expect) {
|
||||
if ($byte !== $expect) {
|
||||
throw new UnexpectedValueException('Unexpected byte encountered');
|
||||
}
|
||||
return $byte;
|
||||
});
|
||||
}
|
||||
|
||||
public function readStringNull()
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
$string = '';
|
||||
|
||||
$that = $this;
|
||||
$readOne = function () use (&$readOne, $that, $deferred, &$string) {
|
||||
$that->readByte()->then(function ($byte) use ($deferred, &$string, $readOne) {
|
||||
if ($byte === 0x00) {
|
||||
$deferred->resolve($string);
|
||||
} else {
|
||||
$string .= chr($byte);
|
||||
$readOne();
|
||||
}
|
||||
});
|
||||
};
|
||||
$readOne();
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
public function readBufferCallback(/* callable */ $callable)
|
||||
{
|
||||
if (!is_callable($callable)) {
|
||||
throw new InvalidArgumentException('Given function must be callable');
|
||||
}
|
||||
|
||||
if ($this->queue) {
|
||||
$this->queue []= $callable;
|
||||
} else {
|
||||
$this->queue = array($callable);
|
||||
|
||||
if ($this->buffer !== '') {
|
||||
// this is the first element in the queue and the buffer is filled => trigger write procedure
|
||||
$this->write('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getBuffer()
|
||||
{
|
||||
return $this->buffer;
|
||||
}
|
||||
}
|
||||
403
vendor/clue/socks-react/tests/ClientTest.php
vendored
Executable file
403
vendor/clue/socks-react/tests/ClientTest.php
vendored
Executable file
@@ -0,0 +1,403 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use React\Promise\Promise;
|
||||
use Clue\React\Socks\Server;
|
||||
|
||||
class ClientTest extends TestCase
|
||||
{
|
||||
private $loop;
|
||||
|
||||
private $connector;
|
||||
|
||||
/** @var Client */
|
||||
private $client;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = React\EventLoop\Factory::create();
|
||||
$this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
|
||||
$this->client = new Client('127.0.0.1:1080', $this->connector);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithHostAndPort()
|
||||
{
|
||||
$client = new Client('127.0.0.1:9050', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithScheme()
|
||||
{
|
||||
$client = new Client('socks://127.0.0.1:9050', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithHostOnlyAssumesDefaultPort()
|
||||
{
|
||||
$client = new Client('127.0.0.1', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithSecureScheme()
|
||||
{
|
||||
$client = new Client('sockss://127.0.0.1:9050', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithSecureVersionScheme()
|
||||
{
|
||||
$client = new Client('socks5s://127.0.0.1:9050', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithSocksUnixScheme()
|
||||
{
|
||||
$client = new Client('socks+unix:///tmp/socks.socket', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCtorAcceptsUriWithSocks5UnixScheme()
|
||||
{
|
||||
$client = new Client('socks5+unix:///tmp/socks.socket', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testCtorThrowsForInvalidUri()
|
||||
{
|
||||
new Client('////', $this->connector);
|
||||
}
|
||||
|
||||
public function testValidAuthFromUri()
|
||||
{
|
||||
$this->client = new Client('username:password@127.0.0.1', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidAuthInformation()
|
||||
{
|
||||
new Client(str_repeat('a', 256) . ':test@127.0.0.1', $this->connector);
|
||||
}
|
||||
|
||||
public function testValidAuthAndVersionFromUri()
|
||||
{
|
||||
$this->client = new Client('socks5://username:password@127.0.0.1:9050', $this->connector);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidCanNotSetAuthenticationForSocks4Uri()
|
||||
{
|
||||
$this->client = new Client('socks4://username:password@127.0.0.1:9050', $this->connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidProtocolVersion()
|
||||
{
|
||||
$this->client = new Client('socks3://127.0.0.1:9050', $this->connector);
|
||||
}
|
||||
|
||||
public function testCreateWillConnectToProxy()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=localhost')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('localhost:80');
|
||||
|
||||
$this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testCreateWillConnectToProxyWithFullUri()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080/?hostname=test#fragment')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('localhost:80/?hostname=test#fragment');
|
||||
|
||||
$this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testCreateWithInvalidHostDoesNotConnect()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$promise = $this->client->connect(str_repeat('a', '256') . ':80');
|
||||
|
||||
$this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testCreateWithInvalidPortDoesNotConnect()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$promise = $this->client->connect('some-random-site:some-random-port');
|
||||
|
||||
$this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testConnectorRejectsWillRejectConnection()
|
||||
{
|
||||
$promise = \React\Promise\reject(new RuntimeException());
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
|
||||
}
|
||||
|
||||
public function testCancelConnectionDuringConnectionWillCancelConnection()
|
||||
{
|
||||
$promise = new Promise(function () { }, function () {
|
||||
throw new \RuntimeException();
|
||||
});
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
$promise->cancel();
|
||||
|
||||
$this->expectPromiseReject($promise);
|
||||
}
|
||||
|
||||
public function testCancelConnectionDuringSessionWillCloseStream()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = new Promise(function ($resolve) use ($stream) { $resolve($stream); });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
$promise->cancel();
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNABORTED));
|
||||
}
|
||||
|
||||
public function testEmitConnectionCloseDuringSessionWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('close');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNRESET));
|
||||
}
|
||||
|
||||
public function testEmitConnectionErrorDuringSessionWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('error', array(new RuntimeException()));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EIO));
|
||||
}
|
||||
|
||||
public function testEmitInvalidSocks4DataDuringSessionWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("HTTP/1.1 400 Bad Request\r\n\r\n"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
|
||||
}
|
||||
|
||||
public function testEmitInvalidSocks5DataDuringSessionWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("HTTP/1.1 400 Bad Request\r\n\r\n"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
|
||||
}
|
||||
|
||||
public function testEmitSocks5DataErrorDuringSessionWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("\x05\x00" . "\x05\x01\x00\x00"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
|
||||
}
|
||||
|
||||
public function testEmitSocks5DataInvalidAddressTypeWillRejectConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x00"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
|
||||
}
|
||||
|
||||
public function testEmitSocks5DataIpv6AddressWillResolveConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->never())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=%3A%3A1')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('[::1]:80');
|
||||
|
||||
$stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x04" . inet_pton('::1') . "\x00\x50"));
|
||||
|
||||
$promise->then($this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testEmitSocks5DataHostnameAddressWillResolveConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->never())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x03\x0Agoogle.com\x00\x50"));
|
||||
|
||||
$promise->then($this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function provideConnectionErrors()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
Server::ERROR_GENERAL,
|
||||
SOCKET_ECONNREFUSED
|
||||
),
|
||||
array(
|
||||
Server::ERROR_NOT_ALLOWED_BY_RULESET,
|
||||
SOCKET_EACCES
|
||||
),
|
||||
array(
|
||||
Server::ERROR_NETWORK_UNREACHABLE,
|
||||
SOCKET_ENETUNREACH
|
||||
),
|
||||
array(
|
||||
Server::ERROR_HOST_UNREACHABLE,
|
||||
SOCKET_EHOSTUNREACH
|
||||
),
|
||||
array(
|
||||
Server::ERROR_CONNECTION_REFUSED,
|
||||
SOCKET_ECONNREFUSED
|
||||
),
|
||||
array(
|
||||
Server::ERROR_TTL,
|
||||
SOCKET_ETIMEDOUT
|
||||
),
|
||||
array(
|
||||
Server::ERROR_COMMAND_UNSUPPORTED,
|
||||
SOCKET_EPROTO
|
||||
),
|
||||
array(
|
||||
Server::ERROR_ADDRESS_UNSUPPORTED,
|
||||
SOCKET_EPROTO
|
||||
),
|
||||
array(
|
||||
200,
|
||||
SOCKET_ECONNREFUSED
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideConnectionErrors
|
||||
* @param int $error
|
||||
* @param int $expectedCode
|
||||
*/
|
||||
public function testEmitSocks5DataErrorMapsToExceptionCode($error, $expectedCode)
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
|
||||
|
||||
$promise = $this->client->connect('google.com:80');
|
||||
|
||||
$stream->emit('data', array("\x05\x00" . "\x05" . chr($error) . "\x00\x00"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode($expectedCode));
|
||||
}
|
||||
}
|
||||
437
vendor/clue/socks-react/tests/FunctionalTest.php
vendored
Executable file
437
vendor/clue/socks-react/tests/FunctionalTest.php
vendored
Executable file
@@ -0,0 +1,437 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Client;
|
||||
use Clue\React\Socks\Server;
|
||||
use Clue\React\Block;
|
||||
use React\Socket\TimeoutConnector;
|
||||
use React\Socket\SecureConnector;
|
||||
use React\Socket\TcpConnector;
|
||||
use React\Socket\UnixServer;
|
||||
use React\Socket\Connector;
|
||||
|
||||
class FunctionalTest extends TestCase
|
||||
{
|
||||
private $loop;
|
||||
private $connector;
|
||||
private $client;
|
||||
|
||||
private $port;
|
||||
private $server;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = React\EventLoop\Factory::create();
|
||||
|
||||
$socket = new React\Socket\Server(0, $this->loop);
|
||||
$address = $socket->getAddress();
|
||||
if (strpos($address, '://') === false) {
|
||||
$address = 'tcp://' . $address;
|
||||
}
|
||||
$this->port = parse_url($address, PHP_URL_PORT);
|
||||
$this->assertNotEquals(0, $this->port);
|
||||
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
$this->connector = new TcpConnector($this->loop);
|
||||
$this->client = new Client('127.0.0.1:' . $this->port, $this->connector);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnection()
|
||||
{
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionInvalid()
|
||||
{
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com.invalid:80'));
|
||||
}
|
||||
|
||||
public function testConnectionWithIpViaSocks4()
|
||||
{
|
||||
$this->server->setProtocolVersion('4');
|
||||
|
||||
$this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('127.0.0.1:' . $this->port));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionWithHostnameViaSocks4Fails()
|
||||
{
|
||||
$this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionWithInvalidPortFails()
|
||||
{
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:100000'));
|
||||
}
|
||||
|
||||
public function testConnectionWithIpv6ViaSocks4Fails()
|
||||
{
|
||||
$this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('[::1]:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocks4a()
|
||||
{
|
||||
$this->server->setProtocolVersion('4a');
|
||||
$this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocks5()
|
||||
{
|
||||
$this->server->setProtocolVersion(5);
|
||||
$this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocksOverTls()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
|
||||
'local_cert' => __DIR__ . '/../examples/localhost.pem',
|
||||
)));
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
|
||||
$this->connector = new Connector($this->loop, array('tls' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false
|
||||
)));
|
||||
$this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group internet
|
||||
* @requires PHP 5.6
|
||||
*/
|
||||
public function testConnectionSocksOverTlsUsesPeerNameFromSocksUri()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
|
||||
'local_cert' => __DIR__ . '/../examples/localhost.pem',
|
||||
)));
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
|
||||
$this->connector = new Connector($this->loop, array('tls' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => true
|
||||
)));
|
||||
$this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocksOverUnix()
|
||||
{
|
||||
if (!in_array('unix', stream_get_transports())) {
|
||||
$this->markTestSkipped('System does not support unix:// scheme');
|
||||
}
|
||||
|
||||
$path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
|
||||
$socket = new UnixServer($path, $this->loop);
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
|
||||
$this->connector = new Connector($this->loop);
|
||||
$this->client = new Client('socks+unix://' . $path, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
|
||||
unlink($path);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocks5OverUnix()
|
||||
{
|
||||
if (!in_array('unix', stream_get_transports())) {
|
||||
$this->markTestSkipped('System does not support unix:// scheme');
|
||||
}
|
||||
|
||||
$path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
|
||||
$socket = new UnixServer($path, $this->loop);
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
$this->server->setProtocolVersion(5);
|
||||
|
||||
$this->connector = new Connector($this->loop);
|
||||
$this->client = new Client('socks5+unix://' . $path, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
|
||||
unlink($path);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionSocksWithAuthenticationOverUnix()
|
||||
{
|
||||
if (!in_array('unix', stream_get_transports())) {
|
||||
$this->markTestSkipped('System does not support unix:// scheme');
|
||||
}
|
||||
|
||||
$path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
|
||||
$socket = new UnixServer($path, $this->loop);
|
||||
$this->server = new Server($this->loop, $socket);
|
||||
$this->server->setAuthArray(array('name' => 'pass'));
|
||||
|
||||
$this->connector = new Connector($this->loop);
|
||||
$this->client = new Client('socks+unix://name:pass@' . $path, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
|
||||
unlink($path);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationFromUri()
|
||||
{
|
||||
$this->server->setAuthArray(array('name' => 'pass'));
|
||||
|
||||
$this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationCallback()
|
||||
{
|
||||
$called = 0;
|
||||
$that = $this;
|
||||
$this->server->setAuth(function ($name, $pass, $remote) use ($that, &$called) {
|
||||
++$called;
|
||||
$that->assertEquals('name', $name);
|
||||
$that->assertEquals('pass', $pass);
|
||||
$that->assertStringStartsWith('socks5://name:pass@127.0.0.1:', $remote);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
$this->assertEquals(1, $called);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationCallbackWillNotBeInvokedIfClientsSendsNoAuth()
|
||||
{
|
||||
$called = 0;
|
||||
$this->server->setAuth(function () use (&$called) {
|
||||
++$called;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$this->client = new Client('127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'));
|
||||
$this->assertEquals(0, $called);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationFromUriEncoded()
|
||||
{
|
||||
$this->server->setAuthArray(array('name' => 'p@ss:w0rd'));
|
||||
|
||||
$this->client = new Client(rawurlencode('name') . ':' . rawurlencode('p@ss:w0rd') . '@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationFromUriWithOnlyUserAndNoPassword()
|
||||
{
|
||||
$this->server->setAuthArray(array('empty' => ''));
|
||||
|
||||
$this->client = new Client('empty@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationEmptyPassword()
|
||||
{
|
||||
$this->server->setAuthArray(array('user' => ''));
|
||||
$this->client = new Client('user@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectionAuthenticationUnused()
|
||||
{
|
||||
$this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
public function testConnectionInvalidProtocolDoesNotMatchSocks5()
|
||||
{
|
||||
$this->server->setProtocolVersion(5);
|
||||
$this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
|
||||
}
|
||||
|
||||
public function testConnectionInvalidProtocolDoesNotMatchSocks4()
|
||||
{
|
||||
$this->server->setProtocolVersion(4);
|
||||
$this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
|
||||
}
|
||||
|
||||
public function testConnectionInvalidNoAuthentication()
|
||||
{
|
||||
$this->server->setAuthArray(array('name' => 'pass'));
|
||||
|
||||
$this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
|
||||
}
|
||||
|
||||
public function testConnectionInvalidAuthenticationMismatch()
|
||||
{
|
||||
$this->server->setAuthArray(array('name' => 'pass'));
|
||||
|
||||
$this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
|
||||
|
||||
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectorOkay()
|
||||
{
|
||||
$this->assertResolveStream($this->client->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectorInvalidDomain()
|
||||
{
|
||||
$this->assertRejectPromise($this->client->connect('www.google.commm:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectorCancelConnection()
|
||||
{
|
||||
$promise = $this->client->connect('www.google.com:80');
|
||||
$promise->cancel();
|
||||
|
||||
$this->assertRejectPromise($promise);
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testConnectorInvalidUnboundPortTimeout()
|
||||
{
|
||||
// time out the connection attempt in 0.1s (as expected)
|
||||
$tcp = new TimeoutConnector($this->client, 0.1, $this->loop);
|
||||
|
||||
$this->assertRejectPromise($tcp->connect('www.google.com:8080'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testSecureConnectorOkay()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$ssl = new SecureConnector($this->client, $this->loop);
|
||||
|
||||
$this->assertResolveStream($ssl->connect('www.google.com:443'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testSecureConnectorToBadSslWithVerifyFails()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$ssl = new SecureConnector($this->client, $this->loop, array('verify_peer' => true));
|
||||
|
||||
$this->assertRejectPromise($ssl->connect('self-signed.badssl.com:443'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testSecureConnectorToBadSslWithoutVerifyWorks()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$ssl = new SecureConnector($this->client, $this->loop, array('verify_peer' => false));
|
||||
|
||||
$this->assertResolveStream($ssl->connect('self-signed.badssl.com:443'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testSecureConnectorInvalidPlaintextIsNotSsl()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
|
||||
}
|
||||
|
||||
$ssl = new SecureConnector($this->client, $this->loop);
|
||||
|
||||
$this->assertRejectPromise($ssl->connect('www.google.com:80'));
|
||||
}
|
||||
|
||||
/** @group internet */
|
||||
public function testSecureConnectorInvalidUnboundPortTimeout()
|
||||
{
|
||||
$ssl = new SecureConnector($this->client, $this->loop);
|
||||
|
||||
// time out the connection attempt in 0.1s (as expected)
|
||||
$ssl = new TimeoutConnector($ssl, 0.1, $this->loop);
|
||||
|
||||
$this->assertRejectPromise($ssl->connect('www.google.com:8080'));
|
||||
}
|
||||
|
||||
private function assertResolveStream($promise)
|
||||
{
|
||||
$this->expectPromiseResolve($promise);
|
||||
|
||||
$promise->then(function ($stream) {
|
||||
$stream->close();
|
||||
});
|
||||
|
||||
Block\await($promise, $this->loop, 2.0);
|
||||
}
|
||||
|
||||
private function assertRejectPromise($promise, $message = null, $code = null)
|
||||
{
|
||||
$this->expectPromiseReject($promise);
|
||||
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException('Exception');
|
||||
if ($message !== null) {
|
||||
$this->expectExceptionMessage($message);
|
||||
}
|
||||
if ($code !== null) {
|
||||
$this->expectExceptionCode($code);
|
||||
}
|
||||
} else {
|
||||
$this->setExpectedException('Exception', $message, $code);
|
||||
}
|
||||
|
||||
Block\await($promise, $this->loop, 2.0);
|
||||
}
|
||||
}
|
||||
428
vendor/clue/socks-react/tests/ServerTest.php
vendored
Executable file
428
vendor/clue/socks-react/tests/ServerTest.php
vendored
Executable file
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\Server;
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
|
||||
class ServerTest extends TestCase
|
||||
{
|
||||
/** @var Server */
|
||||
private $server;
|
||||
private $connector;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$socket = $this->getMockBuilder('React\Socket\ServerInterface')
|
||||
->getMock();
|
||||
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')
|
||||
->getMock();
|
||||
|
||||
$this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')
|
||||
->getMock();
|
||||
|
||||
$this->server = new Server($loop, $socket, $this->connector);
|
||||
}
|
||||
|
||||
public function testSetProtocolVersion()
|
||||
{
|
||||
$this->server->setProtocolVersion(4);
|
||||
$this->server->setProtocolVersion('4a');
|
||||
$this->server->setProtocolVersion(5);
|
||||
$this->server->setProtocolVersion(null);
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testSetInvalidProtocolVersion()
|
||||
{
|
||||
$this->server->setProtocolVersion(6);
|
||||
}
|
||||
|
||||
public function testSetAuthArray()
|
||||
{
|
||||
$this->server->setAuthArray(array());
|
||||
|
||||
$this->server->setAuthArray(array(
|
||||
'name1' => 'password1',
|
||||
'name2' => 'password2'
|
||||
));
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testSetAuthInvalid()
|
||||
{
|
||||
$this->server->setAuth(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException UnexpectedValueException
|
||||
*/
|
||||
public function testUnableToSetAuthIfProtocolDoesNotSupportAuth()
|
||||
{
|
||||
$this->server->setProtocolVersion(4);
|
||||
|
||||
$this->server->setAuthArray(array());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException UnexpectedValueException
|
||||
*/
|
||||
public function testUnableToSetProtocolWhichDoesNotSupportAuth()
|
||||
{
|
||||
$this->server->setAuthArray(array());
|
||||
|
||||
// this is okay
|
||||
$this->server->setProtocolVersion(5);
|
||||
|
||||
$this->server->setProtocolVersion(4);
|
||||
}
|
||||
|
||||
public function testConnectWillCreateConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80));
|
||||
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testConnectWillCreateConnectionWithSourceUri()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('google.com:80?source=socks5%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80, 'socks5://10.20.30.40:5060'));
|
||||
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
}
|
||||
|
||||
public function testConnectWillRejectIfConnectionFails()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
|
||||
|
||||
$promise = new Promise(function ($_, $reject) { $reject(new \RuntimeException()); });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testConnectWillCancelConnectionIfStreamCloses()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { }, function () {
|
||||
throw new \RuntimeException();
|
||||
});
|
||||
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80));
|
||||
|
||||
$stream->emit('close');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testConnectWillAbortIfPromiseIsCanceled()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
|
||||
|
||||
$promise = new Promise(function () { }, function () {
|
||||
throw new \RuntimeException();
|
||||
});
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80));
|
||||
|
||||
$promise->cancel();
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function provideConnectionErrors()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
new RuntimeException('', SOCKET_EACCES),
|
||||
Server::ERROR_NOT_ALLOWED_BY_RULESET
|
||||
),
|
||||
array(
|
||||
new RuntimeException('', SOCKET_ENETUNREACH),
|
||||
Server::ERROR_NETWORK_UNREACHABLE
|
||||
),
|
||||
array(
|
||||
new RuntimeException('', SOCKET_EHOSTUNREACH),
|
||||
Server::ERROR_HOST_UNREACHABLE,
|
||||
),
|
||||
array(
|
||||
new RuntimeException('', SOCKET_ECONNREFUSED),
|
||||
Server::ERROR_CONNECTION_REFUSED
|
||||
),
|
||||
array(
|
||||
new RuntimeException('Connection refused'),
|
||||
Server::ERROR_CONNECTION_REFUSED
|
||||
),
|
||||
array(
|
||||
new RuntimeException('', SOCKET_ETIMEDOUT),
|
||||
Server::ERROR_TTL
|
||||
),
|
||||
array(
|
||||
new TimeoutException(1.0),
|
||||
Server::ERROR_TTL
|
||||
),
|
||||
array(
|
||||
new RuntimeException(),
|
||||
Server::ERROR_GENERAL
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideConnectionErrors
|
||||
* @param Exception $error
|
||||
* @param int $expectedCode
|
||||
*/
|
||||
public function testConnectWillReturnMappedSocks5ErrorCodeFromConnector($error, $expectedCode)
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
|
||||
|
||||
$promise = \React\Promise\reject($error);
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$promise = $this->server->connectTarget($stream, array('google.com', 80));
|
||||
|
||||
$code = null;
|
||||
$promise->then(null, function ($error) use (&$code) {
|
||||
$code = $error->getCode();
|
||||
});
|
||||
|
||||
$this->assertEquals($expectedCode, $code);
|
||||
}
|
||||
|
||||
public function testHandleSocksConnectionWillEndOnInvalidData()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
|
||||
$connection->expects($this->once())->method('pause');
|
||||
$connection->expects($this->once())->method('end');
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array('asdasdasdasdasd'));
|
||||
}
|
||||
|
||||
public function testHandleSocks4ConnectionWithIpv4WillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00"));
|
||||
}
|
||||
|
||||
public function testHandleSocks4aConnectionWithHostnameWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
|
||||
}
|
||||
|
||||
public function testHandleSocks4aConnectionWithHostnameAndSourceAddressWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'getRemoteAddress'))->getMock();
|
||||
$connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://10.20.30.40:5060');
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('example.com:80?source=socks4%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
|
||||
}
|
||||
|
||||
public function testHandleSocks4aConnectionWithSecureTlsSourceAddressWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'getRemoteAddress'))->getMock();
|
||||
$connection->expects($this->once())->method('getRemoteAddress')->willReturn('tls://10.20.30.40:5060');
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('example.com:80?source=socks4s%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
|
||||
}
|
||||
|
||||
public function testHandleSocks4aConnectionWithInvalidHostnameWillNotEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
|
||||
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "tls://example.com:80?" . "\x00"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithIpv4WillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithIpv4AndSourceAddressWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'getRemoteAddress'))->getMock();
|
||||
$connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://10.20.30.40:5060');
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80?source=socks5%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithSecureTlsIpv4AndSourceAddressWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'getRemoteAddress'))->getMock();
|
||||
$connection->expects($this->once())->method('getRemoteAddress')->willReturn('tls://10.20.30.40:5060');
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80?source=socks5s%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithIpv6WillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('[::1]:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x04" . inet_pton('::1') . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithHostnameWillEstablishOutgoingConnection()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { });
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x0B" . "example.com" . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithConnectorRefusedWillReturnReturnRefusedError()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\reject(new RuntimeException('Connection refused'));
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x05" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x0B" . "example.com" . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5UdpCommandWillNotEstablishOutgoingConnectionAndReturnCommandError()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x07" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x03\x00\x03\x0B" . "example.com" . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocks5ConnectionWithInvalidHostnameWillNotEstablishOutgoingConnectionAndReturnGeneralError()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
|
||||
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x01" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
|
||||
|
||||
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x15" . "tls://example.com:80?" . "\x00\x50"));
|
||||
}
|
||||
|
||||
public function testHandleSocksConnectionWillCancelOutputConnectionIfIncomingCloses()
|
||||
{
|
||||
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
|
||||
|
||||
$promise = new Promise(function () { }, $this->expectCallableOnce());
|
||||
|
||||
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
|
||||
|
||||
$this->server->onConnection($connection);
|
||||
|
||||
$connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00"));
|
||||
$connection->emit('close');
|
||||
}
|
||||
|
||||
public function testUnsetAuth()
|
||||
{
|
||||
$this->server->unsetAuth();
|
||||
$this->server->unsetAuth();
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
82
vendor/clue/socks-react/tests/StreamReaderTest.php
vendored
Executable file
82
vendor/clue/socks-react/tests/StreamReaderTest.php
vendored
Executable file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
use Clue\React\Socks\StreamReader;
|
||||
|
||||
class StreamReaderTest extends TestCase
|
||||
{
|
||||
private $reader;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->reader = new StreamReader();
|
||||
}
|
||||
|
||||
public function testReadByteAssertCorrect()
|
||||
{
|
||||
$this->reader->readByteAssert(0x01)->then($this->expectCallableOnce(0x01));
|
||||
|
||||
$this->reader->write("\x01");
|
||||
}
|
||||
|
||||
public function testReadByteAssertInvalid()
|
||||
{
|
||||
$this->reader->readByteAssert(0x02)->then(null, $this->expectCallableOnce());
|
||||
|
||||
$this->reader->write("\x03");
|
||||
}
|
||||
|
||||
public function testReadStringNull()
|
||||
{
|
||||
$this->reader->readStringNull()->then($this->expectCallableOnce('hello'));
|
||||
|
||||
$this->reader->write("hello\x00");
|
||||
}
|
||||
|
||||
public function testReadStringLength()
|
||||
{
|
||||
$this->reader->readLength(5)->then($this->expectCallableOnce('hello'));
|
||||
|
||||
$this->reader->write('he');
|
||||
$this->reader->write('ll');
|
||||
$this->reader->write('o ');
|
||||
|
||||
$this->assertEquals(' ', $this->reader->getBuffer());
|
||||
}
|
||||
|
||||
public function testReadBuffered()
|
||||
{
|
||||
$this->reader->write('hello');
|
||||
|
||||
$this->reader->readLength(5)->then($this->expectCallableOnce('hello'));
|
||||
|
||||
$this->assertEquals('', $this->reader->getBuffer());
|
||||
}
|
||||
|
||||
public function testSequence()
|
||||
{
|
||||
$this->reader->readByte()->then($this->expectCallableOnce(ord('h')));
|
||||
$this->reader->readByteAssert(ord('e'))->then($this->expectCallableOnce(ord('e')));
|
||||
$this->reader->readLength(4)->then($this->expectCallableOnce('llo '));
|
||||
$this->reader->readBinary(array('w'=>'C', 'o' => 'C'))->then($this->expectCallableOnce(array('w' => ord('w'), 'o' => ord('o'))));
|
||||
|
||||
$this->reader->write('hello world');
|
||||
|
||||
$this->assertEquals('rld', $this->reader->getBuffer());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidStructure()
|
||||
{
|
||||
$this->reader->readBinary(array('invalid' => 'y'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidCallback()
|
||||
{
|
||||
$this->reader->readBufferCallback(array());
|
||||
}
|
||||
}
|
||||
103
vendor/clue/socks-react/tests/bootstrap.php
vendored
Executable file
103
vendor/clue/socks-react/tests/bootstrap.php
vendored
Executable file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
(include_once __DIR__.'/../vendor/autoload.php') OR die(PHP_EOL.'ERROR: composer autoloader not found, run "composer install" or see README for instructions'.PHP_EOL);
|
||||
|
||||
class TestCase extends PHPUnit\Framework\TestCase
|
||||
{
|
||||
protected function expectCallableOnce()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
|
||||
|
||||
if (func_num_args() > 0) {
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->equalTo(func_get_arg(0)));
|
||||
} else {
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke');
|
||||
}
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableNever()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->never())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnceWithExceptionCode($code)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->callback(function ($e) use ($code) {
|
||||
return $e->getCode() === $code;
|
||||
}));
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnceParameter($type)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->isInstanceOf($type));
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
|
||||
*/
|
||||
protected function createCallableMock()
|
||||
{
|
||||
return $this->getMockBuilder('CallableStub')->getMock();
|
||||
}
|
||||
|
||||
protected function expectPromiseResolve($promise)
|
||||
{
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
|
||||
$that = $this;
|
||||
$promise->then(null, function($error) use ($that) {
|
||||
$that->assertNull($error);
|
||||
$that->fail('promise rejected');
|
||||
});
|
||||
$promise->then($this->expectCallableOnce(), $this->expectCallableNever());
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
protected function expectPromiseReject($promise)
|
||||
{
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
|
||||
$that = $this;
|
||||
$promise->then(function($value) use ($that) {
|
||||
$that->assertNull($value);
|
||||
$that->fail('promise resolved');
|
||||
});
|
||||
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
|
||||
|
||||
return $promise;
|
||||
}
|
||||
}
|
||||
|
||||
class CallableStub
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user