diff options
Diffstat (limited to 'vendor/clue/http-proxy-react')
-rw-r--r-- | vendor/clue/http-proxy-react/CHANGELOG.md | 200 | ||||
-rw-r--r-- | vendor/clue/http-proxy-react/LICENSE | 21 | ||||
-rw-r--r-- | vendor/clue/http-proxy-react/README.md | 510 | ||||
-rw-r--r-- | vendor/clue/http-proxy-react/composer.json | 31 | ||||
-rw-r--r-- | vendor/clue/http-proxy-react/src/ProxyConnector.php | 278 |
5 files changed, 1040 insertions, 0 deletions
diff --git a/vendor/clue/http-proxy-react/CHANGELOG.md b/vendor/clue/http-proxy-react/CHANGELOG.md new file mode 100644 index 0000000..19d534d --- /dev/null +++ b/vendor/clue/http-proxy-react/CHANGELOG.md @@ -0,0 +1,200 @@ +# Changelog + +## 1.8.0 (2022-09-01) + +* Feature: Full support for PHP 8.1 and PHP 8.2. + (#47 and #48 by @SimonFrings) + +* Feature: Mark passwords and URIs as `#[\SensitiveParameter]` (PHP 8.2+). + (#49 by @SimonFrings) + +* Feature: Forward compatibility with upcoming Promise v3. + (#44 by @clue) + +* Fix: Fix invalid references in exception stack trace. + (#45 by @clue) + +* Improve test suite and fix legacy HHVM build. + (#46 by @SimonFrings) + +## 1.7.0 (2021-08-06) + +* Feature: Simplify usage by supporting new default loop and making `Connector` optional. + (#41 and #42 by @clue) + + ```php + // old (still supported) + $proxy = new Clue\React\HttpProxy\ProxyConnector( + '127.0.0.1:8080', + new React\Socket\Connector($loop) + ); + + // new (using default loop) + $proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); + ``` + +* Documentation improvements and updated examples. + (#39 and #43 by @clue and #40 by @PaulRotmann) + +* Improve test suite and use GitHub actions for continuous integration (CI). + (#38 by @SimonFrings) + +## 1.6.0 (2020-10-23) + +* Enhanced documentation for ReactPHP's new HTTP client. + (#35 and #37 by @SimonFrings) + +* Improve test suite, prepare PHP 8 support and support PHPUnit 9.3. + (#36 by @SimonFrings) + +## 1.5.0 (2020-06-19) + +* Feature / Fix: Support PHP 7.4 by skipping unneeded cleanup of exception trace args. + (#33 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. + (#27 by @clue and #31, #32 and #34 by @SimonFrings) + +## 1.4.0 (2018-10-30) + +* Feature: Improve error reporting for failed connection attempts and improve + cancellation forwarding during proxy connection setup. + (#23 and #26 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(); + }); + ``` + +* Feature: Add support for custom HTTP request headers. + (#25 by @valga and @clue) + + ```php + // new: now supports custom HTTP request headers + $proxy = new ProxyConnector('127.0.0.1:8080', $connector, array( + 'Proxy-Authorization' => 'Bearer abc123', + 'User-Agent' => 'ReactPHP' + )); + ``` + +* Fix: Fix connecting to IPv6 destination hosts. + (#22 by @clue) + +* Link to clue/reactphp-buzz for HTTP requests and update project homepage. + (#21 and #24 by @clue) + +## 1.3.0 (2018-02-13) + +* Feature: Support communication over Unix domain sockets (UDS) + (#20 by @clue) + + ```php + // new: now supports communication over Unix domain sockets (UDS) + $proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector); + ``` + +* Reduce memory consumption by avoiding circular reference from stream reader + (#18 by @valga) + +* Improve documentation + (#19 by @clue) + +## 1.2.0 (2017-08-30) + +* Feature: Use socket error codes for connection rejections + (#17 by @clue) + + ```php + $promise = $proxy->connect('imap.example.com:143'); + $promise->then(null, function (Exeption $e) { + if ($e->getCode() === SOCKET_EACCES) { + echo 'Failed to authenticate with proxy!'; + } + throw $e; + }); + ``` + +* Improve test suite by locking Travis distro so new defaults will not break the build and + optionally exclude tests that rely on working internet connection + (#15 and #16 by @clue) + +## 1.1.0 (2017-06-11) + +* Feature: Support proxy authentication if proxy URL contains username/password + (#14 by @clue) + + ```php + // new: username/password will now be passed to HTTP proxy server + $proxy = new ProxyConnector('user:pass@127.0.0.1:8080', $connector); + ``` + +## 1.0.0 (2017-06-10) + +* First stable release, now following SemVer + +> Contains no other changes, so it's actually fully compatible with the v0.3.2 release. + +## 0.3.2 (2017-06-10) + +* Fix: Fix rejecting invalid URIs and unexpected URI schemes + (#13 by @clue) + +* Fix HHVM build for now again and ignore future HHVM build errors + (#12 by @clue) + +* Documentation for Connector concepts (TCP/TLS, timeouts, DNS resolution) + (#11 by @clue) + +## 0.3.1 (2017-05-10) + +* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8 + (#10 by @clue) + +## 0.3.0 (2017-04-10) + +* Feature / BC break: Replace deprecated SocketClient with new Socket component + (#9 by @clue) + + This implies that the `ProxyConnector` from this package now implements the + `React\Socket\ConnectorInterface` instead of the legacy + `React\SocketClient\ConnectorInterface`. + +## 0.2.0 (2017-04-10) + +* Feature / BC break: Update SocketClient to v0.7 or v0.6 and + use `connect($uri)` instead of `create($host, $port)` + (#8 by @clue) + + ```php + // old + $connector->create($host, $port)->then(function (Stream $conn) { + $conn->write("…"); + }); + + // new + $connector->connect($uri)->then(function (ConnectionInterface $conn) { + $conn->write("…"); + }); + ``` + +* Improve test suite by adding PHPUnit to require-dev + (#7 by @clue) + + +## 0.1.0 (2016-11-01) + +* First tagged release diff --git a/vendor/clue/http-proxy-react/LICENSE b/vendor/clue/http-proxy-react/LICENSE new file mode 100644 index 0000000..7baae8e --- /dev/null +++ b/vendor/clue/http-proxy-react/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Christian Lück + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/clue/http-proxy-react/README.md b/vendor/clue/http-proxy-react/README.md new file mode 100644 index 0000000..b5337c1 --- /dev/null +++ b/vendor/clue/http-proxy-react/README.md @@ -0,0 +1,510 @@ +# clue/reactphp-http-proxy + +[](https://github.com/clue/reactphp-http-proxy/actions) +[](https://packagist.org/packages/clue/http-proxy-react) + +Async HTTP proxy connector, tunnel any TCP/IP-based protocol through an HTTP +CONNECT proxy server, built on top of [ReactPHP](https://reactphp.org/). + +HTTP CONNECT proxy servers (also commonly known as "HTTPS proxy" or "SSL proxy") +are commonly used to tunnel HTTPS traffic through an intermediary ("proxy"), to +conceal the origin address (anonymity) or to circumvent address blocking +(geoblocking). While many (public) HTTP CONNECT proxy servers often limit this +to HTTPS port `443` only, this can technically be used to tunnel any +TCP/IP-based protocol (HTTP, SMTP, IMAP etc.). +This library provides a simple API to create these tunneled 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 HTTP CONNECT proxy support to pretty much any +existing higher-level protocol implementation. + +* **Async execution of connections** - + Send any number of HTTP CONNECT requests in parallel and process their + responses as soon as results come in. + The Promise-based design provides a *sane* interface to working with out of + 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) + * [ProxyConnector](#proxyconnector) + * [Plain TCP connections](#plain-tcp-connections) + * [Secure TLS connections](#secure-tls-connections) + * [HTTP requests](#http-requests) + * [Connection timeout](#connection-timeout) + * [DNS resolution](#dns-resolution) + * [Authentication](#authentication) + * [Advanced HTTP headers](#advanced-http-headers) + * [Advanced secure proxy connections](#advanced-secure-proxy-connections) + * [Advanced Unix domain sockets](#advanced-unix-domain-sockets) +* [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 + +The following example code demonstrates how this library can be used to send a +secure HTTPS request to google.com through a local HTTP proxy server: + +```php +<?php + +require __DIR__ . '/vendor/autoload.php'; + +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); + +$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; +}); +``` + +See also the [examples](examples). + +## Usage + +### ProxyConnector + +The `ProxyConnector` is responsible for creating plain TCP/IP connections to +any destination by using an intermediary HTTP CONNECT proxy. + +``` +[you] -> [proxy] -> [destination] +``` + +Its constructor simply accepts an HTTP proxy URL with the proxy server address: + +```php +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); +``` + +The proxy URL may or may not contain a scheme and port definition. The default +port will be `80` for HTTP (or `443` for HTTPS), but many common HTTP proxy +servers use custom ports (often the alternative HTTP port `8080`). + +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' + ), + 'tls' => array( + 'verify_peer' => false, + 'verify_peer_name' => false + ) +)); + +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080', $connector); +``` + +This is the main class in this package. +Because it implements ReactPHP's standard +[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface), +it can simply be used in place of a normal connector. +Accordingly, it provides only a single public method, the +[`connect()`](https://github.com/reactphp/socket#connect) method. +The `connect(string $uri): PromiseInterface<ConnectionInterface, Exception>` +method can be used to establish a streaming connection. +It returns a [Promise](https://github.com/reactphp/promise) which either +fulfills with a [ConnectionInterface](https://github.com/reactphp/socket#connectioninterface) +on success or rejects with an `Exception` on error. + +This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any +higher-level component: + +```diff +- $acme = new AcmeApi($connector); ++ $proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080', $connector); ++ $acme = new AcmeApi($proxy); +``` + +#### Plain TCP connections + +HTTP CONNECT proxies are most frequently used to issue HTTPS requests to your destination. +However, this is actually performed on a higher protocol layer and this +connector is actually inherently a general-purpose plain TCP/IP connector. +As documented above, you can simply invoke its `connect()` method to establish +a streaming plain TCP/IP connection and use any higher level protocol like so: + +```php +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); + +$proxy->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write("EHLO local\r\n"); + $connection->on('data', function ($chunk) use ($connection) { + echo $chunk; + }); +}); +``` + +You can either use the `ProxyConnector` directly or you may want to wrap this connector +in ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector): + +```php +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); + +$connector = new React\Socket\Connector(array( + 'tcp' => $proxy, + 'dns' => false +)); + +$connector->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write("EHLO local\r\n"); + $connection->on('data', function ($chunk) use ($connection) { + echo $chunk; + }); +}); +``` + +Note that HTTP CONNECT proxies often restrict which ports one may connect to. +Many (public) proxy servers do in fact limit this to HTTPS (443) only. + +#### Secure TLS connections + +This class can also be used if you want to establish a secure TLS connection +(formerly known as SSL) between you and your destination, such as when using +secure HTTPS to your destination site. You can simply wrap this connector in +ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector): + +```php +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); + +$connector = new React\Socket\Connector(array( + 'tcp' => $proxy, + 'dns' => false +)); + +$connector->connect('tls://smtp.googlemail.com:465')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write("EHLO local\r\n"); + $connection->on('data', function ($chunk) use ($connection) { + echo $chunk; + }); +}); +``` + +> Note how secure TLS connections are in fact entirely handled outside of + this HTTP CONNECT client implementation. + +#### HTTP requests + +This library also allows you to send HTTP requests through an HTTP CONNECT proxy server. + +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\HttpProxy\ProxyConnector('127.0.0.1:8080'); + +$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. + +#### Connection timeout + +By default, the `ProxyConnector` does not implement any timeouts for establishing remote +connections. +Your underlying operating system may impose limits on pending and/or idle TCP/IP +connections, anywhere in a range of a few minutes to several hours. + +Many use cases require more control over the timeout and likely values much +smaller, usually in the range of a few seconds only. + +You can use ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector) +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\HttpProxy\ProxyConnector('127.0.0.1:8080'); + +$connector = new React\Socket\Connector(array( + 'tcp' => $proxy, + 'dns' => false, + 'timeout' => 3.0 +)); + +$connector->connect('tcp://google.com:80')->then(function ($connection) { + // connection succeeded within 3.0 seconds +}); +``` + +See also any of the [examples](examples). + +> Note how the connection timeout is in fact entirely handled outside of this + HTTP CONNECT client implementation. + +#### DNS resolution + +By default, the `ProxyConnector` does not perform any DNS resolution at all and simply +forwards any hostname you're trying to connect to the remote proxy server. +The remote proxy server is thus responsible for looking up any hostnames via DNS +(this default mode is thus called *remote DNS resolution*). + +As an alternative, you can also send the destination IP to the remote proxy +server. +In this mode you either have to stick to using IPs only (which is ofen unfeasable) +or perform any DNS lookups locally and only transmit the resolved destination IPs +(this mode is thus called *local DNS resolution*). + +The default *remote DNS resolution* is useful if your local `ProxyConnector` either can +not resolve target hostnames because it has no direct access to the internet or +if it should not resolve target hostnames because its outgoing DNS traffic might +be intercepted. + +As noted above, the `ProxyConnector` defaults to using remote DNS resolution. +However, wrapping the `ProxyConnector` in ReactPHP's +[`Connector`](https://github.com/reactphp/socket#connector) actually +performs local DNS resolution unless explicitly defined otherwise. +Given that remote DNS resolution is assumed to be the preferred mode, all +other examples explicitly disable DNS resolution like this: + +```php +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); + +$connector = new React\Socket\Connector(array( + 'tcp' => $proxy, + 'dns' => false +)); +``` + +If you want to explicitly use *local DNS resolution*, you can use the following code: + +```php +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); + +// 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' +)); +``` + +> Note how local DNS resolution is in fact entirely handled outside of this + HTTP CONNECT client implementation. + +#### Authentication + +If your HTTP proxy server requires authentication, you may pass the username and +password as part of the HTTP proxy URL like this: + +```php +$proxy = new Clue\React\HttpProxy\ProxyConnector('alice:password@127.0.0.1:8080'); +``` + +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:8080'; + +$proxy = new Clue\React\HttpProxy\ProxyConnector($url); +``` + +> The authentication details will be used for basic authentication and will be + transferred in the `Proxy-Authorization` HTTP request header for each + connection attempt. + If the authentication details are missing or not accepted by the remote HTTP + proxy server, it is expected to reject each connection attempt with a + `407` (Proxy Authentication Required) response status code and an exception + error code of `SOCKET_EACCES` (13). + +#### Advanced HTTP headers + +The `ProxyConnector` constructor accepts an optional array of custom request +headers to send in the `CONNECT` request. This can be useful if you're using a +custom proxy setup or authentication scheme if the proxy server does not support +basic [authentication](#authentication) as documented above. This is rarely used +in practice, but may be useful for some more advanced use cases. In this case, +you may simply pass an assoc array of additional request headers like this: + +```php +$proxy = new Clue\React\HttpProxy\ProxyConnector( + '127.0.0.1:8080', + null, + array( + 'Proxy-Authorization' => 'Bearer abc123', + 'User-Agent' => 'ReactPHP' + ) +); +``` + +#### Advanced secure proxy connections + +Note that communication between the client and the proxy is usually via an +unencrypted, plain TCP/IP HTTP connection. Note that this is the most common +setup, because you can still establish a TLS connection between you and the +destination host as above. + +If you want to connect to a (rather rare) HTTPS proxy, you may want use the +`https://` scheme (HTTPS default port 443) to create a secure connection to the proxy: + +```php +$proxy = new Clue\React\HttpProxy\ProxyConnector('https://127.0.0.1:443'); + +$proxy->connect('tcp://smtp.googlemail.com:587'); +``` + +#### Advanced Unix domain sockets + +HTTP CONNECT proxy servers support forwarding TCP/IP based connections and +higher level protocols. +In some advanced cases, it may be useful to let your HTTP CONNECT proxy server +listen on a Unix domain socket (UDS) path instead of a IP:port combination. +For example, this allows you to rely on file system permissions instead of +having to rely on explicit [authentication](#authentication). + +You can simply use the `http+unix://` URI scheme like this: + +```php +$proxy = new Clue\React\HttpProxy\ProxyConnector('http+unix:///tmp/proxy.sock'); + +$proxy->connect('tcp://google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { + // connected… +}); +``` + +Similarly, you can also combine this with [authentication](#authentication) +like this: + +```php +$proxy = new Clue\React\HttpProxy\ProxyConnector('http+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 HTTP CONNECT protocol does not support the notion of UDS paths. + The above works reasonably well because UDS is only used for the connection between + client and proxy server and the path will not actually passed over the protocol. + This implies that this does not support connecting to UDS destination paths. + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org/). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This project follows [SemVer](https://semver.org/). +This will install the latest supported version: + +```bash +composer require clue/http-proxy-react:^1.8 +``` + +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 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](https://github.com/reactphp/socket) component. +* If you want to learn more about processing streams of data, refer to the + documentation of the underlying + [react/stream](https://github.com/reactphp/stream) component. +* As an alternative to an HTTP CONNECT proxy, you may also want to look into + using a SOCKS (SOCKS4/SOCKS5) proxy instead. + You may want to use [clue/reactphp-socks](https://github.com/clue/reactphp-socks) + 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 an HTTP CONNECT 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 HTTP CONNECT proxy server daemon, you may + want to use [LeProxy](https://leproxy.org/). diff --git a/vendor/clue/http-proxy-react/composer.json b/vendor/clue/http-proxy-react/composer.json new file mode 100644 index 0000000..4941b1a --- /dev/null +++ b/vendor/clue/http-proxy-react/composer.json @@ -0,0 +1,31 @@ +{ + "name": "clue/http-proxy-react", + "description": "Async HTTP proxy connector, tunnel any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP", + "keywords": ["HTTP", "CONNECT", "proxy", "ReactPHP", "async"], + "homepage": "https://github.com/clue/reactphp-http-proxy", + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "require": { + "php": ">=5.3", + "react/promise": "^3 || ^2.1 || ^1.2.1", + "react/socket": "^1.12", + "ringcentral/psr7": "^1.2" + }, + "require-dev": { + "clue/block-react": "^1.5", + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8", + "react/event-loop": "^1.2", + "react/http": "^1.5" + }, + "autoload": { + "psr-4": { "Clue\\React\\HttpProxy\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "Clue\\Tests\\React\\HttpProxy\\": "tests/" } + } +} diff --git a/vendor/clue/http-proxy-react/src/ProxyConnector.php b/vendor/clue/http-proxy-react/src/ProxyConnector.php new file mode 100644 index 0000000..165e8ba --- /dev/null +++ b/vendor/clue/http-proxy-react/src/ProxyConnector.php @@ -0,0 +1,278 @@ +<?php + +namespace Clue\React\HttpProxy; + +use Exception; +use InvalidArgumentException; +use RuntimeException; +use RingCentral\Psr7; +use React\Promise; +use React\Promise\Deferred; +use React\Socket\ConnectionInterface; +use React\Socket\Connector; +use React\Socket\ConnectorInterface; +use React\Socket\FixedUriConnector; +use React\Socket\UnixConnector; + +/** + * A simple Connector that uses an HTTP CONNECT proxy to create plain TCP/IP connections to any destination + * + * [you] -> [proxy] -> [destination] + * + * This is most frequently used to issue HTTPS requests to your destination. + * However, this is actually performed on a higher protocol layer and this + * connector is actually inherently a general-purpose plain TCP/IP connector. + * + * Note that HTTP CONNECT proxies often restrict which ports one may connect to. + * Many (public) proxy servers do in fact limit this to HTTPS (443) only. + * + * If you want to establish a TLS connection (such as HTTPS) between you and + * your destination, you may want to wrap this connector in a SecureConnector + * instance. + * + * Note that communication between the client and the proxy is usually via an + * unencrypted, plain TCP/IP HTTP connection. Note that this is the most common + * setup, because you can still establish a TLS connection between you and the + * destination host as above. + * + * If you want to connect to a (rather rare) HTTPS proxy, you may want use its + * HTTPS port (443) and use a SecureConnector instance to create a secure + * connection to the proxy. + * + * @link https://tools.ietf.org/html/rfc7231#section-4.3.6 + */ +class ProxyConnector implements ConnectorInterface +{ + private $connector; + private $proxyUri; + private $headers = ''; + + /** + * Instantiate a new ProxyConnector which uses the given $proxyUrl + * + * @param string $proxyUrl The proxy URL may or may not contain a scheme and + * port definition. The default port will be `80` for HTTP (or `443` for + * HTTPS), but many common HTTP proxy servers use custom ports. + * @param ?ConnectorInterface $connector (Optional) Connector to use. + * @param array $httpHeaders Custom HTTP headers to be sent to the proxy. + * @throws InvalidArgumentException if the proxy URL is invalid + */ + public function __construct( + #[\SensitiveParameter] + $proxyUrl, + ConnectorInterface $connector = null, + array $httpHeaders = array() + ) { + // support `http+unix://` scheme for Unix domain socket (UDS) paths + if (preg_match('/^http\+unix:\/\/(.*?@)?(.+?)$/', $proxyUrl, $match)) { + // rewrite URI to parse authentication from dummy host + $proxyUrl = 'http://' . $match[1] . 'localhost'; + + // connector uses Unix transport scheme and explicit path given + $connector = new FixedUriConnector( + 'unix://' . $match[2], + $connector ?: new UnixConnector() + ); + } + + if (strpos($proxyUrl, '://') === false) { + $proxyUrl = 'http://' . $proxyUrl; + } + + $parts = parse_url($proxyUrl); + if (!$parts || !isset($parts['scheme'], $parts['host']) || ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')) { + throw new InvalidArgumentException('Invalid proxy URL "' . $proxyUrl . '"'); + } + + // apply default port and TCP/TLS transport for given scheme + if (!isset($parts['port'])) { + $parts['port'] = $parts['scheme'] === 'https' ? 443 : 80; + } + $parts['scheme'] = $parts['scheme'] === 'https' ? 'tls' : 'tcp'; + + $this->connector = $connector ?: new Connector(); + $this->proxyUri = $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port']; + + // prepare Proxy-Authorization header if URI contains username/password + if (isset($parts['user']) || isset($parts['pass'])) { + $this->headers = 'Proxy-Authorization: Basic ' . base64_encode( + rawurldecode($parts['user'] . ':' . (isset($parts['pass']) ? $parts['pass'] : '')) + ) . "\r\n"; + } + + // append any additional custom request headers + foreach ($httpHeaders as $name => $values) { + foreach ((array)$values as $value) { + $this->headers .= $name . ': ' . $value . "\r\n"; + } + } + } + + public function connect($uri) + { + if (strpos($uri, '://') === false) { + $uri = 'tcp://' . $uri; + } + + $parts = parse_url($uri); + if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') { + return Promise\reject(new InvalidArgumentException('Invalid target URI specified')); + } + + $target = $parts['host'] . ':' . $parts['port']; + + // construct URI to HTTP CONNECT proxy server to connect to + $proxyUri = $this->proxyUri; + + // append path from URI if given + if (isset($parts['path'])) { + $proxyUri .= $parts['path']; + } + + // parse query args + $args = array(); + if (isset($parts['query'])) { + parse_str($parts['query'], $args); + } + + // append hostname from URI to query string unless explicitly given + if (!isset($args['hostname'])) { + $args['hostname'] = trim($parts['host'], '[]'); + } + + // append query string + $proxyUri .= '?' . http_build_query($args, '', '&'); + + // append fragment from URI if given + if (isset($parts['fragment'])) { + $proxyUri .= '#' . $parts['fragment']; + } + + $connecting = $this->connector->connect($proxyUri); + + $deferred = new Deferred(function ($_, $reject) use ($connecting, $uri) { + $reject(new RuntimeException( + 'Connection to ' . $uri . ' cancelled while waiting for proxy (ECONNABORTED)', + defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103 + )); + + // either close active connection or cancel pending connection attempt + $connecting->then(function (ConnectionInterface $stream) { + $stream->close(); + }); + $connecting->cancel(); + }); + + $headers = $this->headers; + $connecting->then(function (ConnectionInterface $stream) use ($target, $headers, $deferred, $uri) { + // keep buffering data until headers are complete + $buffer = ''; + $stream->on('data', $fn = function ($chunk) use (&$buffer, $deferred, $stream, &$fn, $uri) { + $buffer .= $chunk; + + $pos = strpos($buffer, "\r\n\r\n"); + if ($pos !== false) { + // end of headers received => stop buffering + $stream->removeListener('data', $fn); + $fn = null; + + // try to parse headers as response message + try { + $response = Psr7\parse_response(substr($buffer, 0, $pos)); + } catch (Exception $e) { + $deferred->reject(new RuntimeException( + 'Connection to ' . $uri . ' failed because proxy returned invalid response (EBADMSG)', + defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71, + $e + )); + $stream->close(); + return; + } + + if ($response->getStatusCode() === 407) { + // map status code 407 (Proxy Authentication Required) to EACCES + $deferred->reject(new RuntimeException( + 'Connection to ' . $uri . ' failed because proxy denied access with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (EACCES)', + defined('SOCKET_EACCES') ? SOCKET_EACCES : 13 + )); + $stream->close(); + return; + } elseif ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { + // map non-2xx status code to ECONNREFUSED + $deferred->reject(new RuntimeException( + 'Connection to ' . $uri . ' failed because proxy refused connection with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (ECONNREFUSED)', + defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111 + )); + $stream->close(); + return; + } + + // all okay, resolve with stream instance + $deferred->resolve($stream); + + // emit remaining incoming as data event + $buffer = (string)substr($buffer, $pos + 4); + if ($buffer !== '') { + $stream->emit('data', array($buffer)); + $buffer = ''; + } + return; + } + + // stop buffering when 8 KiB have been read + if (isset($buffer[8192])) { + $deferred->reject(new RuntimeException( + 'Connection to ' . $uri . ' failed because proxy response headers exceed maximum of 8 KiB (EMSGSIZE)', + defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90 + )); + $stream->close(); + } + }); + + $stream->on('error', function (Exception $e) use ($deferred, $uri) { + $deferred->reject(new RuntimeException( + 'Connection to ' . $uri . ' failed because connection to proxy caused a stream error (EIO)', + defined('SOCKET_EIO') ? SOCKET_EIO : 5, + $e + )); + }); + + $stream->on('close', function () use ($deferred, $uri) { + $deferred->reject(new RuntimeException( + 'Connection to ' . $uri . ' failed because connection to proxy was lost while waiting for response (ECONNRESET)', + defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104 + )); + }); + + $stream->write("CONNECT " . $target . " HTTP/1.1\r\nHost: " . $target . "\r\n" . $headers . "\r\n"); + }, function (Exception $e) use ($deferred, $uri) { + $deferred->reject($e = new RuntimeException( + 'Connection to ' . $uri . ' failed because connection to proxy failed (ECONNREFUSED)', + defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, + $e + )); + + // avoid garbage references by replacing all closures in call stack. + // what a lovely piece of code! + $r = new \ReflectionProperty('Exception', 'trace'); + $r->setAccessible(true); + $trace = $r->getValue($e); + + // Exception trace arguments are not available on some PHP 7.4 installs + // @codeCoverageIgnoreStart + foreach ($trace as $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(); + } +} |