diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:38:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:38:42 +0000 |
commit | c3ca98e1b35123f226c7f4c596b5dee78caa4223 (patch) | |
tree | 9b6eb109283da55e7d9064baa9fac795a40264cb /vendor/clue/http-proxy-react | |
parent | Initial commit. (diff) | |
download | icinga-php-thirdparty-c3ca98e1b35123f226c7f4c596b5dee78caa4223.tar.xz icinga-php-thirdparty-c3ca98e1b35123f226c7f4c596b5dee78caa4223.zip |
Adding upstream version 0.11.0.upstream/0.11.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/clue/http-proxy-react')
-rw-r--r-- | vendor/clue/http-proxy-react/LICENSE | 21 | ||||
-rw-r--r-- | vendor/clue/http-proxy-react/composer.json | 31 | ||||
-rw-r--r-- | vendor/clue/http-proxy-react/src/ProxyConnector.php | 274 |
3 files changed, 326 insertions, 0 deletions
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/composer.json b/vendor/clue/http-proxy-react/composer.json new file mode 100644 index 0000000..9840cf0 --- /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": " ^2.1 || ^1.2.1", + "react/socket": "^1.9", + "ringcentral/psr7": "^1.2" + }, + "require-dev": { + "clue/block-react": "^1.1", + "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..0bca17e --- /dev/null +++ b/vendor/clue/http-proxy-react/src/ProxyConnector.php @@ -0,0 +1,274 @@ +<?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($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 &$one) { + if (isset($one['args'])) { + foreach ($one['args'] as &$arg) { + if ($arg instanceof \Closure) { + $arg = 'Object(' . get_class($arg) . ')'; + } + } + } + } + // @codeCoverageIgnoreEnd + $r->setValue($e, $trace); + }); + + return $deferred->promise(); + } +} |