summaryrefslogtreecommitdiffstats
path: root/vendor/clue/socks-react
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/clue/socks-react')
-rw-r--r--vendor/clue/socks-react/CHANGELOG.md481
-rw-r--r--vendor/clue/socks-react/LICENSE21
-rw-r--r--vendor/clue/socks-react/README.md1116
-rw-r--r--vendor/clue/socks-react/composer.json31
-rw-r--r--vendor/clue/socks-react/src/Client.php458
-rw-r--r--vendor/clue/socks-react/src/Server.php416
-rw-r--r--vendor/clue/socks-react/src/StreamReader.php149
7 files changed, 2672 insertions, 0 deletions
diff --git a/vendor/clue/socks-react/CHANGELOG.md b/vendor/clue/socks-react/CHANGELOG.md
new file mode 100644
index 0000000..cd72e1d
--- /dev/null
+++ b/vendor/clue/socks-react/CHANGELOG.md
@@ -0,0 +1,481 @@
+# Changelog
+
+## 1.4.0 (2022-08-31)
+
+* Feature: Full support for PHP 8.1 and PHP 8.2.
+ (#105 by @clue and #108 by @SimonFrings)
+
+* Feature: Mark passwords and URIs as `#[\SensitiveParameter]` (PHP 8.2+).
+ (#109 by @SimonFrings)
+
+* Feature: Forward compatibility with upcoming Promise v3.
+ (#106 by @clue)
+
+* Bug: Fix invalid references in exception stack trace.
+ (#104 by @clue)
+
+* Improve test suite and fix legacy HHVM build.
+ (#107 by @SimonFrings)
+
+## 1.3.0 (2021-08-06)
+
+* Feature: Simplify usage by supporting new default loop and making `Connector` optional.
+ (#100 and #101 by @clue)
+
+ ```php
+ // old (still supported)
+ $proxy = new Clue\React\Socks\Client(
+ $url,
+ new React\Socket\Connector($loop)
+ );
+ $server = new Clue\React\Socks\Server($loop);
+ $server->listen(new React\Socket\Server('127.0.0.1:1080', $loop));
+
+ // new (using default loop)
+ $proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+ $socks = new Clue\React\Socks\Server();
+ $socks->listen(new React\Socket\SocketServer('127.0.0.1:1080'));
+ ```
+
+* Documentation improvements and updated examples.
+ (#98 and #102 by @clue and #99 by @PaulRotmann)
+
+* Improve test suite and use GitHub actions for continuous integration (CI).
+ (#97 by @SimonFrings)
+
+## 1.2.0 (2020-10-23)
+
+* Enhanced documentation for ReactPHP's new HTTP client.
+ (#95 by @SimonFrings)
+
+* Improve test suite, prepare PHP 8 support and support PHPUnit 9.3.
+ (#96 by @SimonFrings)
+
+## 1.1.0 (2020-06-19)
+
+* Feature / Fix: Support PHP 7.4 by skipping unneeded cleanup of exception trace args.
+ (#92 by @clue)
+
+* Clean up test suite and add `.gitattributes` to exclude dev files from exports.
+ Run tests on PHP 7.4, PHPUnit 9 and simplify test matrix.
+ Link to using SSH proxy (SSH tunnel) as an alternative.
+ (#88 by @clue and #91 and #93 by @SimonFrings)
+
+## 1.0.0 (2018-11-20)
+
+* First stable release, now following SemVer!
+
+* Feature / BC break: Unify SOCKS5 and SOCKS4(a) protocol version handling,
+ the `Client` now defaults to SOCKS5 instead of SOCKS4a,
+ remove explicit SOCKS4a handling and merge into SOCKS4 protocol handling and
+ URI scheme `socks5://` now only acts as an alias for default `socks://` scheme.
+ (#74, #81 and #87 by @clue)
+
+ ```php
+ // old: defaults to SOCKS4a
+ $client = new Client('127.0.0.1:1080', $connector);
+ $client = new Client('socks://127.0.0.1:1080', $connector);
+
+ // new: defaults to SOCKS5
+ $client = new Client('127.0.0.1:1080', $connector);
+ $client = new Client('socks://127.0.0.1:1080', $connector);
+
+ // new: explicitly use legacy SOCKS4(a)
+ $client = new Client('socks4://127.0.0.1:1080', $connector);
+
+ // unchanged: explicitly use SOCKS5
+ $client = new Client('socks5://127.0.0.1:1080', $connector);
+ ```
+
+* Feature / BC break: Clean up `Server` interface,
+ add `Server::listen()` method instead of accepting socket in constructor,
+ replace `Server::setAuth()` with optional constructor parameter,
+ remove undocumented "connection" event from Server and drop explicit Evenement dependency and
+ mark all classes as `final` and all internal APIs as `@internal`
+ (#78, #79, #80 and #84 by @clue)
+
+ ```php
+ // old: socket passed to server constructor
+ $socket = new React\Socket\Server(1080, $loop);
+ $server = new Clue\React\Socks\Server($loop, $socket);
+
+ // old: authentication via setAuthArray()/setAuth() methods
+ $server = new Clue\React\Socks\Server($loop, $socket);
+ $server->setAuthArray(array(
+ 'tom' => 'password',
+ 'admin' => 'root'
+ ));
+
+ // new: socket passed to listen() method
+ $server = new Clue\React\Socks\Server($loop);
+ $socket = new React\Socket\Server(1080, $loop);
+ $server->listen($socket);
+
+ // new: authentication passed to server constructor
+ $server = new Clue\React\Socks\Server($loop, null, array(
+ 'tom' => 'password',
+ 'admin' => 'root'
+ ));
+ $server->listen($socket);
+ ```
+
+* Feature: Improve error reporting for failed connections attempts by always including target URI in exceptions and
+ improve promise cancellation and clean up any garbage references.
+ (#82 and #83 by @clue)
+
+ All error messages now always contain a reference to the remote URI to give
+ more details which connection actually failed and the reason for this error.
+ Similarly, any underlying connection issues to the proxy server will now be
+ reported as part of the previous exception.
+
+ For most common use cases this means that simply reporting the `Exception`
+ message should give the most relevant details for any connection issues:
+
+ ```php
+ $promise = $proxy->connect('tcp://example.com:80');
+ $promise->then(function (ConnectionInterface $connection) {
+ // …
+ }, function (Exception $e) {
+ echo $e->getMessage();
+ });
+ ```
+
+* Improve documentation and examples, link to other projects and update project homepage.
+ (#73, #75 and #85 by @clue)
+
+## 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)
diff --git a/vendor/clue/socks-react/LICENSE b/vendor/clue/socks-react/LICENSE
new file mode 100644
index 0000000..8efa9aa
--- /dev/null
+++ b/vendor/clue/socks-react/LICENSE
@@ -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.
diff --git a/vendor/clue/socks-react/README.md b/vendor/clue/socks-react/README.md
new file mode 100644
index 0000000..c886a23
--- /dev/null
+++ b/vendor/clue/socks-react/README.md
@@ -0,0 +1,1116 @@
+# clue/reactphp-socks
+
+[![CI status](https://github.com/clue/reactphp-socks/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/reactphp-socks/actions)
+[![installs on Packagist](https://img.shields.io/packagist/dt/clue/socks-react?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/socks-react)
+
+Async SOCKS proxy connector client and server implementation, tunnel any TCP/IP-based
+protocol through a SOCKS5 or SOCKS4(a) proxy server, built on top of
+[ReactPHP](https://reactphp.org/).
+
+The SOCKS proxy protocol family (SOCKS5, SOCKS4 and SOCKS4a) is commonly used to
+tunnel HTTP(S) traffic through an intermediary ("proxy"), to conceal the origin
+address (anonymity) or to circumvent address blocking (geoblocking). While many
+(public) SOCKS proxy servers often limit this to HTTP(S) port `80` and `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 connections 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 SOCKS proxy support to pretty much any
+existing higher-level protocol implementation.
+Besides the client side, it also provides a simple SOCKS server implementation
+which allows you to build your own SOCKS proxy servers with custom business logic.
+
+* **Async execution of connections** -
+ Send any number of SOCKS 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
+ order 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*](https://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**
+
+* [Support us](#support-us)
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [Client](#client)
+ * [Plain TCP connections](#plain-tcp-connections)
+ * [Secure TLS connections](#secure-tls-connections)
+ * [HTTP requests](#http-requests)
+ * [Protocol version](#protocol-version)
+ * [DNS resolution](#dns-resolution)
+ * [Authentication](#authentication)
+ * [Proxy chaining](#proxy-chaining)
+ * [Connection timeout](#connection-timeout)
+ * [SOCKS over TLS](#socks-over-tls)
+ * [Unix domain sockets](#unix-domain-sockets)
+ * [Server](#server)
+ * [Server connector](#server-connector)
+ * [Authentication](#server-authentication)
+ * [Proxy chaining](#server-proxy-chaining)
+ * [SOCKS over TLS](#server-socks-over-tls)
+ * [Unix domain sockets](#server-unix-domain-sockets)
+* [Servers](#servers)
+ * [Using a PHP SOCKS server](#using-a-php-socks-server)
+ * [Using SSH as a SOCKS server](#using-ssh-as-a-socks-server)
+ * [Using the Tor (anonymity network) to tunnel SOCKS connections](#using-the-tor-anonymity-network-to-tunnel-socks-connections)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Quickstart example
+
+Once [installed](#install), you can use the following code to send a secure
+HTTPS request to google.com through a local SOCKS proxy server:
+
+```php
+<?php
+
+require __DIR__ . '/vendor/autoload.php';
+
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$browser = new React\Http\Browser($connector);
+
+$browser->get('https://google.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
+ var_dump($response->getHeaders(), (string) $response->getBody());
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+If you're not already running any other [SOCKS proxy server](#servers),
+you can use the following code to create a SOCKS
+proxy server listening for connections on `127.0.0.1:1080`:
+
+```php
+<?php
+
+require __DIR__ . '/vendor/autoload.php';
+
+// start a new SOCKS proxy server
+$socks = new Clue\React\Socks\Server();
+
+// listen on 127.0.0.1:1080
+$socket = new React\Socket\SocketServer('127.0.0.1:1080');
+$socks->listen($socket);
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### Client
+
+The `Client` is responsible for communication with your SOCKS server instance.
+
+Its constructor simply accepts a SOCKS proxy URI with the SOCKS proxy server address:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+```
+
+You can omit the port if you're using the default SOCKS port 1080:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1');
+```
+
+If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
+proxy servers etc.), you can explicitly pass a custom instance of the
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
+
+```php
+$connector = new React\Socket\Connector(array(
+ 'dns' => '127.0.0.1',
+ 'tcp' => array(
+ 'bindto' => '192.168.10.1:0'
+ )
+));
+
+$proxy = new Clue\React\Socks\Client('my-socks-server.local:1080', $connector);
+```
+
+This is one of the two main classes 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 SOCKS proxy support to pretty much any
+higher-level component:
+
+```diff
+- $acme = new AcmeApi($connector);
++ $proxy = new Clue\React\Socks\Client('127.0.0.1:1080', $connector);
++ $acme = new AcmeApi($proxy);
+```
+
+#### Plain TCP connections
+
+SOCKS proxies are most frequently used to issue HTTP(S) 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 Clue\React\Socks\Client('127.0.0.1:1080');
+
+$proxy->connect('tcp://www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ echo 'connected to www.google.com:80';
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+You can either use the `Client` directly or you may want to wrap this connector
+in ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector):
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$connector->connect('tcp://www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ echo 'connected to www.google.com:80';
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+See also the [first example](examples).
+
+The `tcp://` scheme can also be omitted.
+Passing any other scheme will reject the promise.
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $connector->connect($uri);
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying TCP/IP
+connection to the SOCKS server and/or the SOCKS protocol negotiation and reject
+the resulting promise.
+
+#### 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):
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$connector->connect('tls://www.google.com:443')->then(function (React\Socket\ConnectionInterface $connection) {
+ // proceed with just the plain text data
+ // everything is encrypted/decrypted automatically
+ echo 'connected to SSL encrypted www.google.com';
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+See also the [second example](examples).
+
+Pending connection attempts can be cancelled by canceling its pending promise
+as usual.
+
+> Note how secure TLS connections are in fact entirely handled outside of
+ this SOCKS client implementation.
+
+You can optionally pass additional
+[SSL context options](http://php.net/manual/en/context.ssl.php)
+to the constructor like this:
+
+```php
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ ),
+ 'dns' => false
+));
+```
+
+#### HTTP requests
+
+This library also allows you to send
+[HTTP requests through a SOCKS proxy server](https://github.com/reactphp/http#socks-proxy).
+
+In order to send HTTP requests, you first have to add a dependency for
+[ReactPHP's async HTTP client](https://github.com/reactphp/http#client-usage).
+This allows you to send both plain HTTP and TLS-encrypted HTTPS requests like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$browser = new React\Http\Browser($connector);
+
+$browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
+ var_dump($response->getHeaders(), (string) $response->getBody());
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+See also [ReactPHP's HTTP client](https://github.com/reactphp/http#client-usage)
+and any of the [examples](examples) for more details.
+
+#### Protocol version
+
+This library supports the SOCKS5 and SOCKS4(a) protocol versions.
+It focuses on the most commonly used core feature of connecting to a destination
+host through the SOCKS proxy server. In this mode, a SOCKS proxy server acts as
+a generic proxy allowing higher level application protocols to work through it.
+
+<table>
+ <tr>
+ <th></th>
+ <th>SOCKS5</th>
+ <th>SOCKS4(a)</th>
+ </tr>
+ <tr>
+ <th>Protocol specification</th>
+ <td><a href="https://tools.ietf.org/html/rfc1928">RFC 1928</a></td>
+ <td>
+ <a href="https://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol">SOCKS4.protocol</a> /
+ <a href="https://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4A.protocol">SOCKS4A.protocol</a>
+ </td>
+ </tr>
+ <tr>
+ <th>Tunnel outgoing TCP/IP connections</th>
+ <td>✓</td>
+ <td>✓</td>
+ </tr>
+ <tr>
+ <th><a href="#dns-resolution">Remote DNS resolution</a></th>
+ <td>✓</td>
+ <td>✗ / ✓</td>
+ </tr>
+ <tr>
+ <th>IPv6 addresses</th>
+ <td>✓</td>
+ <td>✗</td>
+ </tr>
+ <tr>
+ <th><a href="#authentication">Username/Password authentication</a></th>
+ <td>✓ (as per <a href="https://tools.ietf.org/html/rfc1929">RFC 1929</a>)</td>
+ <td>✗</td>
+ </tr>
+ <tr>
+ <th>Handshake # roundtrips</th>
+ <td>2 (3 with authentication)</td>
+ <td>1</td>
+ </tr>
+ <tr>
+ <th>Handshake traffic<br />+ remote DNS</th>
+ <td><em>variable</em> (+ auth + IPv6)<br />+ hostname - 3</td>
+ <td>17 bytes<br />+ hostname + 1</td>
+ </tr>
+ <tr>
+ <th>Incoming BIND requests</th>
+ <td><em>not implemented</em></td>
+ <td><em>not implemented</em></td>
+ </tr>
+ <tr>
+ <th>UDP datagrams</th>
+ <td><em>not implemented</em></td>
+ <td>✗</td>
+ </tr>
+ <tr>
+ <th>GSSAPI authentication</th>
+ <td><em>not implemented</em></td>
+ <td>✗</td>
+ </tr>
+</table>
+
+By default, the `Client` communicates via SOCKS5 with the SOCKS server.
+This is done because SOCKS5 is the latest version from the SOCKS protocol family
+and generally has best support across other vendors.
+You can also omit the default `socks://` URI scheme. Similarly, the `socks5://`
+URI scheme acts as an alias for the default `socks://` URI scheme.
+
+```php
+// all three forms are equivalent
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+$proxy = new Clue\React\Socks\Client('socks://127.0.0.1:1080');
+$proxy = new Clue\React\Socks\Client('socks5://127.0.0.1:1080');
+```
+
+If want to explicitly set the protocol version to SOCKS4(a), you can use the URI
+scheme `socks4://` as part of the SOCKS URI:
+
+```php
+$proxy = new Clue\React\Socks\Client('socks4://127.0.0.1:1080');
+```
+
+#### DNS resolution
+
+By default, the `Client` does not perform any DNS resolution at all and simply
+forwards any hostname you're trying to connect to to the SOCKS server.
+The remote SOCKS server is thus responsible for looking up any hostnames via DNS
+(this default mode is thus called *remote DNS resolution*).
+As seen above, this mode is supported by the SOCKS5 and SOCKS4a protocols, but
+not the original SOCKS4 protocol, as the protocol lacks a way to communicate hostnames.
+
+On the other hand, all SOCKS protocol versions support sending destination IP
+addresses to the SOCKS 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 `Client` 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 (in particular when using the
+[Tor network](#using-the-tor-anonymity-network-to-tunnel-socks-connections)).
+
+As noted above, the `Client` defaults to using remote DNS resolution.
+However, wrapping the `Client` 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 resolution like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+```
+
+If you want to explicitly use *local DNS resolution* (such as when explicitly
+using SOCKS4), you can use the following code:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+// set up Connector which uses Google's public DNS (8.8.8.8)
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => '8.8.8.8'
+));
+```
+
+See also the [fourth example](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise
+as usual.
+
+> Note how local DNS resolution is in fact entirely handled outside of this
+ SOCKS client implementation.
+
+#### Authentication
+
+This library supports username/password authentication for SOCKS5 servers as
+defined in [RFC 1929](https://tools.ietf.org/html/rfc1929).
+
+On the client side, simply pass your username and password to use for
+authentication (see below).
+For each further connection the client will merely send a flag to the server
+indicating authentication information is available.
+Only if the server requests authentication during the initial handshake,
+the actual authentication credentials will be transmitted to the server.
+
+Note that the password is transmitted in cleartext to the SOCKS proxy server,
+so this methods should not be used on a network where you have to worry about eavesdropping.
+
+You can simply pass the authentication information as part of the SOCKS URI:
+
+```php
+$proxy = new Clue\React\Socks\Client('alice:password@127.0.0.1:1080');
+```
+
+Note that both the username and password must be percent-encoded if they contain
+special characters:
+
+```php
+$user = 'he:llo';
+$pass = 'p@ss';
+$url = rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1:1080';
+
+$proxy = new Clue\React\Socks\Client($url);
+```
+
+> The authentication details will be transmitted in cleartext to the SOCKS proxy
+ server only if it requires username/password authentication.
+ If the authentication details are missing or not accepted by the remote SOCKS
+ proxy server, it is expected to reject each connection attempt with an
+ exception error code of `SOCKET_EACCES` (13).
+
+Authentication is only supported by protocol version 5 (SOCKS5),
+so passing authentication to the `Client` enforces communication with protocol
+version 5 and complains if you have explicitly set anything else:
+
+```php
+// throws InvalidArgumentException
+new Clue\React\Socks\Client('socks4://alice:password@127.0.0.1:1080');
+```
+
+#### Proxy chaining
+
+The `Client` is responsible for creating connections to the SOCKS server which
+then connects to the target host.
+
+```
+Client -> SocksServer -> TargetHost
+```
+
+Sometimes it may be required to establish outgoing connections via another SOCKS
+server.
+For example, this can be useful if you want to conceal your origin address.
+
+```
+Client -> MiddlemanSocksServer -> TargetSocksServer -> TargetHost
+```
+
+The `Client` uses any instance of the `ConnectorInterface` to establish
+outgoing connections.
+In order to connect through another SOCKS server, you can simply use another
+SOCKS connector from another SOCKS client like this:
+
+```php
+// https via the proxy chain "MiddlemanSocksServer -> TargetSocksServer -> TargetHost"
+// please note how the client uses TargetSocksServer (not MiddlemanSocksServer!),
+// which in turn then uses MiddlemanSocksServer.
+// this creates a TCP/IP connection to MiddlemanSocksServer, which then connects
+// to TargetSocksServer, which then connects to the TargetHost
+$middle = new Clue\React\Socks\Client('127.0.0.1:1080');
+$target = new Clue\React\Socks\Client('example.com:1080', $middle);
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $target,
+ 'dns' => false
+));
+
+$connector->connect('tls://www.google.com:443')->then(function (React\Socket\ConnectionInterface $connection) {
+ // …
+});
+```
+
+See also the [third example](examples).
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+Proxy chaining can happen on the server side and/or the client side:
+
+* If you ask your client to chain through multiple proxies, then each proxy
+ server does not really know anything about chaining at all.
+ This means that this is a client-only property.
+
+* If you ask your server to chain through another proxy, then your client does
+ not really know anything about chaining at all.
+ This means that this is a server-only property and not part of this class.
+ For example, you can find this in the below [`Server`](#server-proxy-chaining)
+ class or somewhat similar when you're using the
+ [Tor network](#using-the-tor-anonymity-network-to-tunnel-socks-connections).
+
+#### Connection timeout
+
+By default, the `Client` 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)
+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
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$connector = new React\Socket\Connector(array(
+ 'tcp' => $proxy,
+ 'dns' => false,
+ 'timeout' => 3.0
+));
+
+$connector->connect('tcp://google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise
+as usual.
+
+> Note how connection timeout is in fact entirely handled outside of this
+ SOCKS client implementation.
+
+#### SOCKS over TLS
+
+All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+This implies that you can also use [secure TLS connections](#secure-tls-connections)
+to transfer sensitive data across SOCKS proxy servers.
+This means that no eavesdropper nor the proxy server will be able to decrypt
+your data.
+
+However, the initial SOCKS communication between the client and the proxy is
+usually via an unencrypted, plain TCP/IP connection.
+This means that an eavesdropper may be able to see *where* you connect to and
+may also be able to see your [SOCKS authentication](#authentication) details
+in cleartext.
+
+As an alternative, you may establish a secure TLS connection to your SOCKS
+proxy before starting the initial SOCKS communication.
+This means that no eavesdroppper will be able to see the destination address
+you want to connect to or your [SOCKS authentication](#authentication) details.
+
+You can use the `sockss://` URI scheme or use an explicit
+[SOCKS protocol version](#protocol-version) like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('sockss://127.0.0.1:1080');
+
+$proxy = new Clue\React\Socks\Client('socks4s://127.0.0.1:1080');
+```
+
+See also [example 32](examples).
+
+Similarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('sockss://alice:password@127.0.0.1:1080');
+```
+
+> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
+ should be used instead. SOCKS over TLS is considered advanced usage and is
+ used very rarely in practice.
+ In particular, the SOCKS server has to accept secure TLS connections, see
+ also [Server SOCKS over TLS](#server-socks-over-tls) for more details.
+ Also, PHP does not support "double encryption" over a single connection.
+ This means that enabling [secure TLS connections](#secure-tls-connections)
+ over a communication channel that has been opened with SOCKS over TLS
+ may not be supported.
+
+> Note that the SOCKS protocol does not support the notion of TLS. The above
+ works reasonably well because TLS is only used for the connection between
+ client and proxy server and the SOCKS protocol data is otherwise identical.
+ This implies that this may also have only limited support for
+ [proxy chaining](#proxy-chaining) over multiple TLS paths.
+
+#### Unix domain sockets
+
+All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+In some advanced cases, it may be useful to let your SOCKS 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 use the `socks+unix://` URI scheme or use an explicit
+[SOCKS protocol version](#protocol-version) like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('socks+unix:///tmp/proxy.sock');
+
+$proxy = new Clue\React\Socks\Client('socks4+unix:///tmp/proxy.sock');
+```
+
+Similarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$proxy = new Clue\React\Socks\Client('socks+unix://alice:password@/tmp/proxy.sock');
+```
+
+> 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 SOCKS 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 also not support [proxy chaining](#proxy-chaining)
+ over multiple UDS paths.
+
+### Server
+
+The `Server` is responsible for accepting incoming communication from SOCKS clients
+and forwarding the requested connection to the target host.
+It supports the SOCKS5 and SOCKS4(a) protocol versions by default.
+You can start listening on an underlying TCP/IP socket server like this:
+
+```php
+$socks = new Clue\React\Socks\Server();
+
+// listen on 127.0.0.1:1080
+$socket = new React\Socket\SocketServer('127.0.0.1:1080');
+$socks->listen($socket);
+```
+
+This class takes an optional `LoopInterface|null $loop` parameter that can be used to
+pass the event loop instance to use for this object. You can use a `null` value
+here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
+This value SHOULD NOT be given unless you're sure you want to explicitly use a
+given event loop instance.
+
+Additionally, the `Server` constructor accepts optional parameters to explicitly
+configure the [connector](#server-connector) to use and to require
+[authentication](#server-authentication). For more details, read on...
+
+#### Server connector
+
+The `Server` uses an instance of ReactPHP's
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+to establish outgoing connections for each incoming connection request.
+
+If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
+proxy servers etc.), you can explicitly pass a custom instance of the
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
+
+```php
+$connector = new React\Socket\Connector(array(
+ 'dns' => '127.0.0.1',
+ 'tcp' => array(
+ 'bindto' => '192.168.10.1:0'
+ )
+));
+
+$socks = new Clue\React\Socks\Server(null, $connector);
+```
+
+If you want to forward the outgoing connection through another SOCKS proxy, you
+may also pass a [`Client`](#client) instance as a connector, see also
+[server proxy chaining](#server-proxy-chaining) for more details.
+
+Internally, the `Server` uses ReactPHP's normal
+[`connect()`](https://github.com/reactphp/socket#connect) method, but
+it also passes the original client IP as the `?source={remote}` parameter.
+The `source` parameter contains the full remote URI, including the protocol
+and any authentication details, for example `socks://alice:password@1.2.3.4:5678`
+or `socks4://1.2.3.4:5678` for legacy SOCKS4(a).
+You can use this parameter for logging purposes or to restrict connection
+requests for certain clients by providing a custom implementation of the
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface).
+
+#### Server authentication
+
+By default, the `Server` does not require any authentication from the clients.
+You can enable authentication support so that clients need to pass a valid
+username and password before forwarding any connections.
+
+Setting authentication on the `Server` enforces each further connected client
+to use protocol version 5 (SOCKS5).
+If a client tries to use any other protocol version, does not send along
+authentication details or if authentication details can not be verified,
+the connection will be rejected.
+
+If you only want to accept static authentication details, you can simply pass an
+additional assoc array with your authentication details to the `Server` like this:
+
+```php
+$socks = new Clue\React\Socks\Server(null, null, array(
+ 'alice' => 'password',
+ 'bob' => 's3cret!1'
+));
+```
+
+See also [example #12](examples).
+
+If you want more control over authentication, you can pass an authenticator
+function that should return a `bool` value like this synchronous example:
+
+```php
+$socks = new Clue\React\Socks\Server(null, null, function ($username, $password, $remote) {
+ // $remote is a full URI à la socks://alice:password@192.168.1.1:1234
+ // or sockss://alice:password@192.168.1.1:1234 for SOCKS over TLS
+ // or may be null when remote is unknown (SOCKS over Unix Domain Sockets)
+ // useful for logging or extracting parts, such as the remote IP
+ $ip = parse_url($remote, PHP_URL_HOST);
+
+ return ($username === 'root' && $password === 'secret' && $ip === '127.0.0.1');
+});
+```
+
+Because your authentication mechanism might take some time to actually check the
+provided authentication credentials (like querying a remote database or webservice),
+the server also supports a [Promise](https://github.com/reactphp/promise)-based
+interface. While this might seem more complex at first, it actually provides a
+very powerful way of handling a large number of connections concurrently without
+ever blocking any connections. You can return a [Promise](https://github.com/reactphp/promise)
+from the authenticator function that will fulfill with a `bool` value like this
+async example:
+
+```php
+$socks = new Clue\React\Socks\Server(null, null, function ($username, $password) use ($db) {
+ // pseudo-code: query database for given authentication details
+ return $db->query(
+ 'SELECT 1 FROM users WHERE name = ? AND password = ?',
+ array($username, $password)
+ )->then(function (QueryResult $result) {
+ // ensure we find exactly one match in the database
+ return count($result->resultRows) === 1;
+ });
+});
+```
+
+#### Server proxy chaining
+
+The `Server` is responsible for creating connections to the target host.
+
+```
+Client -> SocksServer -> TargetHost
+```
+
+Sometimes it may be required to establish outgoing connections via another SOCKS
+server.
+For example, this can be useful if your target SOCKS server requires
+authentication, but your client does not support sending authentication
+information (e.g. like most webbrowser).
+
+```
+Client -> MiddlemanSocksServer -> TargetSocksServer -> TargetHost
+```
+
+The `Server` uses any instance of the `ConnectorInterface` to establish outgoing
+connections.
+In order to connect through another SOCKS server, you can simply use the
+[`Client`](#client) SOCKS connector from above.
+You can create a SOCKS `Client` instance like this:
+
+```php
+// set next SOCKS server example.com:1080 as target
+$proxy = new Clue\React\Socks\Client('alice:password@example.com:1080');
+
+// start a new server which forwards all connections to the other SOCKS server
+$socks = new Clue\React\Socks\Server(null, $proxy);
+
+// listen on 127.0.0.1:1080
+$socket = new React\Socket\SocketServer('127.0.0.1:1080');
+$socks->listen($socket);
+```
+
+See also [example #21](examples).
+
+Proxy chaining can happen on the server side and/or the client side:
+
+* If you ask your client to chain through multiple proxies, then each proxy
+ server does not really know anything about chaining at all.
+ This means that this is a client-only property and not part of this class.
+ For example, you can find this in the above [`Client`](#proxy-chaining) class.
+
+* If you ask your server to chain through another proxy, then your client does
+ not really know anything about chaining at all.
+ This means that this is a server-only property and can be implemented as above.
+
+#### Server SOCKS over TLS
+
+Both SOCKS5 and SOCKS4(a) protocol versions support forwarding TCP/IP based
+connections and higher level protocols.
+This implies that you can also use [secure TLS connections](#secure-tls-connections)
+to transfer sensitive data across SOCKS proxy servers.
+This means that no eavesdropper nor the proxy server will be able to decrypt
+your data.
+
+However, the initial SOCKS communication between the client and the proxy is
+usually via an unencrypted, plain TCP/IP connection.
+This means that an eavesdropper may be able to see *where* the client connects
+to and may also be able to see the [SOCKS authentication](#authentication)
+details in cleartext.
+
+As an alternative, you may listen for SOCKS over TLS connections so
+that the client has to establish a secure TLS connection to your SOCKS
+proxy before starting the initial SOCKS communication.
+This means that no eavesdroppper will be able to see the destination address
+the client wants to connect to or their [SOCKS authentication](#authentication)
+details.
+
+You can simply start your listening socket on the `tls://` URI scheme like this:
+
+```php
+$socks = new Clue\React\Socks\Server();
+
+// listen on tls://127.0.0.1:1080 with the given server certificate
+$socket = new React\Socket\SocketServer('tls://127.0.0.1:1080', array(
+ 'tls' => array(
+ 'local_cert' => __DIR__ . '/localhost.pem',
+ )
+));
+$socks->listen($socket);
+```
+
+See also [example 31](examples).
+
+> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
+ should be used instead. SOCKS over TLS is considered advanced usage and is
+ used very rarely in practice.
+
+> Note that the SOCKS protocol does not support the notion of TLS. The above
+ works reasonably well because TLS is only used for the connection between
+ client and proxy server and the SOCKS protocol data is otherwise identical.
+ This implies that this does also not support [proxy chaining](#server-proxy-chaining)
+ over multiple TLS paths.
+
+#### Server Unix domain sockets
+
+Both SOCKS5 and SOCKS4(a) protocol versions support forwarding TCP/IP based
+connections and higher level protocols.
+In some advanced cases, it may be useful to let your SOCKS 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](#server-authentication).
+
+You can simply start your listening socket on the `unix://` URI scheme like this:
+
+```php
+$socks = new Clue\React\Socks\Server();
+
+// listen on /tmp/proxy.sock
+$socket = new React\Socket\SocketServer('unix:///tmp/proxy.sock');
+$socks->listen($socket);
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and that
+ the SOCKS 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 also not support [proxy chaining](#server-proxy-chaining)
+ over multiple UDS paths.
+
+## Servers
+
+### Using a PHP SOCKS server
+
+* If you're looking for an end-user SOCKS server daemon, you may want to use
+ [LeProxy](https://leproxy.org/) or [clue/psocksd](https://github.com/clue/psocksd).
+* If you're looking for a SOCKS server implementation, consider using
+ the above [`Server`](#server) class.
+
+### Using SSH as a SOCKS server
+
+If you already have an SSH server set up, you can easily use it as a SOCKS
+tunnel end point. On your client, simply start your SSH client and use
+the `-D <port>` option to start a local SOCKS server (quoting the man page:
+a `local "dynamic" application-level port forwarding`).
+
+You can start a local SOCKS server by creating a loopback connection to your
+local system if you already run an SSH daemon:
+
+```bash
+ssh -D 1080 localhost
+```
+
+Alternatively, you can start a local SOCKS server tunneling through a given
+remote host that runs an SSH daemon:
+
+```bash
+ssh -D 1080 example.com
+```
+
+Now you can simply use this SSH SOCKS server like this:
+
+```PHP
+$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');
+
+$proxy->connect('tcp://www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+Note that the above will allow all users on the local system to connect over
+your SOCKS server without authentication which may or may not be what you need.
+As an alternative, recent OpenSSH client versions also support
+[Unix domain sockets](#unix-domain-sockets) (UDS) paths so that you can rely
+on Unix file system permissions instead:
+
+```bash
+ssh -D/tmp/proxy.sock example.com
+```
+
+Now you can simply use this SSH SOCKS server like this:
+
+```PHP
+$proxy = new Clue\React\Socks\Client('socks+unix:///tmp/proxy.sock');
+
+$proxy->connect('tcp://www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+> As an alternative to requiring this manual setup, you may also want to look
+ into using [clue/reactphp-ssh-proxy](https://github.com/clue/reactphp-ssh-proxy)
+ which automatically creates this SSH tunnel for you. It provides an implementation of the same
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ so that supporting either proxy protocol should be fairly trivial.
+
+### Using the Tor (anonymity network) to tunnel SOCKS connections
+
+The [Tor anonymity network](https://www.torproject.org/) client software is designed
+to encrypt your traffic and route it over a network of several nodes to conceal its origin.
+It presents a SOCKS5 and SOCKS4(a) interface on TCP port 9050 by default
+which allows you to tunnel any traffic through the anonymity network:
+
+```php
+$proxy = new Clue\React\Socks\Client('127.0.0.1:9050');
+
+$proxy->connect('tcp://www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
+ $connection->write("GET / HTTP/1.0\r\n\r\n");
+
+ $connection->on('data', function ($chunk) {
+ echo $chunk;
+ });
+});
+```
+
+In most common scenarios you probably want to stick to default
+[remote DNS resolution](#dns-resolution) and don't want your client to resolve the target hostnames,
+because you would leak DNS information to anybody observing your local traffic.
+Also, Tor provides hidden services through an `.onion` pseudo top-level domain
+which have to be resolved by Tor.
+
+## 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](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+composer require clue/socks-react:^1.4
+```
+
+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 8+ and
+HHVM.
+It's *highly recommended to use the latest supported PHP version* 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
+vendor/bin/phpunit
+```
+
+The test suite contains a number of tests that rely on a working internet
+connection, alternatively you can also run it like this:
+
+```bash
+vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
+
+## 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 component](https://github.com/reactphp/socket).
+* 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 a SOCKS5 / SOCKS4(a) proxy, you may also want to look into
+ using an HTTP CONNECT proxy instead.
+ You may want to use [clue/reactphp-http-proxy](https://github.com/clue/reactphp-http-proxy)
+ 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.
+* As an alternative to a SOCKS5 / SOCKS4(a) proxy, you may also want to look into
+ using an SSH proxy (SSH tunnel) instead.
+ You may want to use [clue/reactphp-ssh-proxy](https://github.com/clue/reactphp-ssh-proxy)
+ 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/reactphp-connection-manager-extra](https://github.com/clue/reactphp-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 SOCKS server daemon, you may want to use
+ [LeProxy](https://leproxy.org/) or [clue/psocksd](https://github.com/clue/psocksd).
diff --git a/vendor/clue/socks-react/composer.json b/vendor/clue/socks-react/composer.json
new file mode 100644
index 0000000..2a26a7a
--- /dev/null
+++ b/vendor/clue/socks-react/composer.json
@@ -0,0 +1,31 @@
+{
+ "name": "clue/socks-react",
+ "description": "Async SOCKS proxy connector client and server implementation, tunnel any TCP/IP-based protocol through a SOCKS5 or SOCKS4(a) proxy server, built on top of ReactPHP.",
+ "keywords": ["socks client", "socks server", "socks5", "socks4a", "proxy server", "tcp tunnel", "async", "ReactPHP"],
+ "homepage": "https://github.com/clue/reactphp-socks",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering"
+ }
+ ],
+ "require": {
+ "php": ">=5.3",
+ "react/promise": "^3 || ^2.1 || ^1.2",
+ "react/socket": "^1.12"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.5",
+ "clue/connection-manager-extra": "^1.3",
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
+ "react/event-loop": "^1.2",
+ "react/http": "^1.6"
+ },
+ "autoload": {
+ "psr-4": { "Clue\\React\\Socks\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Clue\\Tests\\React\\Socks\\": "tests/" }
+ }
+}
diff --git a/vendor/clue/socks-react/src/Client.php b/vendor/clue/socks-react/src/Client.php
new file mode 100644
index 0000000..5645c25
--- /dev/null
+++ b/vendor/clue/socks-react/src/Client.php
@@ -0,0 +1,458 @@
+<?php
+
+namespace Clue\React\Socks;
+
+use React\Promise;
+use React\Promise\PromiseInterface;
+use React\Promise\Deferred;
+use React\Socket\ConnectionInterface;
+use React\Socket\Connector;
+use React\Socket\ConnectorInterface;
+use React\Socket\FixedUriConnector;
+use Exception;
+use InvalidArgumentException;
+use RuntimeException;
+
+final class Client implements ConnectorInterface
+{
+ /**
+ * @var ConnectorInterface
+ */
+ private $connector;
+
+ private $socksUri;
+
+ private $protocolVersion = 5;
+
+ private $auth = null;
+
+ /**
+ * @param string $socksUri
+ * @param ?ConnectorInterface $connector
+ * @throws InvalidArgumentException
+ */
+ public function __construct(
+ #[\SensitiveParameter]
+ $socksUri,
+ ConnectorInterface $connector = null
+ ) {
+ // support `sockss://` scheme for SOCKS over TLS
+ // support `socks+unix://` scheme for Unix domain socket (UDS) paths
+ if (preg_match('/^(socks(?:5|4)?)(s|\+unix):\/\/(.*?@)?(.+?)$/', $socksUri, $match)) {
+ // rewrite URI to parse SOCKS scheme, authentication and dummy host
+ $socksUri = $match[1] . '://' . $match[3] . 'localhost';
+
+ // connector uses appropriate transport scheme and explicit host given
+ $connector = new FixedUriConnector(
+ ($match[2] === 's' ? 'tls://' : 'unix://') . $match[4],
+ $connector ?: new Connector()
+ );
+ }
+
+ // assume default scheme if none is given
+ if (strpos($socksUri, '://') === false) {
+ $socksUri = 'socks://' . $socksUri;
+ }
+
+ // parse URI into individual parts
+ $parts = parse_url($socksUri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'])) {
+ throw new InvalidArgumentException('Invalid SOCKS server URI "' . $socksUri . '"');
+ }
+
+ // assume default port
+ if (!isset($parts['port'])) {
+ $parts['port'] = 1080;
+ }
+
+ // user or password in URI => SOCKS5 authentication
+ if (isset($parts['user']) || isset($parts['pass'])) {
+ if ($parts['scheme'] !== 'socks' && $parts['scheme'] !== 'socks5') {
+ // fail if any other protocol version given explicitly
+ throw new InvalidArgumentException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
+ }
+ $parts += array('user' => '', 'pass' => '');
+ $this->setAuth(rawurldecode($parts['user']), rawurldecode($parts['pass']));
+ }
+
+ // check for valid protocol version from URI scheme
+ $this->setProtocolVersionFromScheme($parts['scheme']);
+
+ $this->socksUri = $parts['host'] . ':' . $parts['port'];
+ $this->connector = $connector ?: new Connector();
+ }
+
+ private function setProtocolVersionFromScheme($scheme)
+ {
+ if ($scheme === 'socks' || $scheme === 'socks5') {
+ $this->protocolVersion = 5;
+ } elseif ($scheme === 'socks4') {
+ $this->protocolVersion = 4;
+ } else {
+ throw new InvalidArgumentException('Invalid protocol version given "' . $scheme . '://"');
+ }
+ }
+
+ /**
+ * set login data for username/password authentication method (RFC1929)
+ *
+ * @param string $username
+ * @param string $password
+ * @link http://tools.ietf.org/html/rfc1929
+ */
+ private function setAuth(
+ $username,
+ #[\SensitiveParameter]
+ $password
+ ) {
+ if (strlen($username) > 255 || strlen($password) > 255) {
+ throw new InvalidArgumentException('Both username and password MUST NOT exceed a length of 255 bytes each');
+ }
+ $this->auth = pack('C2', 0x01, strlen($username)) . $username . pack('C', strlen($password)) . $password;
+ }
+
+ /**
+ * Establish a TCP/IP connection to the given target URI through the SOCKS server
+ *
+ * Many higher-level networking protocols build on top of TCP. It you're dealing
+ * with one such client implementation, it probably uses/accepts an instance
+ * implementing ReactPHP's `ConnectorInterface` (and usually its default `Connector`
+ * instance). In this case you can also pass this `Connector` instance instead
+ * to make this client implementation SOCKS-aware. That's it.
+ *
+ * @param string $uri
+ * @return PromiseInterface Promise<ConnectionInterface,Exception>
+ */
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
+ }
+
+ $host = trim($parts['host'], '[]');
+ $port = $parts['port'];
+
+ if (strlen($host) > 255 || $port > 65535 || $port < 0 || (string)$port !== (string)(int)$port) {
+ return Promise\reject(new InvalidArgumentException('Invalid target specified'));
+ }
+
+ // construct URI to SOCKS server to connect to
+ $socksUri = $this->socksUri;
+
+ // append path from URI if given
+ if (isset($parts['path'])) {
+ $socksUri .= $parts['path'];
+ }
+
+ // parse query args
+ $args = array();
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $args);
+ }
+
+ // append hostname from URI to query string unless explicitly given
+ if (!isset($args['hostname'])) {
+ $args['hostname'] = $host;
+ }
+
+ // append query string
+ $socksUri .= '?' . http_build_query($args, '', '&');
+
+ // append fragment from URI if given
+ if (isset($parts['fragment'])) {
+ $socksUri .= '#' . $parts['fragment'];
+ }
+
+ // start TCP/IP connection to SOCKS server
+ $connecting = $this->connector->connect($socksUri);
+
+ $deferred = new Deferred(function ($_, $reject) use ($uri, $connecting) {
+ $reject(new RuntimeException(
+ 'Connection to ' . $uri . ' cancelled while waiting for proxy (ECONNABORTED)',
+ defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103
+ ));
+
+ // either close active connection or cancel pending connection attempt
+ $connecting->then(function (ConnectionInterface $stream) {
+ $stream->close();
+ });
+ $connecting->cancel();
+ });
+
+ // handle SOCKS protocol once connection is ready
+ // resolve plain connection once SOCKS protocol is completed
+ $that = $this;
+ $connecting->then(
+ function (ConnectionInterface $stream) use ($that, $host, $port, $deferred, $uri) {
+ $that->handleConnectedSocks($stream, $host, $port, $deferred, $uri);
+ },
+ function (Exception $e) use ($uri, $deferred) {
+ $deferred->reject($e = new RuntimeException(
+ 'Connection to ' . $uri . ' failed because connection to proxy failed (ECONNREFUSED)',
+ defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111,
+ $e
+ ));
+
+ // avoid garbage references by replacing all closures in call stack.
+ // what a lovely piece of code!
+ $r = new \ReflectionProperty('Exception', 'trace');
+ $r->setAccessible(true);
+ $trace = $r->getValue($e);
+
+ // Exception trace arguments are not available on some PHP 7.4 installs
+ // @codeCoverageIgnoreStart
+ foreach ($trace as $ti => $one) {
+ if (isset($one['args'])) {
+ foreach ($one['args'] as $ai => $arg) {
+ if ($arg instanceof \Closure) {
+ $trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
+ }
+ }
+ }
+ }
+ // @codeCoverageIgnoreEnd
+ $r->setValue($e, $trace);
+ }
+ );
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Internal helper used to handle the communication with the SOCKS server
+ *
+ * @param ConnectionInterface $stream
+ * @param string $host
+ * @param int $port
+ * @param Deferred $deferred
+ * @param string $uri
+ * @return void
+ * @internal
+ */
+ public function handleConnectedSocks(ConnectionInterface $stream, $host, $port, Deferred $deferred, $uri)
+ {
+ $reader = new StreamReader();
+ $stream->on('data', array($reader, 'write'));
+
+ $stream->on('error', $onError = function (Exception $e) use ($deferred, $uri) {
+ $deferred->reject(new RuntimeException(
+ 'Connection to ' . $uri . ' failed because connection to proxy caused a stream error (EIO)',
+ defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e)
+ );
+ });
+
+ $stream->on('close', $onClose = function () use ($deferred, $uri) {
+ $deferred->reject(new RuntimeException(
+ 'Connection to ' . $uri . ' failed because connection to proxy was lost while waiting for response from proxy (ECONNRESET)',
+ defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104)
+ );
+ });
+
+ if ($this->protocolVersion === 5) {
+ $promise = $this->handleSocks5($stream, $host, $port, $reader, $uri);
+ } else {
+ $promise = $this->handleSocks4($stream, $host, $port, $reader, $uri);
+ }
+
+ $promise->then(function () use ($deferred, $stream, $reader, $onError, $onClose) {
+ $stream->removeListener('data', array($reader, 'write'));
+ $stream->removeListener('error', $onError);
+ $stream->removeListener('close', $onClose);
+
+ $deferred->resolve($stream);
+ }, function (Exception $error) use ($deferred, $stream, $uri) {
+ // pass custom RuntimeException through as-is, otherwise wrap in protocol error
+ if (!$error instanceof RuntimeException) {
+ $error = new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy returned invalid response (EBADMSG)',
+ defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71,
+ $error
+ );
+ }
+
+ $deferred->reject($error);
+ $stream->close();
+ });
+ }
+
+ private function handleSocks4(ConnectionInterface $stream, $host, $port, StreamReader $reader, $uri)
+ {
+ // do not resolve hostname. only try to convert to IP
+ $ip = ip2long($host);
+
+ // send IP or (0.0.0.1) if invalid
+ $data = pack('C2nNC', 0x04, 0x01, $port, $ip === false ? 1 : $ip, 0x00);
+
+ if ($ip === false) {
+ // host is not a valid IP => send along hostname (SOCKS4a)
+ $data .= $host . pack('C', 0x00);
+ }
+
+ $stream->write($data);
+
+ return $reader->readBinary(array(
+ 'null' => 'C',
+ 'status' => 'C',
+ 'port' => 'n',
+ 'ip' => 'N'
+ ))->then(function ($data) use ($uri) {
+ if ($data['null'] !== 0x00) {
+ throw new Exception('Invalid SOCKS response');
+ }
+ if ($data['status'] !== 0x5a) {
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy refused connection with error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)',
+ defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
+ );
+ }
+ });
+ }
+
+ private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamReader $reader, $uri)
+ {
+ // protocol version 5
+ $data = pack('C', 0x05);
+
+ $auth = $this->auth;
+ if ($auth === null) {
+ // one method, no authentication
+ $data .= pack('C2', 0x01, 0x00);
+ } else {
+ // two methods, username/password and no authentication
+ $data .= pack('C3', 0x02, 0x02, 0x00);
+ }
+ $stream->write($data);
+
+ $that = $this;
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'method' => 'C'
+ ))->then(function ($data) use ($auth, $stream, $reader, $uri) {
+ if ($data['version'] !== 0x05) {
+ throw new Exception('Version/Protocol mismatch');
+ }
+
+ if ($data['method'] === 0x02 && $auth !== null) {
+ // username/password authentication requested and provided
+ $stream->write($auth);
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'status' => 'C'
+ ))->then(function ($data) use ($uri) {
+ if ($data['version'] !== 0x01 || $data['status'] !== 0x00) {
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy denied access with given authentication details (EACCES)',
+ defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
+ );
+ }
+ });
+ } else if ($data['method'] !== 0x00) {
+ // any other method than "no authentication"
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy denied access due to unsupported authentication method (EACCES)',
+ defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
+ );
+ }
+ })->then(function () use ($stream, $reader, $host, $port) {
+ // do not resolve hostname. only try to convert to (binary/packed) IP
+ $ip = @inet_pton($host);
+
+ $data = pack('C3', 0x05, 0x01, 0x00);
+ if ($ip === false) {
+ // not an IP, send as hostname
+ $data .= pack('C2', 0x03, strlen($host)) . $host;
+ } else {
+ // send as IPv4 / IPv6
+ $data .= pack('C', (strpos($host, ':') === false) ? 0x01 : 0x04) . $ip;
+ }
+ $data .= pack('n', $port);
+
+ $stream->write($data);
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'status' => 'C',
+ 'null' => 'C',
+ 'type' => 'C'
+ ));
+ })->then(function ($data) use ($reader, $uri) {
+ if ($data['version'] !== 0x05 || $data['null'] !== 0x00) {
+ throw new Exception('Invalid SOCKS response');
+ }
+ if ($data['status'] !== 0x00) {
+ // map limited list of SOCKS error codes to common socket error conditions
+ // @link https://tools.ietf.org/html/rfc1928#section-6
+ if ($data['status'] === Server::ERROR_GENERAL) {
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy refused connection with general server failure (ECONNREFUSED)',
+ defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
+ );
+ } elseif ($data['status'] === Server::ERROR_NOT_ALLOWED_BY_RULESET) {
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy denied access due to ruleset (EACCES)',
+ defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
+ );
+ } elseif ($data['status'] === Server::ERROR_NETWORK_UNREACHABLE) {
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy reported network unreachable (ENETUNREACH)',
+ defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101
+ );
+ } elseif ($data['status'] === Server::ERROR_HOST_UNREACHABLE) {
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy reported host unreachable (EHOSTUNREACH)',
+ defined('SOCKET_EHOSTUNREACH') ? SOCKET_EHOSTUNREACH : 113
+ );
+ } elseif ($data['status'] === Server::ERROR_CONNECTION_REFUSED) {
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy reported connection refused (ECONNREFUSED)',
+ defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
+ );
+ } elseif ($data['status'] === Server::ERROR_TTL) {
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy reported TTL/timeout expired (ETIMEDOUT)',
+ defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110
+ );
+ } elseif ($data['status'] === Server::ERROR_COMMAND_UNSUPPORTED) {
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy does not support the CONNECT command (EPROTO)',
+ defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71
+ );
+ } elseif ($data['status'] === Server::ERROR_ADDRESS_UNSUPPORTED) {
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy does not support this address type (EPROTO)',
+ defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71
+ );
+ }
+
+ throw new RuntimeException(
+ 'Connection to ' . $uri . ' failed because proxy server refused connection with unknown error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)',
+ defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
+ );
+ }
+ if ($data['type'] === 0x01) {
+ // IPv4 address => skip IP and port
+ return $reader->readLength(6);
+ } elseif ($data['type'] === 0x03) {
+ // domain name => read domain name length
+ return $reader->readBinary(array(
+ 'length' => 'C'
+ ))->then(function ($data) use ($reader) {
+ // skip domain name and port
+ return $reader->readLength($data['length'] + 2);
+ });
+ } elseif ($data['type'] === 0x04) {
+ // IPv6 address => skip IP and port
+ return $reader->readLength(18);
+ } else {
+ throw new Exception('Invalid SOCKS reponse: Invalid address type');
+ }
+ });
+ }
+}
diff --git a/vendor/clue/socks-react/src/Server.php b/vendor/clue/socks-react/src/Server.php
new file mode 100644
index 0000000..ca45015
--- /dev/null
+++ b/vendor/clue/socks-react/src/Server.php
@@ -0,0 +1,416 @@
+<?php
+
+namespace Clue\React\Socks;
+
+use React\Socket\ServerInterface;
+use React\Promise\PromiseInterface;
+use React\Socket\ConnectorInterface;
+use React\Socket\Connector;
+use React\Socket\ConnectionInterface;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use \UnexpectedValueException;
+use \InvalidArgumentException;
+use \Exception;
+use React\Promise\Timer\TimeoutException;
+
+final class Server
+{
+ // the following error codes are only used for SOCKS5 only
+ /** @internal */
+ const ERROR_GENERAL = 0x01;
+ /** @internal */
+ const ERROR_NOT_ALLOWED_BY_RULESET = 0x02;
+ /** @internal */
+ const ERROR_NETWORK_UNREACHABLE = 0x03;
+ /** @internal */
+ const ERROR_HOST_UNREACHABLE = 0x04;
+ /** @internal */
+ const ERROR_CONNECTION_REFUSED = 0x05;
+ /** @internal */
+ const ERROR_TTL = 0x06;
+ /** @internal */
+ const ERROR_COMMAND_UNSUPPORTED = 0x07;
+ /** @internal */
+ const ERROR_ADDRESS_UNSUPPORTED = 0x08;
+
+ /** @var LoopInterface */
+ private $loop;
+
+ /** @var ConnectorInterface */
+ private $connector;
+
+ /**
+ * @var null|callable
+ */
+ private $auth;
+
+ /**
+ *
+ * This class takes an optional `LoopInterface|null $loop` parameter that can be used to
+ * pass the event loop instance to use for this object. You can use a `null` value
+ * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
+ * This value SHOULD NOT be given unless you're sure you want to explicitly use a
+ * given event loop instance.
+ *
+ * @param ?LoopInterface $loop
+ * @param ?ConnectorInterface $connector
+ * @param null|array|callable $auth
+ */
+ public function __construct(
+ LoopInterface $loop = null,
+ ConnectorInterface $connector = null,
+ #[\SensitiveParameter]
+ $auth = null
+ ) {
+ if (\is_array($auth)) {
+ // wrap authentication array in authentication callback
+ $this->auth = function (
+ $username,
+ #[\SensitiveParameter]
+ $password
+ ) use ($auth) {
+ return \React\Promise\resolve(
+ isset($auth[$username]) && (string)$auth[$username] === $password
+ );
+ };
+ } elseif (\is_callable($auth)) {
+ // wrap authentication callback in order to cast its return value to a promise
+ $this->auth = function(
+ $username,
+ #[\SensitiveParameter]
+ $password,
+ #[\SensitiveParameter]
+ $remote
+ ) use ($auth) {
+ return \React\Promise\resolve(
+ \call_user_func($auth, $username, $password, $remote)
+ );
+ };
+ } elseif ($auth !== null) {
+ throw new \InvalidArgumentException('Invalid authenticator given');
+ }
+
+ $this->loop = $loop ?: Loop::get();
+ $this->connector = $connector ?: new Connector(array(), $this->loop);
+ }
+
+ /**
+ * @param ServerInterface $socket
+ * @return void
+ */
+ public function listen(ServerInterface $socket)
+ {
+ $that = $this;
+ $socket->on('connection', function ($connection) use ($that) {
+ $that->onConnection($connection);
+ });
+ }
+
+ /** @internal */
+ public function onConnection(ConnectionInterface $connection)
+ {
+ $that = $this;
+ $handling = $this->handleSocks($connection)->then(null, function () use ($connection, $that) {
+ // SOCKS failed => close connection
+ $that->endConnection($connection);
+ });
+
+ $connection->on('close', function () use ($handling) {
+ $handling->cancel();
+ });
+ }
+
+ /**
+ * [internal] gracefully shutdown connection by flushing all remaining data and closing stream
+ *
+ * @internal
+ */
+ public function endConnection(ConnectionInterface $stream)
+ {
+ $tid = true;
+ $loop = $this->loop;
+
+ // cancel below timer in case connection is closed in time
+ $stream->once('close', function () use (&$tid, $loop) {
+ // close event called before the timer was set up, so everything is okay
+ if ($tid === true) {
+ // make sure to not start a useless timer
+ $tid = false;
+ } else {
+ $loop->cancelTimer($tid);
+ }
+ });
+
+ // shut down connection by pausing input data, flushing outgoing buffer and then exit
+ $stream->pause();
+ $stream->end();
+
+ // check if connection is not already closed
+ if ($tid === true) {
+ // fall back to forcefully close connection in 3 seconds if buffer can not be flushed
+ $tid = $loop->addTimer(3.0, array($stream,'close'));
+ }
+ }
+
+ private function handleSocks(ConnectionInterface $stream)
+ {
+ $reader = new StreamReader();
+ $stream->on('data', array($reader, 'write'));
+
+ $that = $this;
+ $auth = $this->auth;
+
+ return $reader->readByte()->then(function ($version) use ($stream, $that, $auth, $reader){
+ if ($version === 0x04) {
+ if ($auth !== null) {
+ throw new UnexpectedValueException('SOCKS4 not allowed because authentication is required');
+ }
+ return $that->handleSocks4($stream, $reader);
+ } else if ($version === 0x05) {
+ return $that->handleSocks5($stream, $auth, $reader);
+ }
+ throw new UnexpectedValueException('Unexpected/unknown version number');
+ });
+ }
+
+ /** @internal */
+ public function handleSocks4(ConnectionInterface $stream, StreamReader $reader)
+ {
+ $remote = $stream->getRemoteAddress();
+ if ($remote !== null) {
+ // remove transport scheme and prefix socks4:// instead
+ $secure = strpos($remote, 'tls://') === 0;
+ if (($pos = strpos($remote, '://')) !== false) {
+ $remote = substr($remote, $pos + 3);
+ }
+ $remote = 'socks4' . ($secure ? 's' : '') . '://' . $remote;
+ }
+
+ $that = $this;
+ return $reader->readByteAssert(0x01)->then(function () use ($reader) {
+ return $reader->readBinary(array(
+ 'port' => 'n',
+ 'ipLong' => 'N',
+ 'null' => 'C'
+ ));
+ })->then(function ($data) use ($reader, $remote) {
+ if ($data['null'] !== 0x00) {
+ throw new Exception('Not a null byte');
+ }
+ if ($data['ipLong'] === 0) {
+ throw new Exception('Invalid IP');
+ }
+ if ($data['port'] === 0) {
+ throw new Exception('Invalid port');
+ }
+ if ($data['ipLong'] < 256) {
+ // invalid IP => probably a SOCKS4a request which appends the hostname
+ return $reader->readStringNull()->then(function ($string) use ($data, $remote){
+ return array($string, $data['port'], $remote);
+ });
+ } else {
+ $ip = long2ip($data['ipLong']);
+ return array($ip, $data['port'], $remote);
+ }
+ })->then(function ($target) use ($stream, $that) {
+ return $that->connectTarget($stream, $target)->then(function (ConnectionInterface $remote) use ($stream){
+ $stream->write(pack('C8', 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+
+ return $remote;
+ }, function($error) use ($stream){
+ $stream->end(pack('C8', 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+
+ throw $error;
+ });
+ }, function($error) {
+ throw new UnexpectedValueException('SOCKS4 protocol error',0,$error);
+ });
+ }
+
+ /** @internal */
+ public function handleSocks5(ConnectionInterface $stream, $auth, StreamReader $reader)
+ {
+ $remote = $stream->getRemoteAddress();
+ if ($remote !== null) {
+ // remove transport scheme and prefix socks5:// instead
+ $secure = strpos($remote, 'tls://') === 0;
+ if (($pos = strpos($remote, '://')) !== false) {
+ $remote = substr($remote, $pos + 3);
+ }
+ $remote = 'socks' . ($secure ? 's' : '') . '://' . $remote;
+ }
+
+ $that = $this;
+ return $reader->readByte()->then(function ($num) use ($reader) {
+ // $num different authentication mechanisms offered
+ return $reader->readLength($num);
+ })->then(function ($methods) use ($reader, $stream, $auth, &$remote) {
+ if ($auth === null && strpos($methods,"\x00") !== false) {
+ // accept "no authentication"
+ $stream->write(pack('C2', 0x05, 0x00));
+
+ return 0x00;
+ } else if ($auth !== null && strpos($methods,"\x02") !== false) {
+ // username/password authentication (RFC 1929) sub negotiation
+ $stream->write(pack('C2', 0x05, 0x02));
+ return $reader->readByteAssert(0x01)->then(function () use ($reader) {
+ return $reader->readByte();
+ })->then(function ($length) use ($reader) {
+ return $reader->readLength($length);
+ })->then(function ($username) use ($reader, $auth, $stream, &$remote) {
+ return $reader->readByte()->then(function ($length) use ($reader) {
+ return $reader->readLength($length);
+ })->then(function (
+ #[\SensitiveParameter]
+ $password
+ ) use ($username, $auth, $stream, &$remote) {
+ // username and password given => authenticate
+
+ // prefix username/password to remote URI
+ if ($remote !== null) {
+ $remote = str_replace('://', '://' . rawurlencode($username) . ':' . rawurlencode($password) . '@', $remote);
+ }
+
+ return $auth($username, $password, $remote)->then(function ($authenticated) use ($stream) {
+ if ($authenticated) {
+ // accept auth
+ $stream->write(pack('C2', 0x01, 0x00));
+ } else {
+ // reject auth => send any code but 0x00
+ $stream->end(pack('C2', 0x01, 0xFF));
+ throw new UnexpectedValueException('Authentication denied');
+ }
+ }, function ($e) use ($stream) {
+ // reject failed authentication => send any code but 0x00
+ $stream->end(pack('C2', 0x01, 0xFF));
+ throw new UnexpectedValueException('Authentication error', 0, $e);
+ });
+ });
+ });
+ } else {
+ // reject all offered authentication methods
+ $stream->write(pack('C2', 0x05, 0xFF));
+ throw new UnexpectedValueException('No acceptable authentication mechanism found');
+ }
+ })->then(function ($method) use ($reader) {
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'command' => 'C',
+ 'null' => 'C',
+ 'type' => 'C'
+ ));
+ })->then(function ($data) use ($reader) {
+ if ($data['version'] !== 0x05) {
+ throw new UnexpectedValueException('Invalid SOCKS version');
+ }
+ if ($data['command'] !== 0x01) {
+ throw new UnexpectedValueException('Only CONNECT requests supported', Server::ERROR_COMMAND_UNSUPPORTED);
+ }
+// if ($data['null'] !== 0x00) {
+// throw new UnexpectedValueException('Reserved byte has to be NULL');
+// }
+ if ($data['type'] === 0x03) {
+ // target hostname string
+ return $reader->readByte()->then(function ($len) use ($reader) {
+ return $reader->readLength($len);
+ });
+ } else if ($data['type'] === 0x01) {
+ // target IPv4
+ return $reader->readLength(4)->then(function ($addr) {
+ return inet_ntop($addr);
+ });
+ } else if ($data['type'] === 0x04) {
+ // target IPv6
+ return $reader->readLength(16)->then(function ($addr) {
+ return inet_ntop($addr);
+ });
+ } else {
+ throw new UnexpectedValueException('Invalid address type', Server::ERROR_ADDRESS_UNSUPPORTED);
+ }
+ })->then(function ($host) use ($reader, &$remote) {
+ return $reader->readBinary(array('port'=>'n'))->then(function ($data) use ($host, &$remote) {
+ return array($host, $data['port'], $remote);
+ });
+ })->then(function ($target) use ($that, $stream) {
+ return $that->connectTarget($stream, $target);
+ }, function($error) use ($stream) {
+ throw new UnexpectedValueException('SOCKS5 protocol error', $error->getCode(), $error);
+ })->then(function (ConnectionInterface $remote) use ($stream) {
+ $stream->write(pack('C4Nn', 0x05, 0x00, 0x00, 0x01, 0, 0));
+
+ return $remote;
+ }, function(Exception $error) use ($stream){
+ $stream->write(pack('C4Nn', 0x05, $error->getCode() === 0 ? Server::ERROR_GENERAL : $error->getCode(), 0x00, 0x01, 0, 0));
+
+ throw $error;
+ });
+ }
+
+ /** @internal */
+ public function connectTarget(ConnectionInterface $stream, array $target)
+ {
+ $uri = $target[0];
+ if (strpos($uri, ':') !== false) {
+ $uri = '[' . $uri . ']';
+ }
+ $uri .= ':' . $target[1];
+
+ // validate URI so a string hostname can not pass excessive URI parts
+ $parts = parse_url('tcp://' . $uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || count($parts) !== 3) {
+ return \React\Promise\reject(new InvalidArgumentException('Invalid target URI given'));
+ }
+
+ if (isset($target[2])) {
+ $uri .= '?source=' . rawurlencode($target[2]);
+ }
+
+ $that = $this;
+ $connecting = $this->connector->connect($uri);
+
+ $stream->on('close', function () use ($connecting) {
+ $connecting->cancel();
+ });
+
+ return $connecting->then(function (ConnectionInterface $remote) use ($stream, $that) {
+ $stream->pipe($remote, array('end'=>false));
+ $remote->pipe($stream, array('end'=>false));
+
+ // remote end closes connection => stop reading from local end, try to flush buffer to local and disconnect local
+ $remote->on('end', function() use ($stream, $that) {
+ $that->endConnection($stream);
+ });
+
+ // local end closes connection => stop reading from remote end, try to flush buffer to remote and disconnect remote
+ $stream->on('end', function() use ($remote, $that) {
+ $that->endConnection($remote);
+ });
+
+ // set bigger buffer size of 100k to improve performance
+ $stream->bufferSize = $remote->bufferSize = 100 * 1024 * 1024;
+
+ return $remote;
+ }, function(Exception $error) {
+ // default to general/unknown error
+ $code = Server::ERROR_GENERAL;
+
+ // map common socket error conditions to limited list of SOCKS error codes
+ if ((defined('SOCKET_EACCES') && $error->getCode() === SOCKET_EACCES) || $error->getCode() === 13) {
+ $code = Server::ERROR_NOT_ALLOWED_BY_RULESET;
+ } elseif ((defined('SOCKET_EHOSTUNREACH') && $error->getCode() === SOCKET_EHOSTUNREACH) || $error->getCode() === 113) {
+ $code = Server::ERROR_HOST_UNREACHABLE;
+ } elseif ((defined('SOCKET_ENETUNREACH') && $error->getCode() === SOCKET_ENETUNREACH) || $error->getCode() === 101) {
+ $code = Server::ERROR_NETWORK_UNREACHABLE;
+ } elseif ((defined('SOCKET_ECONNREFUSED') && $error->getCode() === SOCKET_ECONNREFUSED) || $error->getCode() === 111 || $error->getMessage() === 'Connection refused') {
+ // Socket component does not currently assign an error code for this, so we have to resort to checking the exception message
+ $code = Server::ERROR_CONNECTION_REFUSED;
+ } elseif ((defined('SOCKET_ETIMEDOUT') && $error->getCode() === SOCKET_ETIMEDOUT) || $error->getCode() === 110 || $error instanceof TimeoutException) {
+ // Socket component does not currently assign an error code for this, but we can rely on the TimeoutException
+ $code = Server::ERROR_TTL;
+ }
+
+ throw new UnexpectedValueException('Unable to connect to remote target', $code, $error);
+ });
+ }
+}
diff --git a/vendor/clue/socks-react/src/StreamReader.php b/vendor/clue/socks-react/src/StreamReader.php
new file mode 100644
index 0000000..f01d252
--- /dev/null
+++ b/vendor/clue/socks-react/src/StreamReader.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace Clue\React\Socks;
+
+use React\Promise\Deferred;
+use \InvalidArgumentException;
+use \UnexpectedValueException;
+
+/**
+ * @internal
+ */
+final class StreamReader
+{
+ const RET_DONE = true;
+ const RET_INCOMPLETE = null;
+
+ private $buffer = '';
+ private $queue = array();
+
+ public function write($data)
+ {
+ $this->buffer .= $data;
+
+ do {
+ $current = reset($this->queue);
+
+ if ($current === false) {
+ break;
+ }
+
+ /* @var $current Closure */
+
+ $ret = $current($this->buffer);
+
+ if ($ret === self::RET_INCOMPLETE) {
+ // current is incomplete, so wait for further data to arrive
+ break;
+ } else {
+ // current is done, remove from list and continue with next
+ array_shift($this->queue);
+ }
+ } while (true);
+ }
+
+ public function readBinary($structure)
+ {
+ $length = 0;
+ $unpack = '';
+ foreach ($structure as $name=>$format) {
+ if ($length !== 0) {
+ $unpack .= '/';
+ }
+ $unpack .= $format . $name;
+
+ if ($format === 'C') {
+ ++$length;
+ } else if ($format === 'n') {
+ $length += 2;
+ } else if ($format === 'N') {
+ $length += 4;
+ } else {
+ throw new InvalidArgumentException('Invalid format given');
+ }
+ }
+
+ return $this->readLength($length)->then(function ($response) use ($unpack) {
+ return unpack($unpack, $response);
+ });
+ }
+
+ public function readLength($bytes)
+ {
+ $deferred = new Deferred();
+
+ $this->readBufferCallback(function (&$buffer) use ($bytes, $deferred) {
+ if (strlen($buffer) >= $bytes) {
+ $deferred->resolve((string)substr($buffer, 0, $bytes));
+ $buffer = (string)substr($buffer, $bytes);
+
+ return StreamReader::RET_DONE;
+ }
+ });
+
+ return $deferred->promise();
+ }
+
+ public function readByte()
+ {
+ return $this->readBinary(array(
+ 'byte' => 'C'
+ ))->then(function ($data) {
+ return $data['byte'];
+ });
+ }
+
+ public function readByteAssert($expect)
+ {
+ return $this->readByte()->then(function ($byte) use ($expect) {
+ if ($byte !== $expect) {
+ throw new UnexpectedValueException('Unexpected byte encountered');
+ }
+ return $byte;
+ });
+ }
+
+ public function readStringNull()
+ {
+ $deferred = new Deferred();
+ $string = '';
+
+ $that = $this;
+ $readOne = function () use (&$readOne, $that, $deferred, &$string) {
+ $that->readByte()->then(function ($byte) use ($deferred, &$string, $readOne) {
+ if ($byte === 0x00) {
+ $deferred->resolve($string);
+ } else {
+ $string .= chr($byte);
+ $readOne();
+ }
+ });
+ };
+ $readOne();
+
+ return $deferred->promise();
+ }
+
+ public function readBufferCallback(/* callable */ $callable)
+ {
+ if (!is_callable($callable)) {
+ throw new InvalidArgumentException('Given function must be callable');
+ }
+
+ if ($this->queue) {
+ $this->queue []= $callable;
+ } else {
+ $this->queue = array($callable);
+
+ if ($this->buffer !== '') {
+ // this is the first element in the queue and the buffer is filled => trigger write procedure
+ $this->write('');
+ }
+ }
+ }
+
+ public function getBuffer()
+ {
+ return $this->buffer;
+ }
+}