init
This commit is contained in:
2
vendor/clue/http-proxy-react/.gitignore
vendored
Executable file
2
vendor/clue/http-proxy-react/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
/vendor/
|
||||
/composer.lock
|
||||
27
vendor/clue/http-proxy-react/.travis.yml
vendored
Executable file
27
vendor/clue/http-proxy-react/.travis.yml
vendored
Executable file
@@ -0,0 +1,27 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
# - 5.3 # requires old distro, see below
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7
|
||||
- hhvm # ignore errors, see below
|
||||
|
||||
# lock distro so new future defaults will not break the build
|
||||
dist: trusty
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.3
|
||||
dist: precise
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
|
||||
sudo: false
|
||||
|
||||
install:
|
||||
- composer install --no-interaction
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-text
|
||||
103
vendor/clue/http-proxy-react/CHANGELOG.md
vendored
Executable file
103
vendor/clue/http-proxy-react/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,103 @@
|
||||
# Changelog
|
||||
|
||||
## 1.3.0 (2018-02-13)
|
||||
|
||||
* Feature: Support communication over Unix domain sockets (UDS)
|
||||
(#20 by @clue)
|
||||
|
||||
```php
|
||||
// new: now supports communication over Unix domain sockets (UDS)
|
||||
$proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector);
|
||||
```
|
||||
|
||||
* Reduce memory consumption by avoiding circular reference from stream reader
|
||||
(#18 by @valga)
|
||||
|
||||
* Improve documentation
|
||||
(#19 by @clue)
|
||||
|
||||
## 1.2.0 (2017-08-30)
|
||||
|
||||
* Feature: Use socket error codes for connection rejections
|
||||
(#17 by @clue)
|
||||
|
||||
```php
|
||||
$promise = $proxy->connect('imap.example.com:143');
|
||||
$promise->then(null, function (Exeption $e) {
|
||||
if ($e->getCode() === SOCKET_EACCES) {
|
||||
echo 'Failed to authenticate with proxy!';
|
||||
}
|
||||
throw $e;
|
||||
});
|
||||
```
|
||||
|
||||
* Improve test suite by locking Travis distro so new defaults will not break the build and
|
||||
optionally exclude tests that rely on working internet connection
|
||||
(#15 and #16 by @clue)
|
||||
|
||||
## 1.1.0 (2017-06-11)
|
||||
|
||||
* Feature: Support proxy authentication if proxy URL contains username/password
|
||||
(#14 by @clue)
|
||||
|
||||
```php
|
||||
// new: username/password will now be passed to HTTP proxy server
|
||||
$proxy = new ProxyConnector('user:pass@127.0.0.1:8080', $connector);
|
||||
```
|
||||
|
||||
## 1.0.0 (2017-06-10)
|
||||
|
||||
* First stable release, now following SemVer
|
||||
|
||||
> Contains no other changes, so it's actually fully compatible with the v0.3.2 release.
|
||||
|
||||
## 0.3.2 (2017-06-10)
|
||||
|
||||
* Fix: Fix rejecting invalid URIs and unexpected URI schemes
|
||||
(#13 by @clue)
|
||||
|
||||
* Fix HHVM build for now again and ignore future HHVM build errors
|
||||
(#12 by @clue)
|
||||
|
||||
* Documentation for Connector concepts (TCP/TLS, timeouts, DNS resolution)
|
||||
(#11 by @clue)
|
||||
|
||||
## 0.3.1 (2017-05-10)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
|
||||
(#10 by @clue)
|
||||
|
||||
## 0.3.0 (2017-04-10)
|
||||
|
||||
* Feature / BC break: Replace deprecated SocketClient with new Socket component
|
||||
(#9 by @clue)
|
||||
|
||||
This implies that the `ProxyConnector` from this package now implements the
|
||||
`React\Socket\ConnectorInterface` instead of the legacy
|
||||
`React\SocketClient\ConnectorInterface`.
|
||||
|
||||
## 0.2.0 (2017-04-10)
|
||||
|
||||
* Feature / BC break: Update SocketClient to v0.7 or v0.6 and
|
||||
use `connect($uri)` instead of `create($host, $port)`
|
||||
(#8 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$connector->create($host, $port)->then(function (Stream $conn) {
|
||||
$conn->write("…");
|
||||
});
|
||||
|
||||
// new
|
||||
$connector->connect($uri)->then(function (ConnectionInterface $conn) {
|
||||
$conn->write("…");
|
||||
});
|
||||
```
|
||||
|
||||
* Improve test suite by adding PHPUnit to require-dev
|
||||
(#7 by @clue)
|
||||
|
||||
|
||||
## 0.1.0 (2016-11-01)
|
||||
|
||||
* First tagged release
|
||||
21
vendor/clue/http-proxy-react/LICENSE
vendored
Executable file
21
vendor/clue/http-proxy-react/LICENSE
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Christian Lück
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
422
vendor/clue/http-proxy-react/README.md
vendored
Executable file
422
vendor/clue/http-proxy-react/README.md
vendored
Executable file
@@ -0,0 +1,422 @@
|
||||
# clue/http-proxy-react [](https://travis-ci.org/clue/php-http-proxy-react)
|
||||
|
||||
Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP
|
||||
CONNECT proxy server, built on top of [ReactPHP](https://reactphp.org).
|
||||
|
||||
HTTP CONNECT proxy servers (also commonly known as "HTTPS proxy" or "SSL proxy")
|
||||
are commonly used to tunnel HTTPS traffic through an intermediary ("proxy"), to
|
||||
conceal the origin address (anonymity) or to circumvent address blocking
|
||||
(geoblocking). While many (public) HTTP CONNECT proxy servers often limit this
|
||||
to HTTPS port `443` only, this can technically be used to tunnel any
|
||||
TCP/IP-based protocol (HTTP, SMTP, IMAP etc.).
|
||||
This library provides a simple API to create these tunneled connection for you.
|
||||
Because it implements ReactPHP's standard
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
|
||||
it can simply be used in place of a normal connector.
|
||||
This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
|
||||
existing higher-level protocol implementation.
|
||||
|
||||
* **Async execution of connections** -
|
||||
Send any number of HTTP CONNECT requests in parallel and process their
|
||||
responses as soon as results come in.
|
||||
The Promise-based design provides a *sane* interface to working with out of
|
||||
bound responses and possible connection errors.
|
||||
* **Standard interfaces** -
|
||||
Allows easy integration with existing higher-level components by implementing
|
||||
ReactPHP's standard
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface).
|
||||
* **Lightweight, SOLID design** -
|
||||
Provides a thin abstraction that is [*just good enough*](http://en.wikipedia.org/wiki/Principle_of_good_enough)
|
||||
and does not get in your way.
|
||||
Builds on top of well-tested components and well-established concepts instead of reinventing the wheel.
|
||||
* **Good test coverage** -
|
||||
Comes with an automated tests suite and is regularly tested against actual proxy servers in the wild
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Quickstart example](#quickstart-example)
|
||||
* [Usage](#usage)
|
||||
* [ProxyConnector](#proxyconnector)
|
||||
* [Plain TCP connections](#plain-tcp-connections)
|
||||
* [Secure TLS connections](#secure-tls-connections)
|
||||
* [Connection timeout](#connection-timeout)
|
||||
* [DNS resolution](#dns-resolution)
|
||||
* [Authentication](#authentication)
|
||||
* [Advanced secure proxy connections](#advanced-secure-proxy-connections)
|
||||
* [Advanced Unix domain sockets](#advanced-unix-domain-sockets)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
* [More](#more)
|
||||
|
||||
### Quickstart example
|
||||
|
||||
The following example code demonstrates how this library can be used to send a
|
||||
secure HTTPS request to google.com through a local HTTP proxy server:
|
||||
|
||||
```php
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$proxy = new ProxyConnector('127.0.0.1:8080', new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
|
||||
$stream->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
```
|
||||
|
||||
See also the [examples](examples).
|
||||
|
||||
## Usage
|
||||
|
||||
### ProxyConnector
|
||||
|
||||
The `ProxyConnector` is responsible for creating plain TCP/IP connections to
|
||||
any destination by using an intermediary HTTP CONNECT proxy.
|
||||
|
||||
```
|
||||
[you] -> [proxy] -> [destination]
|
||||
```
|
||||
|
||||
Its constructor simply accepts an HTTP proxy URL and a connector used to connect
|
||||
to the proxy server address:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop);
|
||||
$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
|
||||
```
|
||||
|
||||
The proxy URL may or may not contain a scheme and port definition. The default
|
||||
port will be `80` for HTTP (or `443` for HTTPS), but many common HTTP proxy
|
||||
servers use custom ports (often the alternative HTTP port `8080`).
|
||||
In its most simple form, the given connector will be a
|
||||
[`\React\Socket\Connector`](https://github.com/reactphp/socket#connector) if you
|
||||
want to connect to a given IP address as above.
|
||||
|
||||
This is the main class in this package.
|
||||
Because it implements ReactPHP's standard
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
|
||||
it can simply be used in place of a normal connector.
|
||||
Accordingly, it provides only a single public method, the
|
||||
[`connect()`](https://github.com/reactphp/socket#connect) method.
|
||||
The `connect(string $uri): PromiseInterface<ConnectionInterface, Exception>`
|
||||
method can be used to establish a streaming connection.
|
||||
It returns a [Promise](https://github.com/reactphp/promise) which either
|
||||
fulfills with a [ConnectionInterface](https://github.com/reactphp/socket#connectioninterface)
|
||||
on success or rejects with an `Exception` on error.
|
||||
|
||||
This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
|
||||
higher-level component:
|
||||
|
||||
```diff
|
||||
- $client = new SomeClient($connector);
|
||||
+ $proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
|
||||
+ $client = new SomeClient($proxy);
|
||||
```
|
||||
|
||||
#### Plain TCP connections
|
||||
|
||||
HTTP CONNECT proxies are most frequently used to issue HTTPS requests to your destination.
|
||||
However, this is actually performed on a higher protocol layer and this
|
||||
connector is actually inherently a general-purpose plain TCP/IP connector.
|
||||
As documented above, you can simply invoke its `connect()` method to establish
|
||||
a streaming plain TCP/IP connection and use any higher level protocol like so:
|
||||
|
||||
```php
|
||||
$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
|
||||
|
||||
$proxy->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("EHLO local\r\n");
|
||||
$stream->on('data', function ($chunk) use ($stream) {
|
||||
echo $chunk;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
You can either use the `ProxyConnector` directly or you may want to wrap this connector
|
||||
in ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector):
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("EHLO local\r\n");
|
||||
$stream->on('data', function ($chunk) use ($stream) {
|
||||
echo $chunk;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Note that HTTP CONNECT proxies often restrict which ports one may connect to.
|
||||
Many (public) proxy servers do in fact limit this to HTTPS (443) only.
|
||||
|
||||
#### Secure TLS connections
|
||||
|
||||
This class can also be used if you want to establish a secure TLS connection
|
||||
(formerly known as SSL) between you and your destination, such as when using
|
||||
secure HTTPS to your destination site. You can simply wrap this connector in
|
||||
ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector) or the
|
||||
low-level [`SecureConnector`](https://github.com/reactphp/socket#secureconnector):
|
||||
|
||||
```php
|
||||
$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("EHLO local\r\n");
|
||||
$stream->on('data', function ($chunk) use ($stream) {
|
||||
echo $chunk;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
> Note how secure TLS connections are in fact entirely handled outside of
|
||||
this HTTP CONNECT client implementation.
|
||||
|
||||
#### Connection timeout
|
||||
|
||||
By default, the `ProxyConnector` does not implement any timeouts for establishing remote
|
||||
connections.
|
||||
Your underlying operating system may impose limits on pending and/or idle TCP/IP
|
||||
connections, anywhere in a range of a few minutes to several hours.
|
||||
|
||||
Many use cases require more control over the timeout and likely values much
|
||||
smaller, usually in the range of a few seconds only.
|
||||
|
||||
You can use ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector)
|
||||
or the low-level
|
||||
[`TimeoutConnector`](https://github.com/reactphp/socket#timeoutconnector)
|
||||
to decorate any given `ConnectorInterface` instance.
|
||||
It provides the same `connect()` method, but will automatically reject the
|
||||
underlying connection attempt if it takes too long:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false,
|
||||
'timeout' => 3.0
|
||||
));
|
||||
|
||||
$connector->connect('tcp://google.com:80')->then(function ($stream) {
|
||||
// connection succeeded within 3.0 seconds
|
||||
});
|
||||
```
|
||||
|
||||
See also any of the [examples](examples).
|
||||
|
||||
> Note how connection timeout is in fact entirely handled outside of this
|
||||
HTTP CONNECT client implementation.
|
||||
|
||||
#### DNS resolution
|
||||
|
||||
By default, the `ProxyConnector` does not perform any DNS resolution at all and simply
|
||||
forwards any hostname you're trying to connect to the remote proxy server.
|
||||
The remote proxy server is thus responsible for looking up any hostnames via DNS
|
||||
(this default mode is thus called *remote DNS resolution*).
|
||||
|
||||
As an alternative, you can also send the destination IP to the remote proxy
|
||||
server.
|
||||
In this mode you either have to stick to using IPs only (which is ofen unfeasable)
|
||||
or perform any DNS lookups locally and only transmit the resolved destination IPs
|
||||
(this mode is thus called *local DNS resolution*).
|
||||
|
||||
The default *remote DNS resolution* is useful if your local `ProxyConnector` either can
|
||||
not resolve target hostnames because it has no direct access to the internet or
|
||||
if it should not resolve target hostnames because its outgoing DNS traffic might
|
||||
be intercepted.
|
||||
|
||||
As noted above, the `ProxyConnector` defaults to using remote DNS resolution.
|
||||
However, wrapping the `ProxyConnector` in ReactPHP's
|
||||
[`Connector`](https://github.com/reactphp/socket#connector) actually
|
||||
performs local DNS resolution unless explicitly defined otherwise.
|
||||
Given that remote DNS resolution is assumed to be the preferred mode, all
|
||||
other examples explicitly disable DNS resoltion like this:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false
|
||||
));
|
||||
```
|
||||
|
||||
If you want to explicitly use *local DNS resolution*, you can use the following code:
|
||||
|
||||
```php
|
||||
// set up Connector which uses Google's public DNS (8.8.8.8)
|
||||
$connector = Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => '8.8.8.8'
|
||||
));
|
||||
```
|
||||
|
||||
> Note how local DNS resolution is in fact entirely handled outside of this
|
||||
HTTP CONNECT client implementation.
|
||||
|
||||
#### Authentication
|
||||
|
||||
If your HTTP proxy server requires authentication, you may pass the username and
|
||||
password as part of the HTTP proxy URL like this:
|
||||
|
||||
```php
|
||||
$proxy = new ProxyConnector('http://user:pass@127.0.0.1:8080', $connector);
|
||||
```
|
||||
|
||||
Note that both the username and password must be percent-encoded if they contain
|
||||
special characters:
|
||||
|
||||
```php
|
||||
$user = 'he:llo';
|
||||
$pass = 'p@ss';
|
||||
|
||||
$proxy = new ProxyConnector(
|
||||
rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1:8080',
|
||||
$connector
|
||||
);
|
||||
```
|
||||
|
||||
> The authentication details will be used for basic authentication and will be
|
||||
transferred in the `Proxy-Authorization` HTTP request header for each
|
||||
connection attempt.
|
||||
If the authentication details are missing or not accepted by the remote HTTP
|
||||
proxy server, it is expected to reject each connection attempt with a
|
||||
`407` (Proxy Authentication Required) response status code and an exception
|
||||
error code of `SOCKET_EACCES` (13).
|
||||
|
||||
#### Advanced secure proxy connections
|
||||
|
||||
Note that communication between the client and the proxy is usually via an
|
||||
unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
|
||||
setup, because you can still establish a TLS connection between you and the
|
||||
destination host as above.
|
||||
|
||||
If you want to connect to a (rather rare) HTTPS proxy, you may want use the
|
||||
`https://` scheme (HTTPS default port 443) and use ReactPHP's
|
||||
[`Connector`](https://github.com/reactphp/socket#connector) or the low-level
|
||||
[`SecureConnector`](https://github.com/reactphp/socket#secureconnector)
|
||||
instance to create a secure connection to the proxy:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop);
|
||||
$proxy = new ProxyConnector('https://127.0.0.1:443', $connector);
|
||||
|
||||
$proxy->connect('tcp://smtp.googlemail.com:587');
|
||||
```
|
||||
|
||||
#### Advanced Unix domain sockets
|
||||
|
||||
HTTP CONNECT proxy servers support forwarding TCP/IP based connections and
|
||||
higher level protocols.
|
||||
In some advanced cases, it may be useful to let your HTTP CONNECT proxy server
|
||||
listen on a Unix domain socket (UDS) path instead of a IP:port combination.
|
||||
For example, this allows you to rely on file system permissions instead of
|
||||
having to rely on explicit [authentication](#authentication).
|
||||
|
||||
You can simply use the `http+unix://` URI scheme like this:
|
||||
|
||||
```php
|
||||
$proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector);
|
||||
|
||||
$proxy->connect('tcp://google.com:80')->then(function (ConnectionInterface $stream) {
|
||||
// connected…
|
||||
});
|
||||
```
|
||||
|
||||
Similarly, you can also combine this with [authentication](#authentication)
|
||||
like this:
|
||||
|
||||
```php
|
||||
$proxy = new ProxyConnector('http+unix://user:pass@/tmp/proxy.sock', $connector);
|
||||
```
|
||||
|
||||
> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
|
||||
has limited support for this.
|
||||
In particular, enabling [secure TLS](#secure-tls-connections) may not be
|
||||
supported.
|
||||
|
||||
> Note that the HTTP CONNECT protocol does not support the notion of UDS paths.
|
||||
The above works reasonably well because UDS is only used for the connection between
|
||||
client and proxy server and the path will not actually passed over the protocol.
|
||||
This implies that this does not support connecting to UDS destination paths.
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This project follows [SemVer](http://semver.org/).
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
$ composer require clue/http-proxy-react:^1.3
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
|
||||
HHVM.
|
||||
It's *highly recommended to use PHP 7+* for this project.
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org):
|
||||
|
||||
```bash
|
||||
$ composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
$ php vendor/bin/phpunit
|
||||
```
|
||||
|
||||
The test suite contains tests that rely on a working internet connection,
|
||||
alternatively you can also run it like this:
|
||||
|
||||
```bash
|
||||
$ php vendor/bin/phpunit --exclude-group internet
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## More
|
||||
|
||||
* If you want to learn more about how the
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
|
||||
and its usual implementations look like, refer to the documentation of the underlying
|
||||
[react/socket](https://github.com/reactphp/socket) component.
|
||||
* If you want to learn more about processing streams of data, refer to the
|
||||
documentation of the underlying
|
||||
[react/stream](https://github.com/reactphp/stream) component.
|
||||
* As an alternative to an HTTP CONNECT proxy, you may also want to look into
|
||||
using a SOCKS (SOCKS4/SOCKS5) proxy instead.
|
||||
You may want to use [clue/socks-react](https://github.com/clue/php-socks-react)
|
||||
which also provides an implementation of the same
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
|
||||
so that supporting either proxy protocol should be fairly trivial.
|
||||
* If you're dealing with public proxies, you'll likely have to work with mixed
|
||||
quality and unreliable proxies. You may want to look into using
|
||||
[clue/connection-manager-extra](https://github.com/clue/php-connection-manager-extra)
|
||||
which allows retrying unreliable ones, implying connection timeouts,
|
||||
concurrently working with multiple connectors and more.
|
||||
* If you're looking for an end-user HTTP CONNECT proxy server daemon, you may
|
||||
want to use [LeProxy](https://leproxy.org/).
|
||||
30
vendor/clue/http-proxy-react/composer.json
vendored
Executable file
30
vendor/clue/http-proxy-react/composer.json
vendored
Executable file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "clue/http-proxy-react",
|
||||
"description": "Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP",
|
||||
"keywords": ["HTTP", "CONNECT", "proxy", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/php-http-proxy-react",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\HttpProxy\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Tests\\Clue\\React\\HttpProxy\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/promise": " ^2.1 || ^1.2.1",
|
||||
"react/socket": "^1.0 || ^0.8.4",
|
||||
"ringcentral/psr7": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.0 || ^4.8",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
|
||||
"clue/block-react": "^1.1"
|
||||
}
|
||||
}
|
||||
30
vendor/clue/http-proxy-react/examples/01-proxy-https.php
vendored
Executable file
30
vendor/clue/http-proxy-react/examples/01-proxy-https.php
vendored
Executable file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
// A simple example which requests https://google.com/ through an HTTP CONNECT proxy.
|
||||
// The proxy can be given as first argument and defaults to localhost:8080 otherwise.
|
||||
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$url = isset($argv[1]) ? $argv[1] : '127.0.0.1:8080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$proxy = new ProxyConnector($url, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
|
||||
$stream->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
37
vendor/clue/http-proxy-react/examples/02-optional-proxy-https.php
vendored
Executable file
37
vendor/clue/http-proxy-react/examples/02-optional-proxy-https.php
vendored
Executable file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
// A simple example which requests https://google.com/ either directly or through
|
||||
// an HTTP CONNECT proxy.
|
||||
// The Proxy can be given as first argument or does not use a proxy otherwise.
|
||||
// This example highlights how changing from direct connection to using a proxy
|
||||
// actually adds very little complexity and does not mess with your actual
|
||||
// network protocol otherwise.
|
||||
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$connector = new Connector($loop);
|
||||
|
||||
// first argument given? use this as the proxy URL
|
||||
if (isset($argv[1])) {
|
||||
$proxy = new ProxyConnector($argv[1], $connector);
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
}
|
||||
|
||||
$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
|
||||
$stream->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
32
vendor/clue/http-proxy-react/examples/11-proxy-smtp.php
vendored
Executable file
32
vendor/clue/http-proxy-react/examples/11-proxy-smtp.php
vendored
Executable file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
// A simple example which uses a plain SMTP connection to Googlemail through a HTTP CONNECT proxy.
|
||||
// Proxy can be given as first argument and defaults to localhost:8080 otherwise.
|
||||
// Please note that MANY public proxies do not allow SMTP connections, YMMV.
|
||||
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$url = isset($argv[1]) ? $argv[1] : '127.0.0.1:8080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$proxy = new ProxyConnector($url, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("EHLO local\r\n");
|
||||
$stream->on('data', function ($chunk) use ($stream) {
|
||||
echo $chunk;
|
||||
$stream->write("QUIT\r\n");
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
35
vendor/clue/http-proxy-react/examples/12-proxy-smtps.php
vendored
Executable file
35
vendor/clue/http-proxy-react/examples/12-proxy-smtps.php
vendored
Executable file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
// A simple example which uses a secure SMTP connection to Googlemail through a HTTP CONNECT proxy.
|
||||
// Proxy can be given as first argument and defaults to localhost:8080 otherwise.
|
||||
// This example highlights how changing from plain connections (see previous
|
||||
// example) to using a secure connection actually adds very little complexity
|
||||
// and does not mess with your actual network protocol otherwise.
|
||||
// Please note that MANY public proxies do not allow SMTP connections, YMMV.
|
||||
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$url = isset($argv[1]) ? $argv[1] : '127.0.0.1:8080';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$proxy = new ProxyConnector($url, new Connector($loop));
|
||||
$connector = new Connector($loop, array(
|
||||
'tcp' => $proxy,
|
||||
'timeout' => 3.0,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) {
|
||||
$stream->write("EHLO local\r\n");
|
||||
$stream->on('data', function ($chunk) use ($stream) {
|
||||
echo $chunk;
|
||||
$stream->write("QUIT\r\n");
|
||||
});
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
14
vendor/clue/http-proxy-react/phpunit.xml.dist
vendored
Executable file
14
vendor/clue/http-proxy-react/phpunit.xml.dist
vendored
Executable file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit bootstrap="vendor/autoload.php" colors="true">
|
||||
<testsuites>
|
||||
<testsuite>
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
213
vendor/clue/http-proxy-react/src/ProxyConnector.php
vendored
Executable file
213
vendor/clue/http-proxy-react/src/ProxyConnector.php
vendored
Executable file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\HttpProxy;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use RingCentral\Psr7;
|
||||
use React\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\FixedUriConnector;
|
||||
|
||||
/**
|
||||
* A simple Connector that uses an HTTP CONNECT proxy to create plain TCP/IP connections to any destination
|
||||
*
|
||||
* [you] -> [proxy] -> [destination]
|
||||
*
|
||||
* This is most frequently used to issue HTTPS requests to your destination.
|
||||
* However, this is actually performed on a higher protocol layer and this
|
||||
* connector is actually inherently a general-purpose plain TCP/IP connector.
|
||||
*
|
||||
* Note that HTTP CONNECT proxies often restrict which ports one may connect to.
|
||||
* Many (public) proxy servers do in fact limit this to HTTPS (443) only.
|
||||
*
|
||||
* If you want to establish a TLS connection (such as HTTPS) between you and
|
||||
* your destination, you may want to wrap this connector in a SecureConnector
|
||||
* instance.
|
||||
*
|
||||
* Note that communication between the client and the proxy is usually via an
|
||||
* unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
|
||||
* setup, because you can still establish a TLS connection between you and the
|
||||
* destination host as above.
|
||||
*
|
||||
* If you want to connect to a (rather rare) HTTPS proxy, you may want use its
|
||||
* HTTPS port (443) and use a SecureConnector instance to create a secure
|
||||
* connection to the proxy.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc7231#section-4.3.6
|
||||
*/
|
||||
class ProxyConnector implements ConnectorInterface
|
||||
{
|
||||
private $connector;
|
||||
private $proxyUri;
|
||||
private $proxyAuth = '';
|
||||
|
||||
/**
|
||||
* Instantiate a new ProxyConnector which uses the given $proxyUrl
|
||||
*
|
||||
* @param string $proxyUrl The proxy URL may or may not contain a scheme and
|
||||
* port definition. The default port will be `80` for HTTP (or `443` for
|
||||
* HTTPS), but many common HTTP proxy servers use custom ports.
|
||||
* @param ConnectorInterface $connector In its most simple form, the given
|
||||
* connector will be a \React\Socket\Connector if you want to connect to
|
||||
* a given IP address.
|
||||
* @throws InvalidArgumentException if the proxy URL is invalid
|
||||
*/
|
||||
public function __construct($proxyUrl, ConnectorInterface $connector)
|
||||
{
|
||||
// support `http+unix://` scheme for Unix domain socket (UDS) paths
|
||||
if (preg_match('/^http\+unix:\/\/(.*?@)?(.+?)$/', $proxyUrl, $match)) {
|
||||
// rewrite URI to parse authentication from dummy host
|
||||
$proxyUrl = 'http://' . $match[1] . 'localhost';
|
||||
|
||||
// connector uses Unix transport scheme and explicit path given
|
||||
$connector = new FixedUriConnector(
|
||||
'unix://' . $match[2],
|
||||
$connector
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($proxyUrl, '://') === false) {
|
||||
$proxyUrl = 'http://' . $proxyUrl;
|
||||
}
|
||||
|
||||
$parts = parse_url($proxyUrl);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host']) || ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')) {
|
||||
throw new InvalidArgumentException('Invalid proxy URL "' . $proxyUrl . '"');
|
||||
}
|
||||
|
||||
// apply default port and TCP/TLS transport for given scheme
|
||||
if (!isset($parts['port'])) {
|
||||
$parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
|
||||
}
|
||||
$parts['scheme'] = $parts['scheme'] === 'https' ? 'tls' : 'tcp';
|
||||
|
||||
$this->connector = $connector;
|
||||
$this->proxyUri = $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port'];
|
||||
|
||||
// prepare Proxy-Authorization header if URI contains username/password
|
||||
if (isset($parts['user']) || isset($parts['pass'])) {
|
||||
$this->proxyAuth = 'Proxy-Authorization: Basic ' . base64_encode(
|
||||
rawurldecode($parts['user'] . ':' . (isset($parts['pass']) ? $parts['pass'] : ''))
|
||||
) . "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
if (strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
}
|
||||
|
||||
$parts = parse_url($uri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
|
||||
}
|
||||
|
||||
$host = trim($parts['host'], '[]');
|
||||
$port = $parts['port'];
|
||||
|
||||
// construct URI to HTTP CONNECT proxy server to connect to
|
||||
$proxyUri = $this->proxyUri;
|
||||
|
||||
// append path from URI if given
|
||||
if (isset($parts['path'])) {
|
||||
$proxyUri .= $parts['path'];
|
||||
}
|
||||
|
||||
// parse query args
|
||||
$args = array();
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $args);
|
||||
}
|
||||
|
||||
// append hostname from URI to query string unless explicitly given
|
||||
if (!isset($args['hostname'])) {
|
||||
$args['hostname'] = $parts['host'];
|
||||
}
|
||||
|
||||
// append query string
|
||||
$proxyUri .= '?' . http_build_query($args, '', '&');;
|
||||
|
||||
// append fragment from URI if given
|
||||
if (isset($parts['fragment'])) {
|
||||
$proxyUri .= '#' . $parts['fragment'];
|
||||
}
|
||||
|
||||
$auth = $this->proxyAuth;
|
||||
|
||||
return $this->connector->connect($proxyUri)->then(function (ConnectionInterface $stream) use ($host, $port, $auth) {
|
||||
$deferred = new Deferred(function ($_, $reject) use ($stream) {
|
||||
$reject(new RuntimeException('Connection canceled while waiting for response from proxy (ECONNABORTED)', defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103));
|
||||
$stream->close();
|
||||
});
|
||||
|
||||
// keep buffering data until headers are complete
|
||||
$buffer = '';
|
||||
$fn = function ($chunk) use (&$buffer, $deferred, $stream) {
|
||||
$buffer .= $chunk;
|
||||
|
||||
$pos = strpos($buffer, "\r\n\r\n");
|
||||
if ($pos !== false) {
|
||||
// try to parse headers as response message
|
||||
try {
|
||||
$response = Psr7\parse_response(substr($buffer, 0, $pos));
|
||||
} catch (Exception $e) {
|
||||
$deferred->reject(new RuntimeException('Invalid response received from proxy (EBADMSG)', defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71, $e));
|
||||
$stream->close();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($response->getStatusCode() === 407) {
|
||||
// map status code 407 (Proxy Authentication Required) to EACCES
|
||||
$deferred->reject(new RuntimeException('Proxy denied connection due to invalid authentication ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13));
|
||||
return $stream->close();
|
||||
} elseif ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
|
||||
// map non-2xx status code to ECONNREFUSED
|
||||
$deferred->reject(new RuntimeException('Proxy refused connection with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111));
|
||||
return $stream->close();
|
||||
}
|
||||
|
||||
// all okay, resolve with stream instance
|
||||
$deferred->resolve($stream);
|
||||
|
||||
// emit remaining incoming as data event
|
||||
$buffer = (string)substr($buffer, $pos + 4);
|
||||
if ($buffer !== '') {
|
||||
$stream->emit('data', array($buffer));
|
||||
$buffer = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// stop buffering when 8 KiB have been read
|
||||
if (isset($buffer[8192])) {
|
||||
$deferred->reject(new RuntimeException('Proxy must not send more than 8 KiB of headers (EMSGSIZE)', defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90));
|
||||
$stream->close();
|
||||
}
|
||||
};
|
||||
$stream->on('data', $fn);
|
||||
|
||||
$stream->on('error', function (Exception $e) use ($deferred) {
|
||||
$deferred->reject(new RuntimeException('Stream error while waiting for response from proxy (EIO)', defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e));
|
||||
});
|
||||
|
||||
$stream->on('close', function () use ($deferred) {
|
||||
$deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
|
||||
});
|
||||
|
||||
$stream->write("CONNECT " . $host . ":" . $port . " HTTP/1.1\r\nHost: " . $host . ":" . $port . "\r\n" . $auth . "\r\n");
|
||||
|
||||
return $deferred->promise()->then(function (ConnectionInterface $stream) use ($fn) {
|
||||
// Stop buffering when connection has been established.
|
||||
$stream->removeListener('data', $fn);
|
||||
return new Promise\FulfilledPromise($stream);
|
||||
});
|
||||
}, function (Exception $e) use ($proxyUri) {
|
||||
throw new RuntimeException('Unable to connect to proxy (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, $e);
|
||||
});
|
||||
}
|
||||
}
|
||||
80
vendor/clue/http-proxy-react/tests/AbstractTestCase.php
vendored
Executable file
80
vendor/clue/http-proxy-react/tests/AbstractTestCase.php
vendored
Executable file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Clue\React\HttpProxy;
|
||||
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
abstract class AbstractTestCase extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected function expectCallableNever()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->never())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnce()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnceWith($value)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->equalTo($value));
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnceWithExceptionCode($code)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->callback(function ($e) use ($code) {
|
||||
return $e->getCode() === $code;
|
||||
}));
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
|
||||
protected function expectCallableOnceParameter($type)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->isInstanceOf($type));
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
|
||||
*/
|
||||
protected function createCallableMock()
|
||||
{
|
||||
return $this->getMockBuilder('Tests\\Clue\\React\\HttpProxy\\CallableStub')->getMock();
|
||||
}
|
||||
}
|
||||
|
||||
class CallableStub
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
75
vendor/clue/http-proxy-react/tests/FunctionalTest.php
vendored
Executable file
75
vendor/clue/http-proxy-react/tests/FunctionalTest.php
vendored
Executable file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Clue\React\HttpProxy;
|
||||
|
||||
use React\EventLoop\Factory;
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Socket\TcpConnector;
|
||||
use React\Socket\DnsConnector;
|
||||
use Clue\React\Block;
|
||||
use React\Socket\SecureConnector;
|
||||
|
||||
/** @group internet */
|
||||
class FunctionalTest extends AbstractTestCase
|
||||
{
|
||||
private $loop;
|
||||
private $tcpConnector;
|
||||
private $dnsConnector;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = Factory::create();
|
||||
|
||||
$this->tcpConnector = new TcpConnector($this->loop);
|
||||
|
||||
$f = new \React\Dns\Resolver\Factory();
|
||||
$resolver = $f->create('8.8.8.8', $this->loop);
|
||||
|
||||
$this->dnsConnector = new DnsConnector($this->tcpConnector, $resolver);
|
||||
}
|
||||
|
||||
public function testNonListeningSocketRejectsConnection()
|
||||
{
|
||||
$proxy = new ProxyConnector('127.0.0.1:9999', $this->dnsConnector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->setExpectedException('RuntimeException', 'Unable to connect to proxy', SOCKET_ECONNREFUSED);
|
||||
Block\await($promise, $this->loop, 3.0);
|
||||
}
|
||||
|
||||
public function testPlainGoogleDoesNotAcceptConnectMethod()
|
||||
{
|
||||
$proxy = new ProxyConnector('google.com', $this->dnsConnector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->setExpectedException('RuntimeException', '405 (Method Not Allowed)', SOCKET_ECONNREFUSED);
|
||||
Block\await($promise, $this->loop, 3.0);
|
||||
}
|
||||
|
||||
public function testSecureGoogleDoesNotAcceptConnectMethod()
|
||||
{
|
||||
if (!function_exists('stream_socket_enable_crypto')) {
|
||||
$this->markTestSkipped('TLS not supported on really old platforms (HHVM < 3.8)');
|
||||
}
|
||||
|
||||
$secure = new SecureConnector($this->dnsConnector, $this->loop);
|
||||
$proxy = new ProxyConnector('https://google.com:443', $secure);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->setExpectedException('RuntimeException', '405 (Method Not Allowed)', SOCKET_ECONNREFUSED);
|
||||
Block\await($promise, $this->loop, 3.0);
|
||||
}
|
||||
|
||||
public function testSecureGoogleDoesNotAcceptPlainStream()
|
||||
{
|
||||
$proxy = new ProxyConnector('google.com:443', $this->dnsConnector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->setExpectedException('RuntimeException', 'Connection to proxy lost', SOCKET_ECONNRESET);
|
||||
Block\await($promise, $this->loop, 3.0);
|
||||
}
|
||||
}
|
||||
333
vendor/clue/http-proxy-react/tests/ProxyConnectorTest.php
vendored
Executable file
333
vendor/clue/http-proxy-react/tests/ProxyConnectorTest.php
vendored
Executable file
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Clue\React\HttpProxy;
|
||||
|
||||
use Clue\React\HttpProxy\ProxyConnector;
|
||||
use React\Promise\Promise;
|
||||
use React\Socket\ConnectionInterface;
|
||||
|
||||
class ProxyConnectorTest extends AbstractTestCase
|
||||
{
|
||||
private $connector;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidProxy()
|
||||
{
|
||||
new ProxyConnector('///', $this->connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidProxyScheme()
|
||||
{
|
||||
new ProxyConnector('ftp://example.com', $this->connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidHttpsUnixScheme()
|
||||
{
|
||||
new ProxyConnector('https+unix:///tmp/proxy.sock', $this->connector);
|
||||
}
|
||||
|
||||
public function testCreatesConnectionToHttpPort()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
$this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testCreatesConnectionToHttpPortAndPassesThroughUriComponents()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
$this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80/path?foo=bar&hostname=google.com#segment')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80/path?foo=bar#segment');
|
||||
}
|
||||
|
||||
public function testCreatesConnectionToHttpPortAndObeysExplicitHostname()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
$this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80?hostname=www.google.com')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80?hostname=www.google.com');
|
||||
}
|
||||
|
||||
public function testCreatesConnectionToHttpsPort()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
$this->connector->expects($this->once())->method('connect')->with('tls://proxy.example.com:443?hostname=google.com')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('https://proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testCreatesConnectionToUnixPath()
|
||||
{
|
||||
$promise = new Promise(function () { });
|
||||
$this->connector->expects($this->once())->method('connect')->with('unix:///tmp/proxy.sock')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testCancelPromiseWillCancelPendingConnection()
|
||||
{
|
||||
$promise = new Promise(function () { }, $this->expectCallableOnce());
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->assertInstanceOf('React\Promise\CancellablePromiseInterface', $promise);
|
||||
|
||||
$promise->cancel();
|
||||
}
|
||||
|
||||
public function testWillWriteToOpenConnection()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\n\r\n");
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testWillProxyAuthorizationHeaderIfProxyUriContainsAuthentication()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n");
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('user:pass@proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testWillProxyAuthorizationHeaderIfProxyUriContainsOnlyUsernameWithoutPassword()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjo=\r\n\r\n");
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('user@proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testWillProxyAuthorizationHeaderIfProxyUriContainsAuthenticationWithPercentEncoding()
|
||||
{
|
||||
$user = 'h@llÖ';
|
||||
$pass = '%secret?';
|
||||
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic " . base64_encode($user . ':' . $pass) . "\r\n\r\n");
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector(rawurlencode($user) . ':' . rawurlencode($pass) . '@proxy.example.com', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testWillProxyAuthorizationHeaderIfUnixProxyUriContainsAuthentication()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n");
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->with('unix:///tmp/proxy.sock')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('http+unix://user:pass@/tmp/proxy.sock', $this->connector);
|
||||
|
||||
$proxy->connect('google.com:80');
|
||||
}
|
||||
|
||||
public function testRejectsInvalidUri()
|
||||
{
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('///');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testRejectsUriWithNonTcpScheme()
|
||||
{
|
||||
$this->connector->expects($this->never())->method('connect');
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('tls://google.com:80');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testRejectsIfConnectorRejects()
|
||||
{
|
||||
$promise = \React\Promise\reject(new \RuntimeException());
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testRejectsAndClosesIfStreamWritesNonHttp()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$stream->expects($this->once())->method('close');
|
||||
$stream->emit('data', array("invalid\r\n\r\n"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
|
||||
}
|
||||
|
||||
public function testRejectsAndClosesIfStreamWritesTooMuchData()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$stream->expects($this->once())->method('close');
|
||||
$stream->emit('data', array(str_repeat('*', 100000)));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EMSGSIZE));
|
||||
}
|
||||
|
||||
public function testRejectsAndClosesIfStreamReturnsProyAuthenticationRequired()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$stream->expects($this->once())->method('close');
|
||||
$stream->emit('data', array("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EACCES));
|
||||
}
|
||||
|
||||
public function testRejectsAndClosesIfStreamReturnsNonSuccess()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$stream->expects($this->once())->method('close');
|
||||
$stream->emit('data', array("HTTP/1.1 403 Not allowed\r\n\r\n"));
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
|
||||
}
|
||||
|
||||
public function testResolvesIfStreamReturnsSuccess()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$promise->then($this->expectCallableOnce('React\Stream\Stream'));
|
||||
$never = $this->expectCallableNever();
|
||||
$promise->then(function (ConnectionInterface $stream) use ($never) {
|
||||
$stream->on('data', $never);
|
||||
});
|
||||
|
||||
$stream->emit('data', array("HTTP/1.1 200 OK\r\n\r\n"));
|
||||
}
|
||||
|
||||
public function testResolvesIfStreamReturnsSuccessAndEmitsExcessiveData()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$once = $this->expectCallableOnceWith('hello!');
|
||||
$promise->then(function (ConnectionInterface $stream) use ($once) {
|
||||
$stream->on('data', $once);
|
||||
});
|
||||
|
||||
$stream->emit('data', array("HTTP/1.1 200 OK\r\n\r\nhello!"));
|
||||
}
|
||||
|
||||
public function testCancelPromiseWillCloseOpenConnectionAndReject()
|
||||
{
|
||||
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
|
||||
$stream->expects($this->once())->method('close');
|
||||
|
||||
$promise = \React\Promise\resolve($stream);
|
||||
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
|
||||
|
||||
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
|
||||
|
||||
$promise = $proxy->connect('google.com:80');
|
||||
|
||||
$this->assertInstanceOf('React\Promise\CancellablePromiseInterface', $promise);
|
||||
|
||||
$promise->cancel();
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNABORTED));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user