Initial Commit
This commit is contained in:
213
dependencies/amphp/socket/src/Internal/functions.php
vendored
Normal file
213
dependencies/amphp/socket/src/Internal/functions.php
vendored
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user