diff options
author | marvin-borner@live.com | 2018-04-10 21:50:16 +0200 |
---|---|---|
committer | marvin-borner@live.com | 2018-04-10 21:54:48 +0200 |
commit | fc9401f04a3aca5abb22f87ebc210de8afe11d32 (patch) | |
tree | b0b310f3581764ec3955f4e496a05137a32951c3 /assets/php/vendor/react/socket/src | |
parent | 286d643180672f20526f3dc3bd19d7b751e2fa97 (diff) |
Initial Commit
Diffstat (limited to 'assets/php/vendor/react/socket/src')
17 files changed, 2029 insertions, 0 deletions
diff --git a/assets/php/vendor/react/socket/src/Connection.php b/assets/php/vendor/react/socket/src/Connection.php new file mode 100644 index 0000000..c6267cc --- /dev/null +++ b/assets/php/vendor/react/socket/src/Connection.php @@ -0,0 +1,178 @@ +<?php + +namespace React\Socket; + +use Evenement\EventEmitter; +use React\EventLoop\LoopInterface; +use React\Stream\DuplexResourceStream; +use React\Stream\Util; +use React\Stream\WritableResourceStream; +use React\Stream\WritableStreamInterface; + +/** + * The actual connection implementation for ConnectionInterface + * + * This class should only be used internally, see ConnectionInterface instead. + * + * @see ConnectionInterface + * @internal + */ +class Connection extends EventEmitter implements ConnectionInterface +{ + /** + * Internal flag whether this is a Unix domain socket (UDS) connection + * + * @internal + */ + public $unix = false; + + /** + * Internal flag whether encryption has been enabled on this connection + * + * Mostly used by internal StreamEncryption so that connection returns + * `tls://` scheme for encrypted connections instead of `tcp://`. + * + * @internal + */ + public $encryptionEnabled = false; + + /** @internal */ + public $stream; + + private $input; + + public function __construct($resource, LoopInterface $loop) + { + // PHP < 5.6.8 suffers from a buffer indicator bug on secure TLS connections + // as a work-around we always read the complete buffer until its end. + // The buffer size is limited due to TCP/IP buffers anyway, so this + // should not affect usage otherwise. + // See https://bugs.php.net/bug.php?id=65137 + // https://bugs.php.net/bug.php?id=41631 + // https://github.com/reactphp/socket-client/issues/24 + $clearCompleteBuffer = PHP_VERSION_ID < 50608; + + // PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big + // chunks of data over TLS streams at once. + // We try to work around this by limiting the write chunk size to 8192 + // bytes for older PHP versions only. + // This is only a work-around and has a noticable performance penalty on + // affected versions. Please update your PHP version. + // This applies to all streams because TLS may be enabled later on. + // See https://github.com/reactphp/socket/issues/105 + $limitWriteChunks = (PHP_VERSION_ID < 70018 || (PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70104)); + + $this->input = new DuplexResourceStream( + $resource, + $loop, + $clearCompleteBuffer ? -1 : null, + new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null) + ); + + $this->stream = $resource; + + Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain')); + + $this->input->on('close', array($this, 'close')); + } + + public function isReadable() + { + return $this->input->isReadable(); + } + + public function isWritable() + { + return $this->input->isWritable(); + } + + public function pause() + { + $this->input->pause(); + } + + public function resume() + { + $this->input->resume(); + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + return $this->input->pipe($dest, $options); + } + + public function write($data) + { + return $this->input->write($data); + } + + public function end($data = null) + { + $this->input->end($data); + } + + public function close() + { + $this->input->close(); + $this->handleClose(); + $this->removeAllListeners(); + } + + public function handleClose() + { + if (!is_resource($this->stream)) { + return; + } + + // Try to cleanly shut down socket and ignore any errors in case other + // side already closed. Shutting down may return to blocking mode on + // some legacy versions, so reset to non-blocking just in case before + // continuing to close the socket resource. + // Underlying Stream implementation will take care of closing file + // handle, so we otherwise keep this open here. + @stream_socket_shutdown($this->stream, STREAM_SHUT_RDWR); + stream_set_blocking($this->stream, false); + } + + public function getRemoteAddress() + { + return $this->parseAddress(@stream_socket_get_name($this->stream, true)); + } + + public function getLocalAddress() + { + return $this->parseAddress(@stream_socket_get_name($this->stream, false)); + } + + private function parseAddress($address) + { + if ($address === false) { + return null; + } + + if ($this->unix) { + // remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo + // note that technically ":" is a valid address, so keep this in place otherwise + if (substr($address, -1) === ':' && defined('HHVM_VERSION_ID') && HHVM_VERSION_ID < 31900) { + $address = (string)substr($address, 0, -1); + } + + // work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556 + // PHP uses "\0" string and HHVM uses empty string (colon removed above) + if ($address === '' || $address[0] === "\x00" ) { + return null; + } + + return 'unix://' . $address; + } + + // check if this is an IPv6 address which includes multiple colons but no square brackets + $pos = strrpos($address, ':'); + if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') { + $port = substr($address, $pos + 1); + $address = '[' . substr($address, 0, $pos) . ']:' . $port; + } + + return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address; + } +} diff --git a/assets/php/vendor/react/socket/src/ConnectionInterface.php b/assets/php/vendor/react/socket/src/ConnectionInterface.php new file mode 100644 index 0000000..64613b5 --- /dev/null +++ b/assets/php/vendor/react/socket/src/ConnectionInterface.php @@ -0,0 +1,119 @@ +<?php + +namespace React\Socket; + +use React\Stream\DuplexStreamInterface; + +/** + * Any incoming and outgoing connection is represented by this interface, + * such as a normal TCP/IP connection. + * + * An incoming or outgoing connection is a duplex stream (both readable and + * writable) that implements React's + * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface). + * It contains additional properties for the local and remote address (client IP) + * where this connection has been established to/from. + * + * Most commonly, instances implementing this `ConnectionInterface` are emitted + * by all classes implementing the [`ServerInterface`](#serverinterface) and + * used by all classes implementing the [`ConnectorInterface`](#connectorinterface). + * + * Because the `ConnectionInterface` implements the underlying + * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface) + * you can use any of its events and methods as usual: + * + * ```php + * $connection->on('data', function ($chunk) { + * echo $chunk; + * }); + * + * $connection->on('end', function () { + * echo 'ended'; + * }); + * + * $connection->on('error', function (Exception $e) { + * echo 'error: ' . $e->getMessage(); + * }); + * + * $connection->on('close', function () { + * echo 'closed'; + * }); + * + * $connection->write($data); + * $connection->end($data = null); + * $connection->close(); + * // … + * ``` + * + * For more details, see the + * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface). + * + * @see DuplexStreamInterface + * @see ServerInterface + * @see ConnectorInterface + */ +interface ConnectionInterface extends DuplexStreamInterface +{ + /** + * Returns the full remote address (URI) where this connection has been established with + * + * ```php + * $address = $connection->getRemoteAddress(); + * echo 'Connection with ' . $address . PHP_EOL; + * ``` + * + * If the remote address can not be determined or is unknown at this time (such as + * after the connection has been closed), it MAY return a `NULL` value instead. + * + * Otherwise, it will return the full address (URI) as a string value, such + * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`, + * `unix://example.sock` or `unix:///path/to/example.sock`. + * Note that individual URI components are application specific and depend + * on the underlying transport protocol. + * + * If this is a TCP/IP based connection and you only want the remote IP, you may + * use something like this: + * + * ```php + * $address = $connection->getRemoteAddress(); + * $ip = trim(parse_url($address, PHP_URL_HOST), '[]'); + * echo 'Connection with ' . $ip . PHP_EOL; + * ``` + * + * @return ?string remote address (URI) or null if unknown + */ + public function getRemoteAddress(); + + /** + * Returns the full local address (full URI with scheme, IP and port) where this connection has been established with + * + * ```php + * $address = $connection->getLocalAddress(); + * echo 'Connection with ' . $address . PHP_EOL; + * ``` + * + * If the local address can not be determined or is unknown at this time (such as + * after the connection has been closed), it MAY return a `NULL` value instead. + * + * Otherwise, it will return the full address (URI) as a string value, such + * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`, + * `unix://example.sock` or `unix:///path/to/example.sock`. + * Note that individual URI components are application specific and depend + * on the underlying transport protocol. + * + * This method complements the [`getRemoteAddress()`](#getremoteaddress) method, + * so they should not be confused. + * + * If your `TcpServer` instance is listening on multiple interfaces (e.g. using + * the address `0.0.0.0`), you can use this method to find out which interface + * actually accepted this connection (such as a public or local interface). + * + * If your system has multiple interfaces (e.g. a WAN and a LAN interface), + * you can use this method to find out which interface was actually + * used for this connection. + * + * @return ?string local address (URI) or null if unknown + * @see self::getRemoteAddress() + */ + public function getLocalAddress(); +} diff --git a/assets/php/vendor/react/socket/src/Connector.php b/assets/php/vendor/react/socket/src/Connector.php new file mode 100644 index 0000000..75276bc --- /dev/null +++ b/assets/php/vendor/react/socket/src/Connector.php @@ -0,0 +1,136 @@ +<?php + +namespace React\Socket; + +use React\Dns\Config\Config; +use React\Dns\Resolver\Factory; +use React\Dns\Resolver\Resolver; +use React\EventLoop\LoopInterface; +use React\Promise; +use RuntimeException; + +/** + * The `Connector` class is the main class in this package that implements the + * `ConnectorInterface` and allows you to create streaming connections. + * + * You can use this connector to create any kind of streaming connections, such + * as plaintext TCP/IP, secure TLS or local Unix connection streams. + * + * Under the hood, the `Connector` is implemented as a *higher-level facade* + * or the lower-level connectors implemented in this package. This means it + * also shares all of their features and implementation details. + * If you want to typehint in your higher-level protocol implementation, you SHOULD + * use the generic [`ConnectorInterface`](#connectorinterface) instead. + * + * @see ConnectorInterface for the base interface + */ +final class Connector implements ConnectorInterface +{ + private $connectors = array(); + + public function __construct(LoopInterface $loop, array $options = array()) + { + // apply default options if not explicitly given + $options += array( + 'tcp' => true, + 'tls' => true, + 'unix' => true, + + 'dns' => true, + 'timeout' => true, + ); + + if ($options['timeout'] === true) { + $options['timeout'] = (float)ini_get("default_socket_timeout"); + } + + if ($options['tcp'] instanceof ConnectorInterface) { + $tcp = $options['tcp']; + } else { + $tcp = new TcpConnector( + $loop, + is_array($options['tcp']) ? $options['tcp'] : array() + ); + } + + if ($options['dns'] !== false) { + if ($options['dns'] instanceof Resolver) { + $resolver = $options['dns']; + } else { + if ($options['dns'] !== true) { + $server = $options['dns']; + } else { + // try to load nameservers from system config or default to Google's public DNS + $config = Config::loadSystemConfigBlocking(); + $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + } + + $factory = new Factory(); + $resolver = $factory->create( + $server, + $loop + ); + } + + $tcp = new DnsConnector($tcp, $resolver); + } + + if ($options['tcp'] !== false) { + $options['tcp'] = $tcp; + + if ($options['timeout'] !== false) { + $options['tcp'] = new TimeoutConnector( + $options['tcp'], + $options['timeout'], + $loop + ); + } + + $this->connectors['tcp'] = $options['tcp']; + } + + if ($options['tls'] !== false) { + if (!$options['tls'] instanceof ConnectorInterface) { + $options['tls'] = new SecureConnector( + $tcp, + $loop, + is_array($options['tls']) ? $options['tls'] : array() + ); + } + + if ($options['timeout'] !== false) { + $options['tls'] = new TimeoutConnector( + $options['tls'], + $options['timeout'], + $loop + ); + } + + $this->connectors['tls'] = $options['tls']; + } + + if ($options['unix'] !== false) { + if (!$options['unix'] instanceof ConnectorInterface) { + $options['unix'] = new UnixConnector($loop); + } + $this->connectors['unix'] = $options['unix']; + } + } + + public function connect($uri) + { + $scheme = 'tcp'; + if (strpos($uri, '://') !== false) { + $scheme = (string)substr($uri, 0, strpos($uri, '://')); + } + + if (!isset($this->connectors[$scheme])) { + return Promise\reject(new RuntimeException( + 'No connector available for URI scheme "' . $scheme . '"' + )); + } + + return $this->connectors[$scheme]->connect($uri); + } +} + diff --git a/assets/php/vendor/react/socket/src/ConnectorInterface.php b/assets/php/vendor/react/socket/src/ConnectorInterface.php new file mode 100644 index 0000000..196d01a --- /dev/null +++ b/assets/php/vendor/react/socket/src/ConnectorInterface.php @@ -0,0 +1,58 @@ +<?php + +namespace React\Socket; + +/** + * The `ConnectorInterface` is responsible for providing an interface for + * establishing streaming connections, such as a normal TCP/IP connection. + * + * This is the main interface defined in this package and it is used throughout + * React's vast ecosystem. + * + * Most higher-level components (such as HTTP, database or other networking + * service clients) accept an instance implementing this interface to create their + * TCP/IP connection to the underlying networking service. + * This is usually done via dependency injection, so it's fairly simple to actually + * swap this implementation against any other implementation of this interface. + * + * The interface only offers a single `connect()` method. + * + * @see ConnectionInterface + */ +interface ConnectorInterface +{ + /** + * Creates a streaming connection to the given remote address + * + * If returns a Promise which either fulfills with a stream implementing + * `ConnectionInterface` on success or rejects with an `Exception` if the + * connection is not successful. + * + * ```php + * $connector->connect('google.com:443')->then( + * function (ConnectionInterface $connection) { + * // connection successfully established + * }, + * function (Exception $error) { + * // failed to connect due to $error + * } + * ); + * ``` + * + * The returned Promise MUST be implemented in such a way that it can be + * cancelled when it is still pending. Cancelling a pending promise MUST + * reject its value with an Exception. It SHOULD clean up any underlying + * resources and references as applicable. + * + * ```php + * $promise = $connector->connect($uri); + * + * $promise->cancel(); + * ``` + * + * @param string $uri + * @return \React\Promise\PromiseInterface resolves with a stream implementing ConnectionInterface on success or rejects with an Exception on error + * @see ConnectionInterface + */ + public function connect($uri); +} diff --git a/assets/php/vendor/react/socket/src/DnsConnector.php b/assets/php/vendor/react/socket/src/DnsConnector.php new file mode 100644 index 0000000..90170e5 --- /dev/null +++ b/assets/php/vendor/react/socket/src/DnsConnector.php @@ -0,0 +1,111 @@ +<?php + +namespace React\Socket; + +use React\Dns\Resolver\Resolver; +use React\Promise; +use React\Promise\CancellablePromiseInterface; +use InvalidArgumentException; +use RuntimeException; + +final class DnsConnector implements ConnectorInterface +{ + private $connector; + private $resolver; + + public function __construct(ConnectorInterface $connector, Resolver $resolver) + { + $this->connector = $connector; + $this->resolver = $resolver; + } + + public function connect($uri) + { + if (strpos($uri, '://') === false) { + $parts = parse_url('tcp://' . $uri); + unset($parts['scheme']); + } else { + $parts = parse_url($uri); + } + + if (!$parts || !isset($parts['host'])) { + return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid')); + } + + $host = trim($parts['host'], '[]'); + $connector = $this->connector; + + return $this + ->resolveHostname($host) + ->then(function ($ip) use ($connector, $host, $parts) { + $uri = ''; + + // prepend original scheme if known + if (isset($parts['scheme'])) { + $uri .= $parts['scheme'] . '://'; + } + + if (strpos($ip, ':') !== false) { + // enclose IPv6 addresses in square brackets before appending port + $uri .= '[' . $ip . ']'; + } else { + $uri .= $ip; + } + + // append original port if known + if (isset($parts['port'])) { + $uri .= ':' . $parts['port']; + } + + // append orignal path if known + if (isset($parts['path'])) { + $uri .= $parts['path']; + } + + // append original query if known + if (isset($parts['query'])) { + $uri .= '?' . $parts['query']; + } + + // append original hostname as query if resolved via DNS and if + // destination URI does not contain "hostname" query param already + $args = array(); + parse_str(isset($parts['query']) ? $parts['query'] : '', $args); + if ($host !== $ip && !isset($args['hostname'])) { + $uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . rawurlencode($host); + } + + // append original fragment if known + if (isset($parts['fragment'])) { + $uri .= '#' . $parts['fragment']; + } + + return $connector->connect($uri); + }); + } + + private function resolveHostname($host) + { + if (false !== filter_var($host, FILTER_VALIDATE_IP)) { + return Promise\resolve($host); + } + + $promise = $this->resolver->resolve($host); + + return new Promise\Promise( + function ($resolve, $reject) use ($promise) { + // resolve/reject with result of DNS lookup + $promise->then($resolve, $reject); + }, + function ($_, $reject) use ($promise) { + // cancellation should reject connection attempt + $reject(new RuntimeException('Connection attempt cancelled during DNS lookup')); + + // (try to) cancel pending DNS lookup + if ($promise instanceof CancellablePromiseInterface) { + $promise->cancel(); + } + } + ); + } +} diff --git a/assets/php/vendor/react/socket/src/FixedUriConnector.php b/assets/php/vendor/react/socket/src/FixedUriConnector.php new file mode 100644 index 0000000..057bcdf --- /dev/null +++ b/assets/php/vendor/react/socket/src/FixedUriConnector.php @@ -0,0 +1,41 @@ +<?php + +namespace React\Socket; + +/** + * Decorates an existing Connector to always use a fixed, preconfigured URI + * + * This can be useful for consumers that do not support certain URIs, such as + * when you want to explicitly connect to a Unix domain socket (UDS) path + * instead of connecting to a default address assumed by an higher-level API: + * + * ```php + * $connector = new FixedUriConnector( + * 'unix:///var/run/docker.sock', + * new UnixConnector($loop) + * ); + * + * // destination will be ignored, actually connects to Unix domain socket + * $promise = $connector->connect('localhost:80'); + * ``` + */ +class FixedUriConnector implements ConnectorInterface +{ + private $uri; + private $connector; + + /** + * @param string $uri + * @param ConnectorInterface $connector + */ + public function __construct($uri, ConnectorInterface $connector) + { + $this->uri = $uri; + $this->connector = $connector; + } + + public function connect($_) + { + return $this->connector->connect($this->uri); + } +} diff --git a/assets/php/vendor/react/socket/src/LimitingServer.php b/assets/php/vendor/react/socket/src/LimitingServer.php new file mode 100644 index 0000000..c7874ee --- /dev/null +++ b/assets/php/vendor/react/socket/src/LimitingServer.php @@ -0,0 +1,203 @@ +<?php + +namespace React\Socket; + +use Evenement\EventEmitter; +use Exception; +use OverflowException; + +/** + * The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible + * for limiting and keeping track of open connections to this server instance. + * + * Whenever the underlying server emits a `connection` event, it will check its + * limits and then either + * - keep track of this connection by adding it to the list of + * open connections and then forward the `connection` event + * - or reject (close) the connection when its limits are exceeded and will + * forward an `error` event instead. + * + * Whenever a connection closes, it will remove this connection from the list of + * open connections. + * + * ```php + * $server = new LimitingServer($server, 100); + * $server->on('connection', function (ConnectionInterface $connection) { + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * See also the `ServerInterface` for more details. + * + * @see ServerInterface + * @see ConnectionInterface + */ +class LimitingServer extends EventEmitter implements ServerInterface +{ + private $connections = array(); + private $server; + private $limit; + + private $pauseOnLimit = false; + private $autoPaused = false; + private $manuPaused = false; + + /** + * Instantiates a new LimitingServer. + * + * You have to pass a maximum number of open connections to ensure + * the server will automatically reject (close) connections once this limit + * is exceeded. In this case, it will emit an `error` event to inform about + * this and no `connection` event will be emitted. + * + * ```php + * $server = new LimitingServer($server, 100); + * $server->on('connection', function (ConnectionInterface $connection) { + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * You MAY pass a `null` limit in order to put no limit on the number of + * open connections and keep accepting new connection until you run out of + * operating system resources (such as open file handles). This may be + * useful if you do not want to take care of applying a limit but still want + * to use the `getConnections()` method. + * + * You can optionally configure the server to pause accepting new + * connections once the connection limit is reached. In this case, it will + * pause the underlying server and no longer process any new connections at + * all, thus also no longer closing any excessive connections. + * The underlying operating system is responsible for keeping a backlog of + * pending connections until its limit is reached, at which point it will + * start rejecting further connections. + * Once the server is below the connection limit, it will continue consuming + * connections from the backlog and will process any outstanding data on + * each connection. + * This mode may be useful for some protocols that are designed to wait for + * a response message (such as HTTP), but may be less useful for other + * protocols that demand immediate responses (such as a "welcome" message in + * an interactive chat). + * + * ```php + * $server = new LimitingServer($server, 100, true); + * $server->on('connection', function (ConnectionInterface $connection) { + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * @param ServerInterface $server + * @param int|null $connectionLimit + * @param bool $pauseOnLimit + */ + public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false) + { + $this->server = $server; + $this->limit = $connectionLimit; + if ($connectionLimit !== null) { + $this->pauseOnLimit = $pauseOnLimit; + } + + $this->server->on('connection', array($this, 'handleConnection')); + $this->server->on('error', array($this, 'handleError')); + } + + /** + * Returns an array with all currently active connections + * + * ```php + * foreach ($server->getConnection() as $connection) { + * $connection->write('Hi!'); + * } + * ``` + * + * @return ConnectionInterface[] + */ + public function getConnections() + { + return $this->connections; + } + + public function getAddress() + { + return $this->server->getAddress(); + } + + public function pause() + { + if (!$this->manuPaused) { + $this->manuPaused = true; + + if (!$this->autoPaused) { + $this->server->pause(); + } + } + } + + public function resume() + { + if ($this->manuPaused) { + $this->manuPaused = false; + + if (!$this->autoPaused) { + $this->server->resume(); + } + } + } + + public function close() + { + $this->server->close(); + } + + /** @internal */ + public function handleConnection(ConnectionInterface $connection) + { + // close connection if limit exceeded + if ($this->limit !== null && count($this->connections) >= $this->limit) { + $this->handleError(new OverflowException('Connection closed because server reached connection limit')); + $connection->close(); + return; + } + + $this->connections[] = $connection; + $that = $this; + $connection->on('close', function () use ($that, $connection) { + $that->handleDisconnection($connection); + }); + + // pause accepting new connections if limit exceeded + if ($this->pauseOnLimit && !$this->autoPaused && count($this->connections) >= $this->limit) { + $this->autoPaused = true; + + if (!$this->manuPaused) { + $this->server->pause(); + } + } + + $this->emit('connection', array($connection)); + } + + /** @internal */ + public function handleDisconnection(ConnectionInterface $connection) + { + unset($this->connections[array_search($connection, $this->connections)]); + + // continue accepting new connection if below limit + if ($this->autoPaused && count($this->connections) < $this->limit) { + $this->autoPaused = false; + + if (!$this->manuPaused) { + $this->server->resume(); + } + } + } + + /** @internal */ + public function handleError(Exception $error) + { + $this->emit('error', array($error)); + } +} diff --git a/assets/php/vendor/react/socket/src/SecureConnector.php b/assets/php/vendor/react/socket/src/SecureConnector.php new file mode 100644 index 0000000..f04183d --- /dev/null +++ b/assets/php/vendor/react/socket/src/SecureConnector.php @@ -0,0 +1,64 @@ +<?php + +namespace React\Socket; + +use React\EventLoop\LoopInterface; +use React\Promise; +use BadMethodCallException; +use InvalidArgumentException; +use UnexpectedValueException; + +final class SecureConnector implements ConnectorInterface +{ + private $connector; + private $streamEncryption; + private $context; + + public function __construct(ConnectorInterface $connector, LoopInterface $loop, array $context = array()) + { + $this->connector = $connector; + $this->streamEncryption = new StreamEncryption($loop, false); + $this->context = $context; + } + + public function connect($uri) + { + if (!function_exists('stream_socket_enable_crypto')) { + return Promise\reject(new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); // @codeCoverageIgnore + } + + if (strpos($uri, '://') === false) { + $uri = 'tls://' . $uri; + } + + $parts = parse_url($uri); + if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') { + return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid')); + } + + $uri = str_replace('tls://', '', $uri); + $context = $this->context; + + $encryption = $this->streamEncryption; + return $this->connector->connect($uri)->then(function (ConnectionInterface $connection) use ($context, $encryption) { + // (unencrypted) TCP/IP connection succeeded + + if (!$connection instanceof Connection) { + $connection->close(); + throw new UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource'); + } + + // set required SSL/TLS context options + foreach ($context as $name => $value) { + stream_context_set_option($connection->stream, 'ssl', $name, $value); + } + + // try to enable encryption + return $encryption->enable($connection)->then(null, function ($error) use ($connection) { + // establishing encryption failed => close invalid connection and return error + $connection->close(); + throw $error; + }); + }); + } +} diff --git a/assets/php/vendor/react/socket/src/SecureServer.php b/assets/php/vendor/react/socket/src/SecureServer.php new file mode 100644 index 0000000..302ae93 --- /dev/null +++ b/assets/php/vendor/react/socket/src/SecureServer.php @@ -0,0 +1,192 @@ +<?php + +namespace React\Socket; + +use Evenement\EventEmitter; +use React\EventLoop\LoopInterface; +use BadMethodCallException; +use UnexpectedValueException; + +/** + * The `SecureServer` class implements the `ServerInterface` and is responsible + * for providing a secure TLS (formerly known as SSL) server. + * + * It does so by wrapping a `TcpServer` instance which waits for plaintext + * TCP/IP connections and then performs a TLS handshake for each connection. + * + * ```php + * $server = new TcpServer(8000, $loop); + * $server = new SecureServer($server, $loop, array( + * // tls context options here… + * )); + * ``` + * + * Whenever a client completes the TLS handshake, it will emit a `connection` event + * with a connection instance implementing [`ConnectionInterface`](#connectioninterface): + * + * ```php + * $server->on('connection', function (ConnectionInterface $connection) { + * echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL; + * + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * Whenever a client fails to perform a successful TLS handshake, it will emit an + * `error` event and then close the underlying TCP/IP connection: + * + * ```php + * $server->on('error', function (Exception $e) { + * echo 'Error' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * See also the `ServerInterface` for more details. + * + * Note that the `SecureServer` class is a concrete implementation for TLS sockets. + * If you want to typehint in your higher-level protocol implementation, you SHOULD + * use the generic `ServerInterface` instead. + * + * @see ServerInterface + * @see ConnectionInterface + */ +final class SecureServer extends EventEmitter implements ServerInterface +{ + private $tcp; + private $encryption; + private $context; + + /** + * Creates a secure TLS server and starts waiting for incoming connections + * + * It does so by wrapping a `TcpServer` instance which waits for plaintext + * TCP/IP connections and then performs a TLS handshake for each connection. + * It thus requires valid [TLS context options], + * which in its most basic form may look something like this if you're using a + * PEM encoded certificate file: + * + * ```php + * $server = new TcpServer(8000, $loop); + * $server = new SecureServer($server, $loop, array( + * 'local_cert' => 'server.pem' + * )); + * ``` + * + * Note that the certificate file will not be loaded on instantiation but when an + * incoming connection initializes its TLS context. + * This implies that any invalid certificate file paths or contents will only cause + * an `error` event at a later time. + * + * If your private key is encrypted with a passphrase, you have to specify it + * like this: + * + * ```php + * $server = new TcpServer(8000, $loop); + * $server = new SecureServer($server, $loop, array( + * 'local_cert' => 'server.pem', + * 'passphrase' => 'secret' + * )); + * ``` + * + * Note that available [TLS context options], + * their defaults and effects of changing these may vary depending on your system + * and/or PHP version. + * Passing unknown context options has no effect. + * + * Advanced usage: Despite allowing any `ServerInterface` as first parameter, + * you SHOULD pass a `TcpServer` instance as first parameter, unless you + * know what you're doing. + * Internally, the `SecureServer` has to set the required TLS context options on + * the underlying stream resources. + * These resources are not exposed through any of the interfaces defined in this + * package, but only through the internal `Connection` class. + * The `TcpServer` class is guaranteed to emit connections that implement + * the `ConnectionInterface` and uses the internal `Connection` class in order to + * expose these underlying resources. + * If you use a custom `ServerInterface` and its `connection` event does not + * meet this requirement, the `SecureServer` will emit an `error` event and + * then close the underlying connection. + * + * @param ServerInterface|TcpServer $tcp + * @param LoopInterface $loop + * @param array $context + * @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support + * @see TcpServer + * @link http://php.net/manual/en/context.ssl.php for TLS context options + */ + public function __construct(ServerInterface $tcp, LoopInterface $loop, array $context) + { + if (!function_exists('stream_socket_enable_crypto')) { + throw new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore + } + + // default to empty passphrase to suppress blocking passphrase prompt + $context += array( + 'passphrase' => '' + ); + + $this->tcp = $tcp; + $this->encryption = new StreamEncryption($loop); + $this->context = $context; + + $that = $this; + $this->tcp->on('connection', function ($connection) use ($that) { + $that->handleConnection($connection); + }); + $this->tcp->on('error', function ($error) use ($that) { + $that->emit('error', array($error)); + }); + } + + public function getAddress() + { + $address = $this->tcp->getAddress(); + if ($address === null) { + return null; + } + + return str_replace('tcp://' , 'tls://', $address); + } + + public function pause() + { + $this->tcp->pause(); + } + + public function resume() + { + $this->tcp->resume(); + } + + public function close() + { + return $this->tcp->close(); + } + + /** @internal */ + public function handleConnection(ConnectionInterface $connection) + { + if (!$connection instanceof Connection) { + $this->emit('error', array(new UnexpectedValueException('Base server does not use internal Connection class exposing stream resource'))); + $connection->end(); + return; + } + + foreach ($this->context as $name => $value) { + stream_context_set_option($connection->stream, 'ssl', $name, $value); + } + + $that = $this; + + $this->encryption->enable($connection)->then( + function ($conn) use ($that) { + $that->emit('connection', array($conn)); + }, + function ($error) use ($that, $connection) { + $that->emit('error', array($error)); + $connection->end(); + } + ); + } +} diff --git a/assets/php/vendor/react/socket/src/Server.php b/assets/php/vendor/react/socket/src/Server.php new file mode 100644 index 0000000..72712e4 --- /dev/null +++ b/assets/php/vendor/react/socket/src/Server.php @@ -0,0 +1,73 @@ +<?php + +namespace React\Socket; + +use Evenement\EventEmitter; +use React\EventLoop\LoopInterface; +use Exception; + +final class Server extends EventEmitter implements ServerInterface +{ + private $server; + + public function __construct($uri, LoopInterface $loop, array $context = array()) + { + // sanitize TCP context options if not properly wrapped + if ($context && (!isset($context['tcp']) && !isset($context['tls']) && !isset($context['unix']))) { + $context = array('tcp' => $context); + } + + // apply default options if not explicitly given + $context += array( + 'tcp' => array(), + 'tls' => array(), + 'unix' => array() + ); + + $scheme = 'tcp'; + $pos = strpos($uri, '://'); + if ($pos !== false) { + $scheme = substr($uri, 0, $pos); + } + + if ($scheme === 'unix') { + $server = new UnixServer($uri, $loop, $context['unix']); + } else { + $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']); + + if ($scheme === 'tls') { + $server = new SecureServer($server, $loop, $context['tls']); + } + } + + $this->server = $server; + + $that = $this; + $server->on('connection', function (ConnectionInterface $conn) use ($that) { + $that->emit('connection', array($conn)); + }); + $server->on('error', function (Exception $error) use ($that) { + $that->emit('error', array($error)); + }); + } + + public function getAddress() + { + return $this->server->getAddress(); + } + + public function pause() + { + $this->server->pause(); + } + + public function resume() + { + $this->server->resume(); + } + + public function close() + { + $this->server->close(); + } +} diff --git a/assets/php/vendor/react/socket/src/ServerInterface.php b/assets/php/vendor/react/socket/src/ServerInterface.php new file mode 100644 index 0000000..5319678 --- /dev/null +++ b/assets/php/vendor/react/socket/src/ServerInterface.php @@ -0,0 +1,151 @@ +<?php + +namespace React\Socket; + +use Evenement\EventEmitterInterface; + +/** + * The `ServerInterface` is responsible for providing an interface for accepting + * incoming streaming connections, such as a normal TCP/IP connection. + * + * Most higher-level components (such as a HTTP server) accept an instance + * implementing this interface to accept incoming streaming connections. + * This is usually done via dependency injection, so it's fairly simple to actually + * swap this implementation against any other implementation of this interface. + * This means that you SHOULD typehint against this interface instead of a concrete + * implementation of this interface. + * + * Besides defining a few methods, this interface also implements the + * `EventEmitterInterface` which allows you to react to certain events: + * + * connection event: + * The `connection` event will be emitted whenever a new connection has been + * established, i.e. a new client connects to this server socket: + * + * ```php + * $server->on('connection', function (ConnectionInterface $connection) { + * echo 'new connection' . PHP_EOL; + * }); + * ``` + * + * See also the `ConnectionInterface` for more details about handling the + * incoming connection. + * + * error event: + * The `error` event will be emitted whenever there's an error accepting a new + * connection from a client. + * + * ```php + * $server->on('error', function (Exception $e) { + * echo 'error: ' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * Note that this is not a fatal error event, i.e. the server keeps listening for + * new connections even after this event. + * + * @see ConnectionInterface + */ +interface ServerInterface extends EventEmitterInterface +{ + /** + * Returns the full address (URI) this server is currently listening on + * + * ```php + * $address = $server->getAddress(); + * echo 'Server listening on ' . $address . PHP_EOL; + * ``` + * + * If the address can not be determined or is unknown at this time (such as + * after the socket has been closed), it MAY return a `NULL` value instead. + * + * Otherwise, it will return the full address (URI) as a string value, such + * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80` or `tls://127.0.0.1:443`. + * Note that individual URI components are application specific and depend + * on the underlying transport protocol. + * + * If this is a TCP/IP based server and you only want the local port, you may + * use something like this: + * + * ```php + * $address = $server->getAddress(); + * $port = parse_url($address, PHP_URL_PORT); + * echo 'Server listening on port ' . $port . PHP_EOL; + * ``` + * + * @return ?string the full listening address (URI) or NULL if it is unknown (not applicable to this server socket or already closed) + */ + public function getAddress(); + + /** + * Pauses accepting new incoming connections. + * + * Removes the socket resource from the EventLoop and thus stop accepting + * new connections. Note that the listening socket stays active and is not + * closed. + * + * This means that new incoming connections will stay pending in the + * operating system backlog until its configurable backlog is filled. + * Once the backlog is filled, the operating system may reject further + * incoming connections until the backlog is drained again by resuming + * to accept new connections. + * + * Once the server is paused, no futher `connection` events SHOULD + * be emitted. + * + * ```php + * $server->pause(); + * + * $server->on('connection', assertShouldNeverCalled()); + * ``` + * + * This method is advisory-only, though generally not recommended, the + * server MAY continue emitting `connection` events. + * + * Unless otherwise noted, a successfully opened server SHOULD NOT start + * in paused state. + * + * You can continue processing events by calling `resume()` again. + * + * Note that both methods can be called any number of times, in particular + * calling `pause()` more than once SHOULD NOT have any effect. + * Similarly, calling this after `close()` is a NO-OP. + * + * @see self::resume() + * @return void + */ + public function pause(); + + /** + * Resumes accepting new incoming connections. + * + * Re-attach the socket resource to the EventLoop after a previous `pause()`. + * + * ```php + * $server->pause(); + * + * $loop->addTimer(1.0, function () use ($server) { + * $server->resume(); + * }); + * ``` + * + * Note that both methods can be called any number of times, in particular + * calling `resume()` without a prior `pause()` SHOULD NOT have any effect. + * Similarly, calling this after `close()` is a NO-OP. + * + * @see self::pause() + * @return void + */ + public function resume(); + + /** + * Shuts down this listening socket + * + * This will stop listening for new incoming connections on this socket. + * + * Calling this method more than once on the same instance is a NO-OP. + * + * @return void + */ + public function close(); +} diff --git a/assets/php/vendor/react/socket/src/StreamEncryption.php b/assets/php/vendor/react/socket/src/StreamEncryption.php new file mode 100644 index 0000000..ba5d472 --- /dev/null +++ b/assets/php/vendor/react/socket/src/StreamEncryption.php @@ -0,0 +1,146 @@ +<?php + +namespace React\Socket; + +use React\EventLoop\LoopInterface; +use React\Promise\Deferred; +use RuntimeException; +use UnexpectedValueException; + +/** + * This class is considered internal and its API should not be relied upon + * outside of Socket. + * + * @internal + */ +class StreamEncryption +{ + private $loop; + private $method; + private $server; + + private $errstr; + private $errno; + + public function __construct(LoopInterface $loop, $server = true) + { + $this->loop = $loop; + $this->server = $server; + + // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3. + // PHP 5.6+ supports bitmasks, legacy PHP only supports predefined + // constants, so apply accordingly below. + // Also, since PHP 5.6.7 up until before PHP 7.2.0 the main constant did + // only support TLSv1.0, so we explicitly apply all versions. + // @link http://php.net/manual/en/migration56.openssl.php#migration56.openssl.crypto-method + // @link https://3v4l.org/plbFn + if ($server) { + $this->method = STREAM_CRYPTO_METHOD_TLS_SERVER; + + if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_SERVER; + } + if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_SERVER; + } + if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; + } + } else { + $this->method = STREAM_CRYPTO_METHOD_TLS_CLIENT; + + if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT; + } + if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + } + if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + } + } + } + + public function enable(Connection $stream) + { + return $this->toggle($stream, true); + } + + public function disable(Connection $stream) + { + return $this->toggle($stream, false); + } + + public function toggle(Connection $stream, $toggle) + { + // pause actual stream instance to continue operation on raw stream socket + $stream->pause(); + + // TODO: add write() event to make sure we're not sending any excessive data + + $deferred = new Deferred(function ($_, $reject) use ($toggle) { + // cancelling this leaves this stream in an inconsistent state… + $reject(new RuntimeException('Cancelled toggling encryption ' . $toggle ? 'on' : 'off')); + }); + + // get actual stream socket from stream instance + $socket = $stream->stream; + + // get crypto method from context options or use global setting from constructor + $method = $this->method; + $context = stream_context_get_options($socket); + if (isset($context['ssl']['crypto_method'])) { + $method = $context['ssl']['crypto_method']; + } + + $that = $this; + $toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) { + $that->toggleCrypto($socket, $deferred, $toggle, $method); + }; + + $this->loop->addReadStream($socket, $toggleCrypto); + + if (!$this->server) { + $toggleCrypto(); + } + + $loop = $this->loop; + + return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) { + $loop->removeReadStream($socket); + + $stream->encryptionEnabled = $toggle; + $stream->resume(); + + return $stream; + }, function($error) use ($stream, $socket, $loop) { + $loop->removeReadStream($socket); + $stream->resume(); + throw $error; + }); + } + + public function toggleCrypto($socket, Deferred $deferred, $toggle, $method) + { + set_error_handler(array($this, 'handleError')); + $result = stream_socket_enable_crypto($socket, $toggle, $method); + restore_error_handler(); + + if (true === $result) { + $deferred->resolve(); + } else if (false === $result) { + $deferred->reject(new UnexpectedValueException( + sprintf("Unable to complete SSL/TLS handshake: %s", $this->errstr), + $this->errno + )); + } else { + // need more data, will retry + } + } + + public function handleError($errno, $errstr) + { + $this->errstr = str_replace(array("\r", "\n"), ' ', $errstr); + $this->errno = $errno; + } +} diff --git a/assets/php/vendor/react/socket/src/TcpConnector.php b/assets/php/vendor/react/socket/src/TcpConnector.php new file mode 100644 index 0000000..90d7df1 --- /dev/null +++ b/assets/php/vendor/react/socket/src/TcpConnector.php @@ -0,0 +1,122 @@ +<?php + +namespace React\Socket; + +use React\EventLoop\LoopInterface; +use React\Promise; +use InvalidArgumentException; +use RuntimeException; + +final class TcpConnector implements ConnectorInterface +{ + private $loop; + private $context; + + public function __construct(LoopInterface $loop, array $context = array()) + { + $this->loop = $loop; + $this->context = $context; + } + + 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('Given URI "' . $uri . '" is invalid')); + } + + $ip = trim($parts['host'], '[]'); + if (false === filter_var($ip, FILTER_VALIDATE_IP)) { + return Promise\reject(new InvalidArgumentException('Given URI "' . $ip . '" does not contain a valid host IP')); + } + + // use context given in constructor + $context = array( + 'socket' => $this->context + ); + + // parse arguments from query component of URI + $args = array(); + if (isset($parts['query'])) { + parse_str($parts['query'], $args); + } + + // If an original hostname has been given, use this for TLS setup. + // This can happen due to layers of nested connectors, such as a + // DnsConnector reporting its original hostname. + // These context options are here in case TLS is enabled later on this stream. + // If TLS is not enabled later, this doesn't hurt either. + if (isset($args['hostname'])) { + $context['ssl'] = array( + 'SNI_enabled' => true, + 'peer_name' => $args['hostname'] + ); + + // Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead. + // The SNI_server_name context option has to be set here during construction, + // as legacy PHP ignores any values set later. + if (PHP_VERSION_ID < 50600) { + $context['ssl'] += array( + 'SNI_server_name' => $args['hostname'], + 'CN_match' => $args['hostname'] + ); + } + } + + // latest versions of PHP no longer accept any other URI components and + // HHVM fails to parse URIs with a query but no path, so let's simplify our URI here + $remote = 'tcp://' . $parts['host'] . ':' . $parts['port']; + + $socket = @stream_socket_client( + $remote, + $errno, + $errstr, + 0, + STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT, + stream_context_create($context) + ); + + if (false === $socket) { + return Promise\reject(new RuntimeException( + sprintf("Connection to %s failed: %s", $uri, $errstr), + $errno + )); + } + + stream_set_blocking($socket, 0); + + // wait for connection + + return $this->waitForStreamOnce($socket); + } + + private function waitForStreamOnce($stream) + { + $loop = $this->loop; + + return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream) { + $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject) { + $loop->removeWriteStream($stream); + + // The following hack looks like the only way to + // detect connection refused errors with PHP's stream sockets. + if (false === stream_socket_get_name($stream, true)) { + fclose($stream); + + $reject(new RuntimeException('Connection refused')); + } else { + $resolve(new Connection($stream, $loop)); + } + }); + }, function () use ($loop, $stream) { + $loop->removeWriteStream($stream); + fclose($stream); + + throw new RuntimeException('Cancelled while waiting for TCP/IP connection to be established'); + }); + } +} diff --git a/assets/php/vendor/react/socket/src/TcpServer.php b/assets/php/vendor/react/socket/src/TcpServer.php new file mode 100644 index 0000000..119e177 --- /dev/null +++ b/assets/php/vendor/react/socket/src/TcpServer.php @@ -0,0 +1,236 @@ +<?php + +namespace React\Socket; + +use Evenement\EventEmitter; +use React\EventLoop\LoopInterface; +use InvalidArgumentException; +use RuntimeException; + +/** + * The `TcpServer` class implements the `ServerInterface` and + * is responsible for accepting plaintext TCP/IP connections. + * + * ```php + * $server = new TcpServer(8080, $loop); + * ``` + * + * Whenever a client connects, it will emit a `connection` event with a connection + * instance implementing `ConnectionInterface`: + * + * ```php + * $server->on('connection', function (ConnectionInterface $connection) { + * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL; + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * See also the `ServerInterface` for more details. + * + * @see ServerInterface + * @see ConnectionInterface + */ +final class TcpServer extends EventEmitter implements ServerInterface +{ + private $master; + private $loop; + private $listening = false; + + /** + * Creates a plaintext TCP/IP socket server and starts listening on the given address + * + * This starts accepting new incoming connections on the given address. + * See also the `connection event` documented in the `ServerInterface` + * for more details. + * + * ```php + * $server = new TcpServer(8080, $loop); + * ``` + * + * As above, the `$uri` parameter can consist of only a port, in which case the + * server will default to listening on the localhost address `127.0.0.1`, + * which means it will not be reachable from outside of this system. + * + * In order to use a random port assignment, you can use the port `0`: + * + * ```php + * $server = new TcpServer(0, $loop); + * $address = $server->getAddress(); + * ``` + * + * In order to change the host the socket is listening on, you can provide an IP + * address through the first parameter provided to the constructor, optionally + * preceded by the `tcp://` scheme: + * + * ```php + * $server = new TcpServer('192.168.0.1:8080', $loop); + * ``` + * + * If you want to listen on an IPv6 address, you MUST enclose the host in square + * brackets: + * + * ```php + * $server = new TcpServer('[::1]:8080', $loop); + * ``` + * + * If the given URI is invalid, does not contain a port, any other scheme or if it + * contains a hostname, it will throw an `InvalidArgumentException`: + * + * ```php + * // throws InvalidArgumentException due to missing port + * $server = new TcpServer('127.0.0.1', $loop); + * ``` + * + * If the given URI appears to be valid, but listening on it fails (such as if port + * is already in use or port below 1024 may require root access etc.), it will + * throw a `RuntimeException`: + * + * ```php + * $first = new TcpServer(8080, $loop); + * + * // throws RuntimeException because port is already in use + * $second = new TcpServer(8080, $loop); + * ``` + * + * Note that these error conditions may vary depending on your system and/or + * configuration. + * See the exception message and code for more details about the actual error + * condition. + * + * Optionally, you can specify [socket context options](http://php.net/manual/en/context.socket.php) + * for the underlying stream socket resource like this: + * + * ```php + * $server = new TcpServer('[::1]:8080', $loop, array( + * 'backlog' => 200, + * 'so_reuseport' => true, + * 'ipv6_v6only' => true + * )); + * ``` + * + * Note that available [socket context options](http://php.net/manual/en/context.socket.php), + * their defaults and effects of changing these may vary depending on your system + * and/or PHP version. + * Passing unknown context options has no effect. + * + * @param string|int $uri + * @param LoopInterface $loop + * @param array $context + * @throws InvalidArgumentException if the listening address is invalid + * @throws RuntimeException if listening on this address fails (already in use etc.) + */ + public function __construct($uri, LoopInterface $loop, array $context = array()) + { + $this->loop = $loop; + + // a single port has been given => assume localhost + if ((string)(int)$uri === (string)$uri) { + $uri = '127.0.0.1:' . $uri; + } + + // assume default scheme if none has been given + if (strpos($uri, '://') === false) { + $uri = 'tcp://' . $uri; + } + + // parse_url() does not accept null ports (random port assignment) => manually remove + if (substr($uri, -2) === ':0') { + $parts = parse_url(substr($uri, 0, -2)); + if ($parts) { + $parts['port'] = 0; + } + } else { + $parts = parse_url($uri); + } + + // ensure URI contains TCP scheme, host and port + if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') { + throw new InvalidArgumentException('Invalid URI "' . $uri . '" given'); + } + + if (false === filter_var(trim($parts['host'], '[]'), FILTER_VALIDATE_IP)) { + throw new InvalidArgumentException('Given URI "' . $uri . '" does not contain a valid host IP'); + } + + $this->master = @stream_socket_server( + $uri, + $errno, + $errstr, + STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, + stream_context_create(array('socket' => $context)) + ); + if (false === $this->master) { + throw new RuntimeException('Failed to listen on "' . $uri . '": ' . $errstr, $errno); + } + stream_set_blocking($this->master, 0); + + $this->resume(); + } + + public function getAddress() + { + if (!is_resource($this->master)) { + return null; + } + + $address = stream_socket_get_name($this->master, false); + + // check if this is an IPv6 address which includes multiple colons but no square brackets + $pos = strrpos($address, ':'); + if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') { + $port = substr($address, $pos + 1); + $address = '[' . substr($address, 0, $pos) . ']:' . $port; + } + + return 'tcp://' . $address; + } + + public function pause() + { + if (!$this->listening) { + return; + } + + $this->loop->removeReadStream($this->master); + $this->listening = false; + } + + public function resume() + { + if ($this->listening || !is_resource($this->master)) { + return; + } + + $that = $this; + $this->loop->addReadStream($this->master, function ($master) use ($that) { + $newSocket = @stream_socket_accept($master); + if (false === $newSocket) { + $that->emit('error', array(new RuntimeException('Error accepting new connection'))); + + return; + } + $that->handleConnection($newSocket); + }); + $this->listening = true; + } + + public function close() + { + if (!is_resource($this->master)) { + return; + } + + $this->pause(); + fclose($this->master); + $this->removeAllListeners(); + } + + /** @internal */ + public function handleConnection($socket) + { + $this->emit('connection', array( + new Connection($socket, $this->loop) + )); + } +} diff --git a/assets/php/vendor/react/socket/src/TimeoutConnector.php b/assets/php/vendor/react/socket/src/TimeoutConnector.php new file mode 100644 index 0000000..d4eba2e --- /dev/null +++ b/assets/php/vendor/react/socket/src/TimeoutConnector.php @@ -0,0 +1,25 @@ +<?php + +namespace React\Socket; + +use React\EventLoop\LoopInterface; +use React\Promise\Timer; + +final class TimeoutConnector implements ConnectorInterface +{ + private $connector; + private $timeout; + private $loop; + + public function __construct(ConnectorInterface $connector, $timeout, LoopInterface $loop) + { + $this->connector = $connector; + $this->timeout = $timeout; + $this->loop = $loop; + } + + public function connect($uri) + { + return Timer\timeout($this->connector->connect($uri), $this->timeout, $this->loop); + } +} diff --git a/assets/php/vendor/react/socket/src/UnixConnector.php b/assets/php/vendor/react/socket/src/UnixConnector.php new file mode 100644 index 0000000..9b84ab0 --- /dev/null +++ b/assets/php/vendor/react/socket/src/UnixConnector.php @@ -0,0 +1,44 @@ +<?php + +namespace React\Socket; + +use React\EventLoop\LoopInterface; +use React\Promise; +use InvalidArgumentException; +use RuntimeException; + +/** + * Unix domain socket connector + * + * Unix domain sockets use atomic operations, so we can as well emulate + * async behavior. + */ +final class UnixConnector implements ConnectorInterface +{ + private $loop; + + public function __construct(LoopInterface $loop) + { + $this->loop = $loop; + } + + public function connect($path) + { + if (strpos($path, '://') === false) { + $path = 'unix://' . $path; + } elseif (substr($path, 0, 7) !== 'unix://') { + return Promise\reject(new InvalidArgumentException('Given URI "' . $path . '" is invalid')); + } + + $resource = @stream_socket_client($path, $errno, $errstr, 1.0); + + if (!$resource) { + return Promise\reject(new RuntimeException('Unable to connect to unix domain socket "' . $path . '": ' . $errstr, $errno)); + } + + $connection = new Connection($resource, $this->loop); + $connection->unix = true; + + return Promise\resolve($connection); + } +} diff --git a/assets/php/vendor/react/socket/src/UnixServer.php b/assets/php/vendor/react/socket/src/UnixServer.php new file mode 100644 index 0000000..8f1ed98 --- /dev/null +++ b/assets/php/vendor/react/socket/src/UnixServer.php @@ -0,0 +1,130 @@ +<?php + +namespace React\Socket; + +use Evenement\EventEmitter; +use React\EventLoop\LoopInterface; +use InvalidArgumentException; +use RuntimeException; + +/** + * The `UnixServer` class implements the `ServerInterface` and + * is responsible for accepting plaintext connections on unix domain sockets. + * + * ```php + * $server = new UnixServer('unix:///tmp/app.sock', $loop); + * ``` + * + * See also the `ServerInterface` for more details. + * + * @see ServerInterface + * @see ConnectionInterface + */ +final class UnixServer extends EventEmitter implements ServerInterface +{ + private $master; + private $loop; + private $listening = false; + + /** + * Creates a plaintext socket server and starts listening on the given unix socket + * + * This starts accepting new incoming connections on the given address. + * See also the `connection event` documented in the `ServerInterface` + * for more details. + * + * ```php + * $server = new UnixServer('unix:///tmp/app.sock', $loop); + * ``` + * + * @param string $path + * @param LoopInterface $loop + * @param array $context + * @throws InvalidArgumentException if the listening address is invalid + * @throws RuntimeException if listening on this address fails (already in use etc.) + */ + public function __construct($path, LoopInterface $loop, array $context = array()) + { + $this->loop = $loop; + + if (strpos($path, '://') === false) { + $path = 'unix://' . $path; + } elseif (substr($path, 0, 7) !== 'unix://') { + throw new InvalidArgumentException('Given URI "' . $path . '" is invalid'); + } + + $this->master = @stream_socket_server( + $path, + $errno, + $errstr, + STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, + stream_context_create(array('socket' => $context)) + ); + if (false === $this->master) { + throw new RuntimeException('Failed to listen on unix domain socket "' . $path . '": ' . $errstr, $errno); + } + stream_set_blocking($this->master, 0); + + $this->resume(); + } + + public function getAddress() + { + if (!is_resource($this->master)) { + return null; + } + + return 'unix://' . stream_socket_get_name($this->master, false); + } + + public function pause() + { + if (!$this->listening) { + return; + } + + $this->loop->removeReadStream($this->master); + $this->listening = false; + } + + public function resume() + { + if ($this->listening || !is_resource($this->master)) { + return; + } + + $that = $this; + $this->loop->addReadStream($this->master, function ($master) use ($that) { + $newSocket = @stream_socket_accept($master); + if (false === $newSocket) { + $that->emit('error', array(new RuntimeException('Error accepting new connection'))); + + return; + } + $that->handleConnection($newSocket); + }); + $this->listening = true; + } + + public function close() + { + if (!is_resource($this->master)) { + return; + } + + $this->pause(); + fclose($this->master); + $this->removeAllListeners(); + } + + /** @internal */ + public function handleConnection($socket) + { + $connection = new Connection($socket, $this->loop); + $connection->unix = true; + + $this->emit('connection', array( + $connection + )); + } +} |