diff options
Diffstat (limited to 'vendor/clue/socket-raw')
-rw-r--r-- | vendor/clue/socket-raw/CHANGELOG.md | 96 | ||||
-rw-r--r-- | vendor/clue/socket-raw/LICENSE | 21 | ||||
-rw-r--r-- | vendor/clue/socket-raw/README.md | 258 | ||||
-rw-r--r-- | vendor/clue/socket-raw/composer.json | 23 | ||||
-rw-r--r-- | vendor/clue/socket-raw/src/Exception.php | 91 | ||||
-rw-r--r-- | vendor/clue/socket-raw/src/Factory.php | 282 | ||||
-rw-r--r-- | vendor/clue/socket-raw/src/Socket.php | 562 |
7 files changed, 1333 insertions, 0 deletions
diff --git a/vendor/clue/socket-raw/CHANGELOG.md b/vendor/clue/socket-raw/CHANGELOG.md new file mode 100644 index 0000000..3a21b1b --- /dev/null +++ b/vendor/clue/socket-raw/CHANGELOG.md @@ -0,0 +1,96 @@ +# Changelog + +## 1.6.0 (2022-04-14) + +* Feature: Forward compatibility with PHP 8.1 release. + (#67 and #68 by @clue) + +* Fix: Fix reporting refused connections on Windows. + (#69 by @clue) + +* Improve CI setup and documentation. + (#70 and #65 by @clue, #64 by @szepeviktor and #66 by @PaulRotmann) + +## 1.5.0 (2020-11-27) + +* Feature: Support PHP 8 and drop legacy HHVM support. + (#60 and #61 by @clue) + +* Improve test suite and add `.gitattributes` to exclude dev files from export. + Update to PHPUnit 9 and simplify test matrix. + (#50, #51, #58 and #63 by @clue and #57 by @SimonFrings) + +## 1.4.1 (2019-10-28) + +* Fix: Fix error reporting when invoking methods on closed socket instance. + (#48 by @clue) + +* Improve test suite to run tests on Windows via Travis CI. + (#49 by @clue) + +## 1.4.0 (2019-01-22) + +* Feature: Improve Windows support (async connections and Unix domain sockets). + (#43 by @clue) + +* Improve test suite by adding forward compatibility with PHPUnit 7 and PHPUnit 6. + (#42 by @clue) + +## 1.3.0 (2018-06-10) + +* Feature: Add `$timeout` parameter for `Factory::createClient()` + (#39 by @Elbandi and @clue) + + ```php + // connect to Google, but wait no longer than 2.5s for connection + $socket = $factory->createClient('www.google.com:80', 2.5); + ``` + +* Improve test suite by adding PHPUnit to require-dev, + update test suite to test against legacy PHP 5.3 through PHP 7.2 and + optionally skip functional integration tests requiring internet. + (#26 by @ascii-soup, #28, #29, #37 and #38 by @clue) + +## 1.2.0 (2015-03-18) + +* Feature: Expose optional `$type` parameter for `Socket::read()` + ([#16](https://github.com/clue/php-socket-raw/pull/16) by @Elbandi) + +## 1.1.0 (2014-10-24) + +* Feature: Accept float timeouts like `0.5` for `Socket::selectRead()` and `Socket::selectWrite()`. + ([#8](https://github.com/clue/php-socket-raw/issues/8)) + +* Feature: Add new `Socket::connectTimeout()` method. + ([#11](https://github.com/clue/php-socket-raw/pull/11)) + +* Fix: Close invalid socket resource when `Factory` fails to create a `Socket`. + ([#12](https://github.com/clue/php-socket-raw/pull/12)) + +* Fix: Calling `accept()` on an idle server socket emits right error code and message. + ([#14](https://github.com/clue/php-socket-raw/pull/14)) + +## 1.0.0 (2014-05-10) + +* Feature: Improved errors reporting through dedicated `Exception` + ([#6](https://github.com/clue/socket-raw/pull/6)) +* Feature: Support HHVM + ([#5](https://github.com/clue/socket-raw/pull/5)) +* Use PSR-4 layout + ([#3](https://github.com/clue/socket-raw/pull/3)) +* Continuous integration via Travis CI + +## 0.1.2 (2013-05-09) + +* Fix: The `Factory::createUdg()` now returns the right socket type. +* Fix: Fix ICMPv6 addressing to not require square brackets because it does not + use ports. +* Extended test suite. + +## 0.1.1 (2013-04-18) + +* Fix: Raw sockets now correctly report no port instead of a `0` port. + +## 0.1.0 (2013-04-10) + +* First tagged release diff --git a/vendor/clue/socket-raw/LICENSE b/vendor/clue/socket-raw/LICENSE new file mode 100644 index 0000000..da15612 --- /dev/null +++ b/vendor/clue/socket-raw/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 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/socket-raw/README.md b/vendor/clue/socket-raw/README.md new file mode 100644 index 0000000..7ea79b3 --- /dev/null +++ b/vendor/clue/socket-raw/README.md @@ -0,0 +1,258 @@ +# clue/socket-raw + +[![CI status](https://github.com/clue/socket-raw/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/socket-raw/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/clue/socket-raw?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/socket-raw) + +Simple and lightweight OOP wrapper for PHP's low-level sockets extension (ext-sockets). + +PHP offers two networking APIs, the newer [streams API](https://www.php.net/manual/en/book.stream.php) and the older [socket API](https://www.php.net/manual/en/ref.sockets.php). +While the former has been a huge step forward in generalizing various streaming resources, +it lacks some of the advanced features of the original and much more low-level socket API. +This lightweight library exposes this socket API in a modern way by providing a thin wrapper around the underlying API. + +* **Full socket API** - + It exposes the whole [socket API](https://www.php.net/manual/en/ref.sockets.php) through a *sane* object-oriented interface. + Provides convenience methods for common operations as well as exposing all underlying methods and options. +* **Fluent interface** - + Uses a fluent interface so you can easily chain method calls. + Error conditions will be signalled using `Exception`s instead of relying on cumbersome return codes. +* **Lightweight, SOLID design** - + Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough) + and does not get in your way. + This library is merely a very thin wrapper and has no other external dependencies. +* **Good test coverage** - + Comes with an automated test suite and is regularly tested in the *real world*. + +**Table of contents** + +* [Support us](#support-us) +* [Quickstart example](#quickstart-example) +* [Usage](#usage) + * [Factory](#factory) + * [createClient()](#createclient) + * [createServer()](#createserver) + * [create*()](#create) + * [Socket](#socket) + * [Methods](#methods) + * [Data I/O](#data-io) + * [Unconnected I/O](#unconnected-io) + * [Non-blocking (async) I/O](#non-blocking-async-io) + * [Connection handling](#connection-handling) +* [Install](#install) +* [Tests](#tests) +* [License](#license) + +## Support us + +We invest a lot of time developing, maintaining and updating our awesome +open-source projects. You can help us sustain this high-quality of our work by +[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get +numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue) +for details. + +Let's take these projects to the next level together! 🚀 + +## Quickstart example + +Once [installed](#install), you can use the following example to send and receive HTTP messages: + +```php +$factory = new \Socket\Raw\Factory(); + +$socket = $factory->createClient('www.google.com:80'); +echo 'Connected to ' . $socket->getPeerName() . PHP_EOL; + +// send simple HTTP request to remote side +$socket->write("GET / HTTP/1.1\r\n\Host: www.google.com\r\n\r\n"); + +// receive and dump HTTP response +var_dump($socket->read(8192)); + +$socket->close(); +``` + +See also the [examples](examples). + +## Usage + +### Factory + +As shown in the [quickstart example](#quickstart-example), this library uses a `Factory` pattern +as a simple API to [`socket_create()`](https://www.php.net/manual/en/function.socket-create.php). +It provides simple access to creating TCP, UDP, UNIX, UDG and ICMP protocol sockets and supports both IPv4 and IPv6 addressing. + +```php +$factory = new \Socket\Raw\Factory(); +``` + +#### createClient() + +The `createClient(string $address, null|float $timeout): Socket` method is +the most convenient method for creating connected client sockets +(similar to how [`fsockopen()`](https://www.php.net/manual/en/function.fsockopen.php) or +[`stream_socket_client()`](https://www.php.net/manual/en/function.stream-socket-client.php) work). + +```php +// establish a TCP/IP stream connection socket to www.google.com on port 80 +$socket = $factory->createClient('tcp://www.google.com:80'); + +// same as above, as scheme defaults to TCP +$socket = $factory->createClient('www.google.com:80'); + +// same as above, but wait no longer than 2.5s for connection +$socket = $factory->createClient('www.google.com:80', 2.5); + +// create connectionless UDP/IP datagram socket connected to google's DNS +$socket = $factory->createClient('udp://8.8.8.8:53'); + +// establish TCP/IPv6 stream connection socket to localhost on port 1337 +$socket = $factory->createClient('tcp://[::1]:1337'); + +// connect to local Unix stream socket path +$socket = $factory->createClient('unix:///tmp/daemon.sock'); + +// create Unix datagram socket +$socket = $factory->createClient('udg:///tmp/udg.socket'); + +// create a raw low-level ICMP socket (requires root!) +$socket = $factory->createClient('icmp://192.168.0.1'); +``` + +#### createServer() + +The `createServer($address)` method can be used to create a server side (listening) socket bound to specific address/path +(similar to how [`stream_socket_server()`](https://www.php.net/manual/en/function.stream-socket-server.php) works). +It accepts the same addressing scheme as the [`createClient()`](#createclient) method. + +```php +// create a TCP/IP stream connection socket server on port 1337 +$socket = $factory->createServer('tcp://localhost:1337'); + +// create a UDP/IPv6 datagram socket server on port 1337 +$socket = $factory->createServer('udp://[::1]:1337'); +``` + +#### create*() + +Less commonly used, the `Factory` provides access to creating (unconnected) sockets for various socket types: + +```php +$socket = $factory->createTcp4(); +$socket = $factory->createTcp6(); + +$socket = $factory->createUdp4(); +$socket = $factory->createUdp6(); + +$socket = $factory->createUnix(); +$socket = $factory->createUdg(); + +$socket = $factory->createIcmp4(); +$socket = $factory->createIcmp6(); +``` + +You can also create arbitrary socket protocol types through the underlying mechanism: + +```php +$factory->create($family, $type, $protocol); +``` + +### Socket + +As discussed above, the `Socket` class is merely an object-oriented wrapper around a socket resource. As such, it helps if you're familar with socket programming in general. + +The recommended way to create a `Socket` instance is via the above [`Factory`](#factory). + +#### Methods + +All low-level socket operations are available as methods on the `Socket` class. + +You can refer to PHP's fairly good [socket API documentation](https://www.php.net/manual/en/ref.sockets.php) or the docblock comments in the [`Socket` class](src/Socket.php) to get you started. + +##### Data I/O: + +``` +$socket->write('data'); +$data = $socket->read(8192); +``` + +##### Unconnected I/O: + +``` +$socket->sendTo('data', $flags, $remote); +$data = $socket->rcvFrom(8192, $flags, $remote); +``` + +##### Non-blocking (async) I/O: + +``` +$socket->setBlocking(false); +$socket->selectRead(); +$socket->selectWrite(); +``` + +##### Connection handling: + +```php +$client = $socket->accept(); +$socket->bind($address); +$socket->connect($address); +$socket->shutdown(); +$socket->close(); +``` + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org/). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This project follows [SemVer](https://semver.org/). +This will install the latest supported version: + +```bash +$ composer require clue/socket-raw:^1.6 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions besides `ext-sockets` and supports running on legacy PHP 5.3 through +current PHP 8+. +It's *highly recommended to use the latest supported PHP version* for this project. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org/): + +```bash +$ composer install +``` + +To run the test suite, go to the project root and run: + +```bash +$ vendor/bin/phpunit +``` + +Note that the test suite contains tests for ICMP sockets which require root +access on Unix/Linux systems. Therefor some tests will be skipped unless you run +the following command to execute the full test suite: + +```bash +$ sudo vendor/bin/phpunit +``` + +The test suite also contains a number of functional integration tests that rely +on a stable internet connection. +If you do not want to run these, they can simply be skipped like this: + +```bash +$ vendor/bin/phpunit --exclude-group internet +``` + +## License + +This project is released under the permissive [MIT license](LICENSE). + +> Did you know that I offer custom development services and issuing invoices for + sponsorships of releases and for contributions? Contact me (@clue) for details. diff --git a/vendor/clue/socket-raw/composer.json b/vendor/clue/socket-raw/composer.json new file mode 100644 index 0000000..2add27e --- /dev/null +++ b/vendor/clue/socket-raw/composer.json @@ -0,0 +1,23 @@ +{ + "name": "clue/socket-raw", + "description": "Simple and lightweight OOP wrapper for PHP's low-level sockets extension (ext-sockets).", + "keywords": ["socket", "stream", "datagram", "dgram", "client", "server", "ipv6", "tcp", "udp", "icmp", "unix", "udg"], + "homepage": "https://github.com/clue/socket-raw", + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "autoload": { + "psr-4": {"Socket\\Raw\\": "src"} + }, + "require": { + "ext-sockets": "*", + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + } +} diff --git a/vendor/clue/socket-raw/src/Exception.php b/vendor/clue/socket-raw/src/Exception.php new file mode 100644 index 0000000..bdabf78 --- /dev/null +++ b/vendor/clue/socket-raw/src/Exception.php @@ -0,0 +1,91 @@ +<?php + +namespace Socket\Raw; + +use RuntimeException; + +class Exception extends RuntimeException +{ + /** + * Create an Exception after a socket operation on the given $resource failed + * + * @param \Socket|resource $resource + * @param string $messagePrefix + * @return self + * @uses socket_last_error() to get last socket error code + * @uses socket_clear_error() to clear socket error code + * @uses self::createFromCode() to automatically construct exception with full error message + */ + public static function createFromSocketResource($resource, $messagePrefix = 'Socket operation failed') + { + if (PHP_VERSION_ID >= 80000) { + try { + $code = socket_last_error($resource); + } catch (\Error $e) { + $code = SOCKET_ENOTSOCK; + } + } elseif (is_resource($resource)) { + $code = socket_last_error($resource); + socket_clear_error($resource); + } else { + // socket already closed, return fixed error code instead of operating on invalid handle + $code = SOCKET_ENOTSOCK; + } + + return self::createFromCode($code, $messagePrefix); + } + + /** + * Create an Exception after a global socket operation failed (like socket creation) + * + * @param string $messagePrefix + * @return self + * @uses socket_last_error() to get last global error code + * @uses socket_clear_error() to clear global error code + * @uses self::createFromCode() to automatically construct exception with full error message + */ + public static function createFromGlobalSocketOperation($messagePrefix = 'Socket operation failed') + { + $code = socket_last_error(); + socket_clear_error(); + + return self::createFromCode($code, $messagePrefix); + } + + /** + * Create an Exception for given error $code + * + * @param int $code + * @param string $messagePrefix + * @return self + * @throws Exception if given $val is boolean false + * @uses self::getErrorMessage() to translate error code to error message + */ + public static function createFromCode($code, $messagePrefix = 'Socket error') + { + return new self($messagePrefix . ': ' . self::getErrorMessage($code), $code); + } + + /** + * get error message for given error code + * + * @param int $code error code + * @return string + * @uses socket_strerror() to translate error code to error message + * @uses get_defined_constants() to check for related error constant + */ + protected static function getErrorMessage($code) + { + $string = socket_strerror($code); + + // search constant starting with SOCKET_ for this error code + foreach (get_defined_constants() as $key => $value) { + if($value === $code && strpos($key, 'SOCKET_') === 0) { + $string .= ' (' . $key . ')'; + break; + } + } + + return $string; + } +} diff --git a/vendor/clue/socket-raw/src/Factory.php b/vendor/clue/socket-raw/src/Factory.php new file mode 100644 index 0000000..a5068f9 --- /dev/null +++ b/vendor/clue/socket-raw/src/Factory.php @@ -0,0 +1,282 @@ +<?php + +namespace Socket\Raw; + +use \InvalidArgumentException; + +class Factory +{ + /** + * create client socket connected to given target address + * + * @param string $address target address to connect to + * @param null|float $timeout connection timeout (in seconds), default null = no limit + * @return \Socket\Raw\Socket + * @throws InvalidArgumentException if given address is invalid + * @throws Exception on error + * @uses self::createFromString() + * @uses Socket::connect() + * @uses Socket::connectTimeout() + */ + public function createClient($address, $timeout = null) + { + $socket = $this->createFromString($address, $scheme); + + try { + if ($timeout === null) { + $socket->connect($address); + } else { + // connectTimeout enables non-blocking mode, so turn blocking on again + $socket->connectTimeout($address, $timeout); + $socket->setBlocking(true); + } + } catch (Exception $e) { + $socket->close(); + throw $e; + } + + return $socket; + } + + /** + * create server socket bound to given address (and start listening for streaming clients to connect to this stream socket) + * + * @param string $address address to bind socket to + * @return \Socket\Raw\Socket + * @throws Exception on error + * @uses self::createFromString() + * @uses Socket::bind() + * @uses Socket::listen() only for stream sockets (TCP/UNIX) + */ + public function createServer($address) + { + $socket = $this->createFromString($address, $scheme); + + try { + $socket->bind($address); + + if ($socket->getType() === SOCK_STREAM) { + $socket->listen(); + } + } catch (Exception $e) { + $socket->close(); + throw $e; + } + + return $socket; + } + + /** + * create TCP/IPv4 stream socket + * + * @return \Socket\Raw\Socket + * @throws Exception on error + * @uses self::create() + */ + public function createTcp4() + { + return $this->create(AF_INET, SOCK_STREAM, SOL_TCP); + } + + /** + * create TCP/IPv6 stream socket + * + * @return \Socket\Raw\Socket + * @throws Exception on error + * @uses self::create() + */ + public function createTcp6() + { + return $this->create(AF_INET6, SOCK_STREAM, SOL_TCP); + } + + /** + * create UDP/IPv4 datagram socket + * + * @return \Socket\Raw\Socket + * @throws Exception on error + * @uses self::create() + */ + public function createUdp4() + { + return $this->create(AF_INET, SOCK_DGRAM, SOL_UDP); + } + + /** + * create UDP/IPv6 datagram socket + * + * @return \Socket\Raw\Socket + * @throws Exception on error + * @uses self::create() + */ + public function createUdp6() + { + return $this->create(AF_INET6, SOCK_DGRAM, SOL_UDP); + } + + /** + * create local UNIX stream socket + * + * @return \Socket\Raw\Socket + * @throws Exception on error + * @uses self::create() + */ + public function createUnix() + { + return $this->create(AF_UNIX, SOCK_STREAM, 0); + } + + /** + * create local UNIX datagram socket (UDG) + * + * @return \Socket\Raw\Socket + * @throws Exception on error + * @uses self::create() + */ + public function createUdg() + { + return $this->create(AF_UNIX, SOCK_DGRAM, 0); + } + + /** + * create raw ICMP/IPv4 datagram socket (requires root!) + * + * @return \Socket\Raw\Socket + * @throws Exception on error + * @uses self::create() + */ + public function createIcmp4() + { + return $this->create(AF_INET, SOCK_RAW, getprotobyname('icmp')); + } + + /** + * create raw ICMPv6 (IPv6) datagram socket (requires root!) + * + * @return \Socket\Raw\Socket + * @throws Exception on error + * @uses self::create() + */ + public function createIcmp6() + { + return $this->create(AF_INET6, SOCK_RAW, 58 /*getprotobyname('icmp')*/); + } + + /** + * create low level socket with given arguments + * + * @param int $domain + * @param int $type + * @param int $protocol + * @return \Socket\Raw\Socket + * @throws Exception if creating socket fails + * @throws \Error PHP 8 only: throws \Error when arguments are invalid + * @uses socket_create() + */ + public function create($domain, $type, $protocol) + { + $sock = @socket_create($domain, $type, $protocol); + if ($sock === false) { + throw Exception::createFromGlobalSocketOperation('Unable to create socket'); + } + return new Socket($sock); + } + + /** + * create a pair of indistinguishable sockets (commonly used in IPC) + * + * @param int $domain + * @param int $type + * @param int $protocol + * @return \Socket\Raw\Socket[] + * @throws Exception if creating pair of sockets fails + * @throws \Error PHP 8 only: throws \Error when arguments are invalid + * @uses socket_create_pair() + */ + public function createPair($domain, $type, $protocol) + { + $ret = @socket_create_pair($domain, $type, $protocol, $pair); + if ($ret === false) { + throw Exception::createFromGlobalSocketOperation('Unable to create pair of sockets'); + } + return array(new Socket($pair[0]), new Socket($pair[1])); + } + + /** + * create TCP/IPv4 stream socket and listen for new connections + * + * @param int $port + * @param int $backlog + * @return \Socket\Raw\Socket + * @throws Exception if creating listening socket fails + * @throws \Error PHP 8 only: throws \Error when arguments are invalid + * @uses socket_create_listen() + * @see self::createServer() as an alternative to bind to specific IP, IPv6, UDP, UNIX, UGP + */ + public function createListen($port, $backlog = 128) + { + $sock = @socket_create_listen($port, $backlog); + if ($sock === false) { + throw Exception::createFromGlobalSocketOperation('Unable to create listening socket'); + } + return new Socket($sock); + } + + /** + * create socket for given address + * + * @param string $address (passed by reference in order to remove scheme, if present) + * @param string|null $scheme default scheme to use, defaults to TCP (passed by reference in order to update with actual scheme used) + * @return \Socket\Raw\Socket + * @throws InvalidArgumentException if given address is invalid + * @throws Exception in case creating socket failed + * @uses self::createTcp4() etc. + */ + public function createFromString(&$address, &$scheme) + { + if ($scheme === null) { + $scheme = 'tcp'; + } + + $hasScheme = false; + + $pos = strpos($address, '://'); + if ($pos !== false) { + $scheme = substr($address, 0, $pos); + $address = substr($address, $pos + 3); + $hasScheme = true; + } + + if (strpos($address, ':') !== strrpos($address, ':') && in_array($scheme, array('tcp', 'udp', 'icmp'))) { + // TCP/UDP/ICMP address with several colons => must be IPv6 + $scheme .= '6'; + } + + if ($scheme === 'tcp') { + $socket = $this->createTcp4(); + } elseif ($scheme === 'udp') { + $socket = $this->createUdp4(); + } elseif ($scheme === 'tcp6') { + $socket = $this->createTcp6(); + } elseif ($scheme === 'udp6') { + $socket = $this->createUdp6(); + } elseif ($scheme === 'unix') { + $socket = $this->createUnix(); + } elseif ($scheme === 'udg') { + $socket = $this->createUdg(); + } elseif ($scheme === 'icmp') { + $socket = $this->createIcmp4(); + } elseif ($scheme === 'icmp6') { + $socket = $this->createIcmp6(); + if ($hasScheme) { + // scheme was stripped from address, resulting IPv6 must not + // have a port (due to ICMP) and thus must not be enclosed in + // square brackets + $address = trim($address, '[]'); + } + } else { + throw new InvalidArgumentException('Invalid address scheme given'); + } + return $socket; + } +} diff --git a/vendor/clue/socket-raw/src/Socket.php b/vendor/clue/socket-raw/src/Socket.php new file mode 100644 index 0000000..67407f2 --- /dev/null +++ b/vendor/clue/socket-raw/src/Socket.php @@ -0,0 +1,562 @@ +<?php + +namespace Socket\Raw; + +/** + * Simple and lightweight OOP wrapper for the low-level sockets extension (ext-sockets) + * + * @author clue + * @link https://github.com/clue/php-socket-raw + */ +class Socket +{ + /** + * reference to actual socket resource + * + * @var \Socket|resource + */ + private $resource; + + /** + * instanciate socket wrapper for given socket resource + * + * should usually not be called manually, see Factory + * + * @param \Socket|resource $resource + * @see Factory as the preferred (and simplest) way to construct socket instances + */ + public function __construct($resource) + { + $this->resource = $resource; + } + + /** + * get actual socket resource + * + * @return \Socket|resource returns the socket resource (a `Socket` object as of PHP 8) + */ + public function getResource() + { + return $this->resource; + } + + /** + * accept an incomming connection on this listening socket + * + * @return \Socket\Raw\Socket new connected socket used for communication + * @throws Exception on error, if this is not a listening socket or there's no connection pending + * @throws \Error PHP 8 only: throws \Error when socket is invalid + * @see self::selectRead() to check if this listening socket can accept() + * @see Factory::createServer() to create a listening socket + * @see self::listen() has to be called first + * @uses socket_accept() + */ + public function accept() + { + $resource = @socket_accept($this->resource); + if ($resource === false) { + throw Exception::createFromGlobalSocketOperation(); + } + return new Socket($resource); + } + + /** + * binds a name/address/path to this socket + * + * has to be called before issuing connect() or listen() + * + * @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path + * @return self $this (chainable) + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @uses socket_bind() + */ + public function bind($address) + { + $ret = @socket_bind($this->resource, $this->unformatAddress($address, $port), $port); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $this; + } + + /** + * close this socket + * + * ATTENTION: make sure to NOT re-use this socket instance after closing it! + * its socket resource remains closed and most further operations will fail! + * + * @return self $this (chainable) + * @throws \Error PHP 8 only: throws \Error when socket is invalid + * @see self::shutdown() should be called before closing socket + * @uses socket_close() + */ + public function close() + { + socket_close($this->resource); + return $this; + } + + /** + * initiate a connection to given address + * + * @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path + * @return self $this (chainable) + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @uses socket_connect() + */ + public function connect($address) + { + $ret = @socket_connect($this->resource, $this->unformatAddress($address, $port), $port); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $this; + } + + /** + * Initiates a new connection to given address, wait for up to $timeout seconds + * + * The given $timeout parameter is an upper bound, a maximum time to wait + * for the connection to be either accepted or rejected. + * + * The resulting socket resource will be set to non-blocking mode, + * regardless of its previous state and whether this method succedes or + * if it fails. Make sure to reset with `setBlocking(true)` if you want to + * continue using blocking calls. + * + * @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path + * @param float $timeout maximum time to wait (in seconds) + * @return self $this (chainable) + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @uses self::setBlocking() to enable non-blocking mode + * @uses self::connect() to initiate the connection + * @uses self::selectWrite() to wait for the connection to complete + * @uses self::assertAlive() to check connection state + */ + public function connectTimeout($address, $timeout) + { + $this->setBlocking(false); + + try { + // socket is non-blocking, so connect should emit EINPROGRESS + $this->connect($address); + + // socket is already connected immediately? + return $this; + } catch (Exception $e) { + // non-blocking connect() should be EINPROGRESS (or EWOULDBLOCK on Windows) => otherwise re-throw + if ($e->getCode() !== SOCKET_EINPROGRESS && $e->getCode() !== SOCKET_EWOULDBLOCK) { + throw $e; + } + + // connection should be completed (or rejected) within timeout: socket becomes writable on success or error + // Windows requires special care because it uses exceptfds for socket errors: https://github.com/reactphp/event-loop/issues/206 + $r = null; + $w = array($this->resource); + $e = DIRECTORY_SEPARATOR === '\\' ? $w : null; + $ret = @socket_select($r, $w, $e, $timeout === null ? null : (int) $timeout, (int) (($timeout - floor($timeout)) * 1000000)); + + if ($ret === false) { + throw Exception::createFromGlobalSocketOperation('Failed to select socket for writing'); + } elseif ($ret === 0) { + throw new Exception('Timed out while waiting for connection', SOCKET_ETIMEDOUT); + } + + // confirm connection success (or fail if connected has been rejected) + $this->assertAlive(); + + return $this; + } + } + + /** + * get socket option + * + * @param int $level + * @param int $optname + * @return mixed + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @uses socket_get_option() + */ + public function getOption($level, $optname) + { + $value = @socket_get_option($this->resource, $level, $optname); + if ($value === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $value; + } + + /** + * get remote side's address/path + * + * @return string + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket is invalid + * @uses socket_getpeername() + */ + public function getPeerName() + { + $ret = @socket_getpeername($this->resource, $address, $port); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $this->formatAddress($address, $port); + } + + /** + * get local side's address/path + * + * @return string + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket is invalid + * @uses socket_getsockname() + */ + public function getSockName() + { + $ret = @socket_getsockname($this->resource, $address, $port); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $this->formatAddress($address, $port); + } + + /** + * start listen for incoming connections + * + * @param int $backlog maximum number of incoming connections to be queued + * @return self $this (chainable) + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @see self::bind() has to be called first to bind name to socket + * @uses socket_listen() + */ + public function listen($backlog = 0) + { + $ret = @socket_listen($this->resource, $backlog); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $this; + } + + /** + * read up to $length bytes from connect()ed / accept()ed socket + * + * The $type parameter specifies if this should use either binary safe reading + * (PHP_BINARY_READ, the default) or stop at CR or LF characters (PHP_NORMAL_READ) + * + * @param int $length maximum length to read + * @param int $type either of PHP_BINARY_READ (the default) or PHP_NORMAL_READ + * @return string + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @see self::recv() if you need to pass flags + * @uses socket_read() + */ + public function read($length, $type = PHP_BINARY_READ) + { + $data = @socket_read($this->resource, $length, $type); + if ($data === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $data; + } + + /** + * receive up to $length bytes from connect()ed / accept()ed socket + * + * @param int $length maximum length to read + * @param int $flags + * @return string + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @see self::read() if you do not need to pass $flags + * @see self::recvFrom() if your socket is not connect()ed + * @uses socket_recv() + */ + public function recv($length, $flags) + { + $ret = @socket_recv($this->resource, $buffer, $length, $flags); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $buffer; + } + + /** + * receive up to $length bytes from socket + * + * @param int $length maximum length to read + * @param int $flags + * @param string $remote reference will be filled with remote/peer address/path + * @return string + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @see self::recv() if your socket is connect()ed + * @uses socket_recvfrom() + */ + public function recvFrom($length, $flags, &$remote) + { + $ret = @socket_recvfrom($this->resource, $buffer, $length, $flags, $address, $port); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + $remote = $this->formatAddress($address, $port); + return $buffer; + } + + /** + * check socket to see if a read/recv/revFrom will not block + * + * @param float|null $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit + * @return boolean true = socket ready (read will not block), false = timeout expired, socket is not ready + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @uses socket_select() + */ + public function selectRead($sec = 0) + { + $usec = $sec === null ? 0 : (int) (($sec - floor($sec)) * 1000000); + $r = array($this->resource); + $n = null; + $ret = @socket_select($r, $n, $n, $sec === null ? null : (int) $sec, $usec); + if ($ret === false) { + throw Exception::createFromGlobalSocketOperation('Failed to select socket for reading'); + } + return !!$ret; + } + + /** + * check socket to see if a write/send/sendTo will not block + * + * @param float|null $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit + * @return boolean true = socket ready (write will not block), false = timeout expired, socket is not ready + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @uses socket_select() + */ + public function selectWrite($sec = 0) + { + $usec = $sec === null ? 0 : (int) (($sec - floor($sec)) * 1000000); + $w = array($this->resource); + $n = null; + $ret = @socket_select($n, $w, $n, $sec === null ? null : (int) $sec, $usec); + if ($ret === false) { + throw Exception::createFromGlobalSocketOperation('Failed to select socket for writing'); + } + return !!$ret; + } + + /** + * send given $buffer to connect()ed / accept()ed socket + * + * @param string $buffer + * @param int $flags + * @return int number of bytes actually written (make sure to check against given buffer length!) + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @see self::write() if you do not need to pass $flags + * @see self::sendTo() if your socket is not connect()ed + * @uses socket_send() + */ + public function send($buffer, $flags) + { + $ret = @socket_send($this->resource, $buffer, strlen($buffer), $flags); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $ret; + } + + /** + * send given $buffer to socket + * + * @param string $buffer + * @param int $flags + * @param string $remote remote/peer address/path + * @return int number of bytes actually written + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @see self::send() if your socket is connect()ed + * @uses socket_sendto() + */ + public function sendTo($buffer, $flags, $remote) + { + $ret = @socket_sendto($this->resource, $buffer, strlen($buffer), $flags, $this->unformatAddress($remote, $port), $port); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $ret; + } + + /** + * enable/disable blocking/nonblocking mode (O_NONBLOCK flag) + * + * @param boolean $toggle + * @return self $this (chainable) + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @uses socket_set_block() + * @uses socket_set_nonblock() + */ + public function setBlocking($toggle = true) + { + $ret = $toggle ? @socket_set_block($this->resource) : @socket_set_nonblock($this->resource); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $this; + } + + /** + * set socket option + * + * @param int $level + * @param int $optname + * @param mixed $optval + * @return self $this (chainable) + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @see self::getOption() + * @uses socket_set_option() + */ + public function setOption($level, $optname, $optval) + { + $ret = @socket_set_option($this->resource, $level, $optname, $optval); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $this; + } + + /** + * shuts down socket for receiving, sending or both + * + * @param int $how 0 = shutdown reading, 1 = shutdown writing, 2 = shutdown reading and writing + * @return self $this (chainable) + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @see self::close() + * @uses socket_shutdown() + */ + public function shutdown($how = 2) + { + $ret = @socket_shutdown($this->resource, $how); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $this; + } + + /** + * write $buffer to connect()ed / accept()ed socket + * + * @param string $buffer + * @return int number of bytes actually written + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid + * @see self::send() if you need to pass flags + * @uses socket_write() + */ + public function write($buffer) + { + $ret = @socket_write($this->resource, $buffer); + if ($ret === false) { + throw Exception::createFromSocketResource($this->resource); + } + return $ret; + } + + /** + * get socket type as passed to socket_create() + * + * @return int usually either SOCK_STREAM or SOCK_DGRAM + * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket is invalid + * @uses self::getOption() + */ + public function getType() + { + return $this->getOption(SOL_SOCKET, SO_TYPE); + } + + /** + * assert that this socket is alive and its error code is 0 + * + * This will fetch and reset the current socket error code from the + * socket and options and will throw an Exception along with error + * message and code if the code is not 0, i.e. if it does indicate + * an error situation. + * + * Calling this method should not be needed in most cases and is + * likely to not throw an Exception. Each socket operation like + * connect(), send(), etc. will throw a dedicated Exception in case + * of an error anyway. + * + * @return self $this (chainable) + * @throws Exception if error code is not 0 + * @throws \Error PHP 8 only: throws \Error when socket is invalid + * @uses self::getOption() to retrieve and clear current error code + * @uses self::getErrorMessage() to translate error code to + */ + public function assertAlive() + { + $code = $this->getOption(SOL_SOCKET, SO_ERROR); + if ($code !== 0) { + throw Exception::createFromCode($code, 'Socket error'); + } + return $this; + } + + /** + * format given address/host/path and port + * + * @param string $address + * @param int $port + * @return string + */ + protected function formatAddress($address, $port) + { + if ($port !== 0) { + if (strpos($address, ':') !== false) { + $address = '[' . $address . ']'; + } + $address .= ':' . $port; + } + return $address; + } + + /** + * format given address by splitting it into returned address and port set by reference + * + * @param string $address + * @param int $port + * @return string address with port removed + */ + protected function unformatAddress($address, &$port) + { + // [::1]:2 => ::1 2 + // test:2 => test 2 + // ::1 => ::1 + // test => test + + $colon = strrpos($address, ':'); + + // there is a colon and this is the only colon or there's a closing IPv6 bracket right before it + if ($colon !== false && (strpos($address, ':') === $colon || strpos($address, ']') === ($colon - 1))) { + $port = (int)substr($address, $colon + 1); + $address = substr($address, 0, $colon); + + // remove IPv6 square brackets + if (substr($address, 0, 1) === '[') { + $address = substr($address, 1, -1); + } + } + return $address; + } +} |