Initial Commit

This commit is contained in:
David Stone
2024-11-30 18:24:12 -07:00
commit e8f7955c1c
5432 changed files with 1397750 additions and 0 deletions

View File

@ -0,0 +1,213 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Socket\Internal;
use WP_Ultimo\Dependencies\Amp\CancellationToken;
use WP_Ultimo\Dependencies\Amp\Deferred;
use WP_Ultimo\Dependencies\Amp\Failure;
use WP_Ultimo\Dependencies\Amp\Loop;
use WP_Ultimo\Dependencies\Amp\NullCancellationToken;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\Amp\Socket\TlsException;
use WP_Ultimo\Dependencies\Amp\Success;
use WP_Ultimo\Dependencies\League\Uri;
use function WP_Ultimo\Dependencies\Amp\call;
/**
* Parse an URI into [scheme, host, port].
*
* @param string $uri
*
* @return array
*
* @throws \Error If an invalid URI has been passed.
*
* @internal
*/
function parseUri(string $uri) : array
{
if (\stripos($uri, 'unix://') === 0 || \stripos($uri, 'udg://') === 0) {
[$scheme, $path] = \explode('://', $uri, 2);
return [$scheme, \ltrim($path, '/'), 0];
}
if (\strpos($uri, '://') === \false) {
// Set a default scheme of tcp if none was given.
$uri = 'tcp://' . $uri;
}
try {
$uriParts = Uri\parse($uri);
} catch (\Exception $exception) {
throw new \Error("Invalid URI: {$uri}", 0, $exception);
}
$scheme = $uriParts['scheme'];
$host = $uriParts['host'] ?? '';
$port = $uriParts['port'] ?? 0;
if (!\in_array($scheme, ['tcp', 'udp', 'unix', 'udg'], \true)) {
throw new \Error("Invalid URI scheme ({$scheme}); tcp, udp, unix or udg scheme expected");
}
if ($host === '' || $port === 0) {
throw new \Error("Invalid URI: {$uri}; host and port components required");
}
if (\strpos($host, ':') !== \false) {
// IPv6 address
$host = \sprintf('[%s]', \trim($host, '[]'));
}
return [$scheme, $host, $port];
}
/**
* Enable encryption on an existing socket stream.
*
* @param resource $socket
* @param array $options
* @param CancellationToken $cancellationToken
*
* @return Promise<void>
*
* @internal
*/
function setupTls($socket, array $options, ?CancellationToken $cancellationToken) : Promise
{
$cancellationToken = $cancellationToken ?? new NullCancellationToken();
if (isset(\stream_get_meta_data($socket)['crypto'])) {
return new Failure(new TlsException("Can't setup TLS, because it has already been set up"));
}
\error_clear_last();
\stream_context_set_option($socket, $options);
try {
\set_error_handler(static function (int $errno, string $errstr) {
new TlsException('TLS negotiation failed: ' . $errstr);
});
$result = \stream_socket_enable_crypto($socket, $enable = \true);
if ($result === \false) {
throw new TlsException('TLS negotiation failed: Unknown error');
}
} catch (TlsException $e) {
return new Failure($e);
} finally {
\restore_error_handler();
}
// Yes, that function can return true / false / 0, don't use weak comparisons.
if ($result === \true) {
/** @psalm-suppress InvalidReturnStatement */
return new Success();
}
return call(static function () use($socket, $cancellationToken) {
$cancellationToken->throwIfRequested();
$deferred = new Deferred();
// Watcher is guaranteed to be created, because we throw above if cancellation has already been requested
$id = $cancellationToken->subscribe(static function ($e) use($deferred, &$watcher) {
Loop::cancel($watcher);
$deferred->fail($e);
});
$watcher = Loop::onReadable($socket, static function (string $watcher, $socket, Deferred $deferred) use($cancellationToken, $id) {
try {
try {
\set_error_handler(static function (int $errno, string $errstr) use($socket) {
if (\feof($socket)) {
$errstr = 'Connection reset by peer';
}
throw new TlsException('TLS negotiation failed: ' . $errstr);
});
$result = \stream_socket_enable_crypto($socket, \true);
if ($result === \false) {
$message = \feof($socket) ? 'Connection reset by peer' : 'Unknown error';
throw new TlsException('TLS negotiation failed: ' . $message);
}
} finally {
\restore_error_handler();
}
} catch (TlsException $e) {
Loop::cancel($watcher);
$cancellationToken->unsubscribe($id);
$deferred->fail($e);
return;
}
// If $result is 0, just wait for the next invocation
if ($result === \true) {
Loop::cancel($watcher);
$cancellationToken->unsubscribe($id);
$deferred->resolve();
}
}, $deferred);
return $deferred->promise();
});
}
/**
* Disable encryption on an existing socket stream.
*
* @param resource $socket
*
* @return Promise<void>
*
* @internal
* @psalm-suppress InvalidReturnType
*/
function shutdownTls($socket) : Promise
{
// note that disabling crypto *ALWAYS* returns false, immediately
// don't set _enabled to false, TLS can be setup only once
@\stream_socket_enable_crypto($socket, \false);
/** @psalm-suppress InvalidReturnStatement */
return new Success();
}
/**
* Normalizes "bindto" options to add a ":0" in case no port is present, otherwise PHP will silently ignore those.
*
* @param string|null $bindTo
*
* @return string|null
*
* @throws \Error If an invalid option has been passed.
*/
function normalizeBindToOption(string $bindTo = null)
{
if ($bindTo === null) {
return null;
}
if (\preg_match("/\\[(?P<ip>[0-9a-f:]+)\\](:(?P<port>\\d+))?\$/", $bindTo, $match)) {
$ip = $match['ip'];
$port = $match['port'] ?? 0;
if (@\inet_pton($ip) === \false) {
throw new \Error("Invalid IPv6 address: {$ip}");
}
if ($port < 0 || $port > 65535) {
throw new \Error("Invalid port: {$port}");
}
return "[{$ip}]:{$port}";
}
if (\preg_match("/(?P<ip>\\d+\\.\\d+\\.\\d+\\.\\d+)(:(?P<port>\\d+))?\$/", $bindTo, $match)) {
$ip = $match['ip'];
$port = $match['port'] ?? 0;
if (@\inet_pton($ip) === \false) {
throw new \Error("Invalid IPv4 address: {$ip}");
}
if ($port < 0 || $port > 65535) {
throw new \Error("Invalid port: {$port}");
}
return "{$ip}:{$port}";
}
throw new \Error("Invalid bindTo value: {$bindTo}");
}
/**
* Cleans up return values of stream_socket_get_name.
*
* @param string|false $address
*
* @return string|null
*/
function cleanupSocketName($address)
{
// https://3v4l.org/5C1lo
if ($address === \false || $address === "\x00") {
return null;
}
// Check if this is an IPv6 address which includes multiple colons but no square brackets
// @see https://github.com/reactphp/socket/blob/v0.8.10/src/TcpServer.php#L179-L184
// @license https://github.com/reactphp/socket/blob/v0.8.10/LICENSE
$pos = \strrpos($address, ':');
if ($pos !== \false && \strpos($address, ':') < $pos && $address[0] !== '[') {
$port = \substr($address, $pos + 1);
$address = '[' . \substr($address, 0, $pos) . ']:' . $port;
}
// -- End of imported code ----- //
return $address;
}