summaryrefslogtreecommitdiffstats
path: root/vendor/clue/http-proxy-react
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:38:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:38:42 +0000
commitc3ca98e1b35123f226c7f4c596b5dee78caa4223 (patch)
tree9b6eb109283da55e7d9064baa9fac795a40264cb /vendor/clue/http-proxy-react
parentInitial commit. (diff)
downloadicinga-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/LICENSE21
-rw-r--r--vendor/clue/http-proxy-react/composer.json31
-rw-r--r--vendor/clue/http-proxy-react/src/ProxyConnector.php274
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();
+ }
+}