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/dns | |
parent | 286d643180672f20526f3dc3bd19d7b751e2fa97 (diff) |
Initial Commit
Diffstat (limited to 'assets/php/vendor/react/dns')
54 files changed, 4581 insertions, 0 deletions
diff --git a/assets/php/vendor/react/dns/.gitignore b/assets/php/vendor/react/dns/.gitignore new file mode 100644 index 0000000..19982ea --- /dev/null +++ b/assets/php/vendor/react/dns/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor
\ No newline at end of file diff --git a/assets/php/vendor/react/dns/.travis.yml b/assets/php/vendor/react/dns/.travis.yml new file mode 100644 index 0000000..41921e3 --- /dev/null +++ b/assets/php/vendor/react/dns/.travis.yml @@ -0,0 +1,29 @@ +language: php + +php: +# - 5.3 # requires old distro, see below + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - hhvm # ignore errors, see below + +# lock distro so new future defaults will not break the build +dist: trusty + +matrix: + include: + - php: 5.3 + dist: precise + allow_failures: + - php: hhvm + +sudo: false + +install: + - composer install --no-interaction + +script: + - vendor/bin/phpunit --coverage-text diff --git a/assets/php/vendor/react/dns/CHANGELOG.md b/assets/php/vendor/react/dns/CHANGELOG.md new file mode 100644 index 0000000..adad0a7 --- /dev/null +++ b/assets/php/vendor/react/dns/CHANGELOG.md @@ -0,0 +1,179 @@ +# Changelog + +## 0.4.13 (2018-02-27) + +* Add `Config::loadSystemConfigBlocking()` to load default system config + and support parsing DNS config on all supported platforms + (`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows) + (#92, #93, #94 and #95 by @clue) + + ```php + $config = Config::loadSystemConfigBlocking(); + $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + ``` + +* Remove unneeded cyclic dependency on react/socket + (#96 by @clue) + +## 0.4.12 (2018-01-14) + +* Improve test suite by adding forward compatibility with PHPUnit 6, + test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases, + add test group to skip integration tests relying on internet connection + and add minor documentation improvements. + (#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor) + +## 0.4.11 (2017-08-25) + +* Feature: Support resolving from default hosts file + (#75, #76 and #77 by @clue) + + This means that resolving hosts such as `localhost` will now work as + expected across all platforms with no changes required: + + ```php + $resolver->resolve('localhost')->then(function ($ip) { + echo 'IP: ' . $ip; + }); + ``` + + The new `HostsExecutor` exists for advanced usage and is otherwise used + internally for this feature. + +## 0.4.10 (2017-08-10) + +* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and + lock minimum dependencies and work around circular dependency for tests + (#70 and #71 by @clue) + +* Fix: Work around DNS timeout issues for Windows users + (#74 by @clue) + +* Documentation and examples for advanced usage + (#66 by @WyriHaximus) + +* Remove broken TCP code, do not retry with invalid TCP query + (#73 by @clue) + +* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and + lock Travis distro so new defaults will not break the build and + fix failing tests for PHP 7.1 + (#68 by @WyriHaximus and #69 and #72 by @clue) + +## 0.4.9 (2017-05-01) + +* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8 + (#61 by @clue) + +## 0.4.8 (2017-04-16) + +* Feature: Add support for the AAAA record type to the protocol parser + (#58 by @othillo) + +* Feature: Add support for the PTR record type to the protocol parser + (#59 by @othillo) + +## 0.4.7 (2017-03-31) + +* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component + (#57 by @clue) + +## 0.4.6 (2017-03-11) + +* Fix: Fix DNS timeout issues for Windows users and add forward compatibility + with Stream v0.5 and upcoming v0.6 + (#53 by @clue) + +* Improve test suite by adding PHPUnit to `require-dev` + (#54 by @clue) + +## 0.4.5 (2017-03-02) + +* Fix: Ensure we ignore the case of the answer + (#51 by @WyriHaximus) + +* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal + code re-use for upcoming versions. + (#48 and #49 by @clue) + +## 0.4.4 (2017-02-13) + +* Fix: Fix handling connection and stream errors + (#45 by @clue) + +* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component + (#46 and #47 by @clue) + +## 0.4.3 (2016-07-31) + +* Feature: Allow for cache adapter injection (#38 by @WyriHaximus) + + ```php + $factory = new React\Dns\Resolver\Factory(); + + $cache = new MyCustomCacheInstance(); + $resolver = $factory->createCached('8.8.8.8', $loop, $cache); + ``` + +* Feature: Support Promise cancellation (#35 by @clue) + + ```php + $promise = $resolver->resolve('reactphp.org'); + + $promise->cancel(); + ``` + +## 0.4.2 (2016-02-24) + +* Repository maintenance, split off from main repo, improve test suite and documentation +* First class support for PHP7 and HHVM (#34 by @clue) +* Adjust compatibility to 5.3 (#30 by @clue) + +## 0.4.1 (2014-04-13) + +* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus) + +## 0.4.0 (2014-02-02) + +* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks +* BC break: Update to React/Promise 2.0 +* Bug fix: Properly resolve CNAME aliases +* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 +* Bump React dependencies to v0.4 + +## 0.3.2 (2013-05-10) + +* Feature: Support default port for IPv6 addresses (@clue) + +## 0.3.0 (2013-04-14) + +* Bump React dependencies to v0.3 + +## 0.2.6 (2012-12-26) + +* Feature: New cache component, used by DNS + +## 0.2.5 (2012-11-26) + +* Version bump + +## 0.2.4 (2012-11-18) + +* Feature: Change to promise-based API (@jsor) + +## 0.2.3 (2012-11-14) + +* Version bump + +## 0.2.2 (2012-10-28) + +* Feature: DNS executor timeout handling (@arnaud-lb) +* Feature: DNS retry executor (@arnaud-lb) + +## 0.2.1 (2012-10-14) + +* Minor adjustments to DNS parser + +## 0.2.0 (2012-09-10) + +* Feature: DNS resolver diff --git a/assets/php/vendor/react/dns/LICENSE b/assets/php/vendor/react/dns/LICENSE new file mode 100644 index 0000000..a808108 --- /dev/null +++ b/assets/php/vendor/react/dns/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Igor Wiedler, Chris Boden + +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/assets/php/vendor/react/dns/README.md b/assets/php/vendor/react/dns/README.md new file mode 100644 index 0000000..ed86667 --- /dev/null +++ b/assets/php/vendor/react/dns/README.md @@ -0,0 +1,209 @@ +# Dns + +[](https://travis-ci.org/reactphp/dns) + +Async DNS resolver for [ReactPHP](https://reactphp.org/). + +The main point of the DNS component is to provide async DNS resolution. +However, it is really a toolkit for working with DNS messages, and could +easily be used to create a DNS server. + +**Table of contents** + +* [Basic usage](#basic-usage) +* [Caching](#caching) + * [Custom cache adapter](#custom-cache-adapter) +* [Advanced usage](#advanced-usage) + * [HostsFileExecutor](#hostsfileexecutor) +* [Install](#install) +* [Tests](#tests) +* [License](#license) +* [References](#references) + +## Basic usage + +The most basic usage is to just create a resolver through the resolver +factory. All you need to give it is a nameserver, then you can start resolving +names, baby! + +```php +$loop = React\EventLoop\Factory::create(); + +$config = React\Dns\Config\Config::loadSystemConfigBlocking(); +$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + +$factory = new React\Dns\Resolver\Factory(); +$dns = $factory->create($server, $loop); + +$dns->resolve('igor.io')->then(function ($ip) { + echo "Host: $ip\n"; +}); + +$loop->run(); +``` + +See also the [first example](examples). + +The `Config` class can be used to load the system default config. This is an +operation that may access the filesystem and block. Ideally, this method should +thus be executed only once before the loop starts and not repeatedly while it is +running. +Note that this class may return an *empty* configuration if the system config +can not be loaded. As such, you'll likely want to apply a default nameserver +as above if none can be found. + +> Note that the factory loads the hosts file from the filesystem once when + creating the resolver instance. + Ideally, this method should thus be executed only once before the loop starts + and not repeatedly while it is running. + +Pending DNS queries can be cancelled by cancelling its pending promise like so: + +```php +$promise = $resolver->resolve('reactphp.org'); + +$promise->cancel(); +``` + +But there's more. + +## Caching + +You can cache results by configuring the resolver to use a `CachedExecutor`: + +```php +$loop = React\EventLoop\Factory::create(); + +$config = React\Dns\Config\Config::loadSystemConfigBlocking(); +$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + +$factory = new React\Dns\Resolver\Factory(); +$dns = $factory->createCached($server, $loop); + +$dns->resolve('igor.io')->then(function ($ip) { + echo "Host: $ip\n"; +}); + +... + +$dns->resolve('igor.io')->then(function ($ip) { + echo "Host: $ip\n"; +}); + +$loop->run(); +``` + +If the first call returns before the second, only one query will be executed. +The second result will be served from an in memory cache. +This is particularly useful for long running scripts where the same hostnames +have to be looked up multiple times. + +See also the [third example](examples). + +### Custom cache adapter + +By default, the above will use an in memory cache. + +You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead: + +```php +$cache = new React\Cache\ArrayCache(); +$loop = React\EventLoop\Factory::create(); +$factory = new React\Dns\Resolver\Factory(); +$dns = $factory->createCached('8.8.8.8', $loop, $cache); +``` + +See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations). + +## Advanced Usage + +For more advanced usages one can utilize the `React\Dns\Query\Executor` directly. +The following example looks up the `IPv6` address for `igor.io`. + +```php +$loop = Factory::create(); + +$executor = new Executor($loop, new Parser(), new BinaryDumper(), null); + +$executor->query( + '8.8.8.8:53', + new Query($name, Message::TYPE_AAAA, Message::CLASS_IN, time()) +)->done(function (Message $message) { + foreach ($message->answers as $answer) { + echo 'IPv6: ' . $answer->data . PHP_EOL; + } +}, 'printf'); + +$loop->run(); + +``` + +See also the [fourth example](examples). + +### HostsFileExecutor + +Note that the above `Executor` class always performs an actual DNS query. +If you also want to take entries from your hosts file into account, you may +use this code: + +```php +$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking(); + +$executor = new Executor($loop, new Parser(), new BinaryDumper(), null); +$executor = new HostsFileExecutor($hosts, $executor); + +$executor->query( + '8.8.8.8:53', + new Query('localhost', Message::TYPE_A, Message::CLASS_IN, time()) +); +``` + +## 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 will install the latest supported version: + +```bash +$ composer require react/dns:^0.4.13 +``` + +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 and supports running on legacy PHP 5.3 through current PHP 7+ and +HHVM. +It's *highly recommended to use PHP 7+* 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 +$ php 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 +$ php vendor/bin/phpunit --exclude-group internet +``` + +## License + +MIT, see [LICENSE file](LICENSE). + +## References + +* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities +* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification diff --git a/assets/php/vendor/react/dns/composer.json b/assets/php/vendor/react/dns/composer.json new file mode 100644 index 0000000..510a43c --- /dev/null +++ b/assets/php/vendor/react/dns/composer.json @@ -0,0 +1,24 @@ +{ + "name": "react/dns", + "description": "Async DNS resolver for ReactPHP", + "keywords": ["dns", "dns-resolver", "ReactPHP", "async"], + "license": "MIT", + "require": { + "php": ">=5.3.0", + "react/cache": "~0.4.0|~0.3.0", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/promise": "^2.1 || ^1.2.1", + "react/promise-timer": "^1.2", + "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5" + }, + "require-dev": { + "clue/block-react": "^1.2", + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "autoload": { + "psr-4": { "React\\Dns\\": "src" } + }, + "autoload-dev": { + "psr-4": { "React\\Tests\\Dns\\": "tests" } + } +} diff --git a/assets/php/vendor/react/dns/examples/01-one.php b/assets/php/vendor/react/dns/examples/01-one.php new file mode 100644 index 0000000..5db164f --- /dev/null +++ b/assets/php/vendor/react/dns/examples/01-one.php @@ -0,0 +1,22 @@ +<?php + +use React\Dns\Config\Config; +use React\Dns\Resolver\Factory; + +require __DIR__ . '/../vendor/autoload.php'; + +$loop = React\EventLoop\Factory::create(); + +$config = Config::loadSystemConfigBlocking(); +$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + +$factory = new Factory(); +$resolver = $factory->create($server, $loop); + +$name = isset($argv[1]) ? $argv[1] : 'www.google.com'; + +$resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; +}, 'printf'); + +$loop->run(); diff --git a/assets/php/vendor/react/dns/examples/02-concurrent.php b/assets/php/vendor/react/dns/examples/02-concurrent.php new file mode 100644 index 0000000..87e3f5c --- /dev/null +++ b/assets/php/vendor/react/dns/examples/02-concurrent.php @@ -0,0 +1,27 @@ +<?php + +use React\Dns\Config\Config; +use React\Dns\Resolver\Factory; + +require __DIR__ . '/../vendor/autoload.php'; + +$loop = React\EventLoop\Factory::create(); + +$config = Config::loadSystemConfigBlocking(); +$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + +$factory = new Factory(); +$resolver = $factory->create($server, $loop); + +$names = array_slice($argv, 1); +if (!$names) { + $names = array('google.com', 'www.google.com', 'gmail.com'); +} + +foreach ($names as $name) { + $resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; + }, 'printf'); +} + +$loop->run(); diff --git a/assets/php/vendor/react/dns/examples/03-cached.php b/assets/php/vendor/react/dns/examples/03-cached.php new file mode 100644 index 0000000..e76a27c --- /dev/null +++ b/assets/php/vendor/react/dns/examples/03-cached.php @@ -0,0 +1,40 @@ +<?php + +use React\Dns\Config\Config; +use React\Dns\Resolver\Factory; + +require __DIR__ . '/../vendor/autoload.php'; + +$loop = React\EventLoop\Factory::create(); + +$config = Config::loadSystemConfigBlocking(); +$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + +$factory = new Factory(); +$resolver = $factory->createCached($server, $loop); + +$name = isset($argv[1]) ? $argv[1] : 'www.google.com'; + +$resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; +}, 'printf'); + +$loop->addTimer(1.0, function() use ($name, $resolver) { + $resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; + }, 'printf'); +}); + +$loop->addTimer(2.0, function() use ($name, $resolver) { + $resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; + }, 'printf'); +}); + +$loop->addTimer(3.0, function() use ($name, $resolver) { + $resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; + }, 'printf'); +}); + +$loop->run(); diff --git a/assets/php/vendor/react/dns/examples/04-query-a-and-aaaa.php b/assets/php/vendor/react/dns/examples/04-query-a-and-aaaa.php new file mode 100644 index 0000000..6c46bbf --- /dev/null +++ b/assets/php/vendor/react/dns/examples/04-query-a-and-aaaa.php @@ -0,0 +1,32 @@ +<?php + +use React\Dns\Model\Message; +use React\Dns\Protocol\BinaryDumper; +use React\Dns\Protocol\Parser; +use React\Dns\Query\Executor; +use React\Dns\Query\Query; +use React\EventLoop\Factory; + +require __DIR__ . '/../vendor/autoload.php'; + +$loop = Factory::create(); + +$executor = new Executor($loop, new Parser(), new BinaryDumper(), null); + +$name = isset($argv[1]) ? $argv[1] : 'www.google.com'; + +$ipv4Query = new Query($name, Message::TYPE_A, Message::CLASS_IN, time()); +$ipv6Query = new Query($name, Message::TYPE_AAAA, Message::CLASS_IN, time()); + +$executor->query('8.8.8.8:53', $ipv4Query)->done(function (Message $message) { + foreach ($message->answers as $answer) { + echo 'IPv4: ' . $answer->data . PHP_EOL; + } +}, 'printf'); +$executor->query('8.8.8.8:53', $ipv6Query)->done(function (Message $message) { + foreach ($message->answers as $answer) { + echo 'IPv6: ' . $answer->data . PHP_EOL; + } +}, 'printf'); + +$loop->run(); diff --git a/assets/php/vendor/react/dns/phpunit.xml.dist b/assets/php/vendor/react/dns/phpunit.xml.dist new file mode 100644 index 0000000..13d3fab --- /dev/null +++ b/assets/php/vendor/react/dns/phpunit.xml.dist @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<phpunit backupGlobals="false" + backupStaticAttributes="false" + colors="true" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + processIsolation="false" + stopOnFailure="false" + syntaxCheck="false" + bootstrap="vendor/autoload.php" +> + <testsuites> + <testsuite name="React Test Suite"> + <directory>./tests/</directory> + </testsuite> + </testsuites> + + <filter> + <whitelist> + <directory>./src/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/assets/php/vendor/react/dns/src/BadServerException.php b/assets/php/vendor/react/dns/src/BadServerException.php new file mode 100644 index 0000000..3bf50f1 --- /dev/null +++ b/assets/php/vendor/react/dns/src/BadServerException.php @@ -0,0 +1,7 @@ +<?php + +namespace React\Dns; + +class BadServerException extends \Exception +{ +} diff --git a/assets/php/vendor/react/dns/src/Config/Config.php b/assets/php/vendor/react/dns/src/Config/Config.php new file mode 100644 index 0000000..c82635d --- /dev/null +++ b/assets/php/vendor/react/dns/src/Config/Config.php @@ -0,0 +1,127 @@ +<?php + +namespace React\Dns\Config; + +use RuntimeException; + +class Config +{ + /** + * Loads the system DNS configuration + * + * Note that this method may block while loading its internal files and/or + * commands and should thus be used with care! While this should be + * relatively fast for most systems, it remains unknown if this may block + * under certain circumstances. In particular, this method should only be + * executed before the loop starts, not while it is running. + * + * Note that this method will try to access its files and/or commands and + * try to parse its output. Currently, this will only parse valid nameserver + * entries from its output and will ignore all other output without + * complaining. + * + * Note that the previous section implies that this may return an empty + * `Config` object if no valid nameserver entries can be found. + * + * @return self + * @codeCoverageIgnore + */ + public static function loadSystemConfigBlocking() + { + // Use WMIC output on Windows + if (DIRECTORY_SEPARATOR === '\\') { + return self::loadWmicBlocking(); + } + + // otherwise (try to) load from resolv.conf + try { + return self::loadResolvConfBlocking(); + } catch (RuntimeException $ignored) { + // return empty config if parsing fails (file not found) + return new self(); + } + } + + /** + * Loads a resolv.conf file (from the given path or default location) + * + * Note that this method blocks while loading the given path and should + * thus be used with care! While this should be relatively fast for normal + * resolv.conf files, this may be an issue if this file is located on a slow + * device or contains an excessive number of entries. In particular, this + * method should only be executed before the loop starts, not while it is + * running. + * + * Note that this method will throw if the given file can not be loaded, + * such as if it is not readable or does not exist. In particular, this file + * is not available on Windows. + * + * Currently, this will only parse valid "nameserver X" lines from the + * given file contents. Lines can be commented out with "#" and ";" and + * invalid lines will be ignored without complaining. See also + * `man resolv.conf` for more details. + * + * Note that the previous section implies that this may return an empty + * `Config` object if no valid "nameserver X" lines can be found. See also + * `man resolv.conf` which suggests that the DNS server on the localhost + * should be used in this case. This is left up to higher level consumers + * of this API. + * + * @param ?string $path (optional) path to resolv.conf file or null=load default location + * @return self + * @throws RuntimeException if the path can not be loaded (does not exist) + */ + public static function loadResolvConfBlocking($path = null) + { + if ($path === null) { + $path = '/etc/resolv.conf'; + } + + $contents = @file_get_contents($path); + if ($contents === false) { + throw new RuntimeException('Unable to load resolv.conf file "' . $path . '"'); + } + + preg_match_all('/^nameserver\s+(\S+)\s*$/m', $contents, $matches); + + $config = new self(); + $config->nameservers = $matches[1]; + + return $config; + } + + /** + * Loads the DNS configurations from Windows's WMIC (from the given command or default command) + * + * Note that this method blocks while loading the given command and should + * thus be used with care! While this should be relatively fast for normal + * WMIC commands, it remains unknown if this may block under certain + * circumstances. In particular, this method should only be executed before + * the loop starts, not while it is running. + * + * Note that this method will only try to execute the given command try to + * parse its output, irrespective of whether this command exists. In + * particular, this command is only available on Windows. Currently, this + * will only parse valid nameserver entries from the command output and will + * ignore all other output without complaining. + * + * Note that the previous section implies that this may return an empty + * `Config` object if no valid nameserver entries can be found. + * + * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing + * @return self + * @link https://ss64.com/nt/wmic.html + */ + public static function loadWmicBlocking($command = null) + { + $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command); + preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches); + + $config = new self(); + $config->nameservers = $matches[1]; + + return $config; + } + + public $nameservers = array(); +} diff --git a/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php b/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php new file mode 100644 index 0000000..68cec3e --- /dev/null +++ b/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php @@ -0,0 +1,73 @@ +<?php + +namespace React\Dns\Config; + +use React\EventLoop\LoopInterface; +use React\Promise; +use React\Promise\Deferred; +use React\Stream\ReadableResourceStream; +use React\Stream\Stream; + +/** + * @deprecated + * @see Config see Config class instead. + */ +class FilesystemFactory +{ + private $loop; + + public function __construct(LoopInterface $loop) + { + $this->loop = $loop; + } + + public function create($filename) + { + return $this + ->loadEtcResolvConf($filename) + ->then(array($this, 'parseEtcResolvConf')); + } + + /** + * @param string $contents + * @return Promise + * @deprecated see Config instead + */ + public function parseEtcResolvConf($contents) + { + return Promise\resolve(Config::loadResolvConfBlocking( + 'data://text/plain;base64,' . base64_encode($contents) + )); + } + + public function loadEtcResolvConf($filename) + { + if (!file_exists($filename)) { + return Promise\reject(new \InvalidArgumentException("The filename for /etc/resolv.conf given does not exist: $filename")); + } + + try { + $deferred = new Deferred(); + + $fd = fopen($filename, 'r'); + stream_set_blocking($fd, 0); + + $contents = ''; + + $stream = class_exists('React\Stream\ReadableResourceStream') ? new ReadableResourceStream($fd, $this->loop) : new Stream($fd, $this->loop); + $stream->on('data', function ($data) use (&$contents) { + $contents .= $data; + }); + $stream->on('end', function () use (&$contents, $deferred) { + $deferred->resolve($contents); + }); + $stream->on('error', function ($error) use ($deferred) { + $deferred->reject($error); + }); + + return $deferred->promise(); + } catch (\Exception $e) { + return Promise\reject($e); + } + } +} diff --git a/assets/php/vendor/react/dns/src/Config/HostsFile.php b/assets/php/vendor/react/dns/src/Config/HostsFile.php new file mode 100644 index 0000000..5b6277e --- /dev/null +++ b/assets/php/vendor/react/dns/src/Config/HostsFile.php @@ -0,0 +1,151 @@ +<?php + +namespace React\Dns\Config; + +use RuntimeException; + +/** + * Represents a static hosts file which maps hostnames to IPs + * + * Hosts files are used on most systems to avoid actually hitting the DNS for + * certain common hostnames. + * + * Most notably, this file usually contains an entry to map "localhost" to the + * local IP. Windows is a notable exception here, as Windows does not actually + * include "localhost" in this file by default. To compensate for this, this + * class may explicitly be wrapped in another HostsFile instance which + * hard-codes these entries for Windows (see also Factory). + * + * This class mostly exists to abstract the parsing/extraction process so this + * can be replaced with a faster alternative in the future. + */ +class HostsFile +{ + /** + * Returns the default path for the hosts file on this system + * + * @return string + * @codeCoverageIgnore + */ + public static function getDefaultPath() + { + // use static path for all Unix-based systems + if (DIRECTORY_SEPARATOR !== '\\') { + return '/etc/hosts'; + } + + // Windows actually stores the path in the registry under + // \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DataBasePath + $path = '%SystemRoot%\\system32\drivers\etc\hosts'; + + $base = getenv('SystemRoot'); + if ($base === false) { + $base = 'C:\\Windows'; + } + + return str_replace('%SystemRoot%', $base, $path); + } + + /** + * Loads a hosts file (from the given path or default location) + * + * Note that this method blocks while loading the given path and should + * thus be used with care! While this should be relatively fast for normal + * hosts file, this may be an issue if this file is located on a slow device + * or contains an excessive number of entries. In particular, this method + * should only be executed before the loop starts, not while it is running. + * + * @param ?string $path (optional) path to hosts file or null=load default location + * @return self + * @throws RuntimeException if the path can not be loaded (does not exist) + */ + public static function loadFromPathBlocking($path = null) + { + if ($path === null) { + $path = self::getDefaultPath(); + } + + $contents = @file_get_contents($path); + if ($contents === false) { + throw new RuntimeException('Unable to load hosts file "' . $path . '"'); + } + + return new self($contents); + } + + /** + * Instantiate new hosts file with the given hosts file contents + * + * @param string $contents + */ + public function __construct($contents) + { + // remove all comments from the contents + $contents = preg_replace('/[ \t]*#.*/', '', strtolower($contents)); + + $this->contents = $contents; + } + + /** + * Returns all IPs for the given hostname + * + * @param string $name + * @return string[] + */ + public function getIpsForHost($name) + { + $name = strtolower($name); + + $ips = array(); + foreach (preg_split('/\r?\n/', $this->contents) as $line) { + $parts = preg_split('/\s+/', $line); + $ip = array_shift($parts); + if ($parts && array_search($name, $parts) !== false) { + // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`) + if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) { + $ip = substr($ip, 0, $pos); + } + + if (@inet_pton($ip) !== false) { + $ips[] = $ip; + } + } + } + + return $ips; + } + + /** + * Returns all hostnames for the given IPv4 or IPv6 address + * + * @param string $ip + * @return string[] + */ + public function getHostsForIp($ip) + { + // check binary representation of IP to avoid string case and short notation + $ip = @inet_pton($ip); + if ($ip === false) { + return array(); + } + + $names = array(); + foreach (preg_split('/\r?\n/', $this->contents) as $line) { + $parts = preg_split('/\s+/', $line, null, PREG_SPLIT_NO_EMPTY); + $addr = array_shift($parts); + + // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`) + if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) { + $addr = substr($addr, 0, $pos); + } + + if (@inet_pton($addr) === $ip) { + foreach ($parts as $part) { + $names[] = $part; + } + } + } + + return $names; + } +} diff --git a/assets/php/vendor/react/dns/src/Model/HeaderBag.php b/assets/php/vendor/react/dns/src/Model/HeaderBag.php new file mode 100644 index 0000000..193e65c --- /dev/null +++ b/assets/php/vendor/react/dns/src/Model/HeaderBag.php @@ -0,0 +1,56 @@ +<?php + +namespace React\Dns\Model; + +class HeaderBag +{ + public $data = ''; + + public $attributes = array( + 'qdCount' => 0, + 'anCount' => 0, + 'nsCount' => 0, + 'arCount' => 0, + 'qr' => 0, + 'opcode' => Message::OPCODE_QUERY, + 'aa' => 0, + 'tc' => 0, + 'rd' => 0, + 'ra' => 0, + 'z' => 0, + 'rcode' => Message::RCODE_OK, + ); + + public function get($name) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : null; + } + + public function set($name, $value) + { + $this->attributes[$name] = $value; + } + + public function isQuery() + { + return 0 === $this->attributes['qr']; + } + + public function isResponse() + { + return 1 === $this->attributes['qr']; + } + + public function isTruncated() + { + return 1 === $this->attributes['tc']; + } + + public function populateCounts(Message $message) + { + $this->attributes['qdCount'] = count($message->questions); + $this->attributes['anCount'] = count($message->answers); + $this->attributes['nsCount'] = count($message->authority); + $this->attributes['arCount'] = count($message->additional); + } +} diff --git a/assets/php/vendor/react/dns/src/Model/Message.php b/assets/php/vendor/react/dns/src/Model/Message.php new file mode 100644 index 0000000..715cb1f --- /dev/null +++ b/assets/php/vendor/react/dns/src/Model/Message.php @@ -0,0 +1,100 @@ +<?php + +namespace React\Dns\Model; + +use React\Dns\Query\Query; +use React\Dns\Model\Record; + +class Message +{ + const TYPE_A = 1; + const TYPE_NS = 2; + const TYPE_CNAME = 5; + const TYPE_SOA = 6; + const TYPE_PTR = 12; + const TYPE_MX = 15; + const TYPE_TXT = 16; + const TYPE_AAAA = 28; + + const CLASS_IN = 1; + + const OPCODE_QUERY = 0; + const OPCODE_IQUERY = 1; // inverse query + const OPCODE_STATUS = 2; + + const RCODE_OK = 0; + const RCODE_FORMAT_ERROR = 1; + const RCODE_SERVER_FAILURE = 2; + const RCODE_NAME_ERROR = 3; + const RCODE_NOT_IMPLEMENTED = 4; + const RCODE_REFUSED = 5; + + /** + * Creates a new request message for the given query + * + * @param Query $query + * @return self + */ + public static function createRequestForQuery(Query $query) + { + $request = new Message(); + $request->header->set('id', self::generateId()); + $request->header->set('rd', 1); + $request->questions[] = (array) $query; + $request->prepare(); + + return $request; + } + + /** + * Creates a new response message for the given query with the given answer records + * + * @param Query $query + * @param Record[] $answers + * @return self + */ + public static function createResponseWithAnswersForQuery(Query $query, array $answers) + { + $response = new Message(); + $response->header->set('id', self::generateId()); + $response->header->set('qr', 1); + $response->header->set('opcode', Message::OPCODE_QUERY); + $response->header->set('rd', 1); + $response->header->set('rcode', Message::RCODE_OK); + + $response->questions[] = (array) $query; + + foreach ($answers as $record) { + $response->answers[] = $record; + } + + $response->prepare(); + + return $response; + } + + private static function generateId() + { + return mt_rand(0, 0xffff); + } + + public $data = ''; + + public $header; + public $questions = array(); + public $answers = array(); + public $authority = array(); + public $additional = array(); + + public $consumed = 0; + + public function __construct() + { + $this->header = new HeaderBag(); + } + + public function prepare() + { + $this->header->populateCounts($this); + } +} diff --git a/assets/php/vendor/react/dns/src/Model/Record.php b/assets/php/vendor/react/dns/src/Model/Record.php new file mode 100644 index 0000000..029d232 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Model/Record.php @@ -0,0 +1,21 @@ +<?php + +namespace React\Dns\Model; + +class Record +{ + public $name; + public $type; + public $class; + public $ttl; + public $data; + + public function __construct($name, $type, $class, $ttl = 0, $data = null) + { + $this->name = $name; + $this->type = $type; + $this->class = $class; + $this->ttl = $ttl; + $this->data = $data; + } +} diff --git a/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php b/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php new file mode 100644 index 0000000..35d6ae6 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php @@ -0,0 +1,62 @@ +<?php + +namespace React\Dns\Protocol; + +use React\Dns\Model\Message; +use React\Dns\Model\HeaderBag; + +class BinaryDumper +{ + public function toBinary(Message $message) + { + $data = ''; + + $data .= $this->headerToBinary($message->header); + $data .= $this->questionToBinary($message->questions); + + return $data; + } + + private function headerToBinary(HeaderBag $header) + { + $data = ''; + + $data .= pack('n', $header->get('id')); + + $flags = 0x00; + $flags = ($flags << 1) | $header->get('qr'); + $flags = ($flags << 4) | $header->get('opcode'); + $flags = ($flags << 1) | $header->get('aa'); + $flags = ($flags << 1) | $header->get('tc'); + $flags = ($flags << 1) | $header->get('rd'); + $flags = ($flags << 1) | $header->get('ra'); + $flags = ($flags << 3) | $header->get('z'); + $flags = ($flags << 4) | $header->get('rcode'); + + $data .= pack('n', $flags); + + $data .= pack('n', $header->get('qdCount')); + $data .= pack('n', $header->get('anCount')); + $data .= pack('n', $header->get('nsCount')); + $data .= pack('n', $header->get('arCount')); + + return $data; + } + + private function questionToBinary(array $questions) + { + $data = ''; + + foreach ($questions as $question) { + $labels = explode('.', $question['name']); + foreach ($labels as $label) { + $data .= chr(strlen($label)).$label; + } + $data .= "\x00"; + + $data .= pack('n*', $question['type'], $question['class']); + } + + return $data; + } +} diff --git a/assets/php/vendor/react/dns/src/Protocol/Parser.php b/assets/php/vendor/react/dns/src/Protocol/Parser.php new file mode 100644 index 0000000..1191cd3 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Protocol/Parser.php @@ -0,0 +1,254 @@ +<?php + +namespace React\Dns\Protocol; + +use React\Dns\Model\Message; +use React\Dns\Model\Record; +use InvalidArgumentException; + +/** + * DNS protocol parser + * + * Obsolete and uncommon types and classes are not implemented. + */ +class Parser +{ + /** + * Parses the given raw binary message into a Message object + * + * @param string $data + * @throws InvalidArgumentException + * @return Message + */ + public function parseMessage($data) + { + $message = new Message(); + if ($this->parse($data, $message) !== $message) { + throw new InvalidArgumentException('Unable to parse binary message'); + } + + return $message; + } + + /** + * @deprecated unused, exists for BC only + */ + public function parseChunk($data, Message $message) + { + return $this->parse($data, $message); + } + + private function parse($data, Message $message) + { + $message->data .= $data; + + if (!$message->header->get('id')) { + if (!$this->parseHeader($message)) { + return; + } + } + + if ($message->header->get('qdCount') != count($message->questions)) { + if (!$this->parseQuestion($message)) { + return; + } + } + + if ($message->header->get('anCount') != count($message->answers)) { + if (!$this->parseAnswer($message)) { + return; + } + } + + return $message; + } + + public function parseHeader(Message $message) + { + if (strlen($message->data) < 12) { + return; + } + + $header = substr($message->data, 0, 12); + $message->consumed += 12; + + list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', $header)); + + $rcode = $fields & bindec('1111'); + $z = ($fields >> 4) & bindec('111'); + $ra = ($fields >> 7) & 1; + $rd = ($fields >> 8) & 1; + $tc = ($fields >> 9) & 1; + $aa = ($fields >> 10) & 1; + $opcode = ($fields >> 11) & bindec('1111'); + $qr = ($fields >> 15) & 1; + + $vars = compact('id', 'qdCount', 'anCount', 'nsCount', 'arCount', + 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode'); + + + foreach ($vars as $name => $value) { + $message->header->set($name, $value); + } + + return $message; + } + + public function parseQuestion(Message $message) + { + if (strlen($message->data) < 2) { + return; + } + + $consumed = $message->consumed; + + list($labels, $consumed) = $this->readLabels($message->data, $consumed); + + if (null === $labels) { + return; + } + + if (strlen($message->data) - $consumed < 4) { + return; + } + + list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4))); + $consumed += 4; + + $message->consumed = $consumed; + + $message->questions[] = array( + 'name' => implode('.', $labels), + 'type' => $type, + 'class' => $class, + ); + + if ($message->header->get('qdCount') != count($message->questions)) { + return $this->parseQuestion($message); + } + + return $message; + } + + public function parseAnswer(Message $message) + { + if (strlen($message->data) < 2) { + return; + } + + $consumed = $message->consumed; + + list($labels, $consumed) = $this->readLabels($message->data, $consumed); + + if (null === $labels) { + return; + } + + if (strlen($message->data) - $consumed < 10) { + return; + } + + list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4))); + $consumed += 4; + + list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4))); + $consumed += 4; + + list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2))); + $consumed += 2; + + $rdata = null; + + if (Message::TYPE_A === $type || Message::TYPE_AAAA === $type) { + $ip = substr($message->data, $consumed, $rdLength); + $consumed += $rdLength; + + $rdata = inet_ntop($ip); + } + + if (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type) { + list($bodyLabels, $consumed) = $this->readLabels($message->data, $consumed); + + $rdata = implode('.', $bodyLabels); + } + + $message->consumed = $consumed; + + $name = implode('.', $labels); + $ttl = $this->signedLongToUnsignedLong($ttl); + $record = new Record($name, $type, $class, $ttl, $rdata); + + $message->answers[] = $record; + + if ($message->header->get('anCount') != count($message->answers)) { + return $this->parseAnswer($message); + } + + return $message; + } + + private function readLabels($data, $consumed) + { + $labels = array(); + + while (true) { + if ($this->isEndOfLabels($data, $consumed)) { + $consumed += 1; + break; + } + + if ($this->isCompressedLabel($data, $consumed)) { + list($newLabels, $consumed) = $this->getCompressedLabel($data, $consumed); + $labels = array_merge($labels, $newLabels); + break; + } + + $length = ord(substr($data, $consumed, 1)); + $consumed += 1; + + if (strlen($data) - $consumed < $length) { + return array(null, null); + } + + $labels[] = substr($data, $consumed, $length); + $consumed += $length; + } + + return array($labels, $consumed); + } + + public function isEndOfLabels($data, $consumed) + { + $length = ord(substr($data, $consumed, 1)); + return 0 === $length; + } + + public function getCompressedLabel($data, $consumed) + { + list($nameOffset, $consumed) = $this->getCompressedLabelOffset($data, $consumed); + list($labels) = $this->readLabels($data, $nameOffset); + + return array($labels, $consumed); + } + + public function isCompressedLabel($data, $consumed) + { + $mask = 0xc000; // 1100000000000000 + list($peek) = array_values(unpack('n', substr($data, $consumed, 2))); + + return (bool) ($peek & $mask); + } + + public function getCompressedLabelOffset($data, $consumed) + { + $mask = 0x3fff; // 0011111111111111 + list($peek) = array_values(unpack('n', substr($data, $consumed, 2))); + + return array($peek & $mask, $consumed + 2); + } + + public function signedLongToUnsignedLong($i) + { + return $i & 0x80000000 ? $i - 0xffffffff : $i; + } +} diff --git a/assets/php/vendor/react/dns/src/Query/CachedExecutor.php b/assets/php/vendor/react/dns/src/Query/CachedExecutor.php new file mode 100644 index 0000000..285936d --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/CachedExecutor.php @@ -0,0 +1,55 @@ +<?php + +namespace React\Dns\Query; + +use React\Dns\Model\Message; + +class CachedExecutor implements ExecutorInterface +{ + private $executor; + private $cache; + + public function __construct(ExecutorInterface $executor, RecordCache $cache) + { + $this->executor = $executor; + $this->cache = $cache; + } + + public function query($nameserver, Query $query) + { + $executor = $this->executor; + $cache = $this->cache; + + return $this->cache + ->lookup($query) + ->then( + function ($cachedRecords) use ($query) { + return Message::createResponseWithAnswersForQuery($query, $cachedRecords); + }, + function () use ($executor, $cache, $nameserver, $query) { + return $executor + ->query($nameserver, $query) + ->then(function ($response) use ($cache, $query) { + $cache->storeResponseMessage($query->currentTime, $response); + return $response; + }); + } + ); + } + + /** + * @deprecated unused, exists for BC only + */ + public function buildResponse(Query $query, array $cachedRecords) + { + return Message::createResponseWithAnswersForQuery($query, $cachedRecords); + } + + /** + * @deprecated unused, exists for BC only + */ + protected function generateId() + { + return mt_rand(0, 0xffff); + } +} diff --git a/assets/php/vendor/react/dns/src/Query/CancellationException.php b/assets/php/vendor/react/dns/src/Query/CancellationException.php new file mode 100644 index 0000000..ac30f4c --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/CancellationException.php @@ -0,0 +1,7 @@ +<?php + +namespace React\Dns\Query; + +class CancellationException extends \RuntimeException +{ +} diff --git a/assets/php/vendor/react/dns/src/Query/Executor.php b/assets/php/vendor/react/dns/src/Query/Executor.php new file mode 100644 index 0000000..4c51f2b --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/Executor.php @@ -0,0 +1,156 @@ +<?php + +namespace React\Dns\Query; + +use React\Dns\Model\Message; +use React\Dns\Protocol\Parser; +use React\Dns\Protocol\BinaryDumper; +use React\EventLoop\LoopInterface; +use React\Promise\Deferred; +use React\Promise; +use React\Stream\DuplexResourceStream; +use React\Stream\Stream; + +class Executor implements ExecutorInterface +{ + private $loop; + private $parser; + private $dumper; + private $timeout; + + /** + * + * Note that albeit supported, the $timeout parameter is deprecated! + * You should pass a `null` value here instead. If you need timeout handling, + * use the `TimeoutConnector` instead. + * + * @param LoopInterface $loop + * @param Parser $parser + * @param BinaryDumper $dumper + * @param null|float $timeout DEPRECATED: timeout for DNS query or NULL=no timeout + */ + public function __construct(LoopInterface $loop, Parser $parser, BinaryDumper $dumper, $timeout = 5) + { + $this->loop = $loop; + $this->parser = $parser; + $this->dumper = $dumper; + $this->timeout = $timeout; + } + + public function query($nameserver, Query $query) + { + $request = Message::createRequestForQuery($query); + + $queryData = $this->dumper->toBinary($request); + $transport = strlen($queryData) > 512 ? 'tcp' : 'udp'; + + return $this->doQuery($nameserver, $transport, $queryData, $query->name); + } + + /** + * @deprecated unused, exists for BC only + */ + public function prepareRequest(Query $query) + { + return Message::createRequestForQuery($query); + } + + public function doQuery($nameserver, $transport, $queryData, $name) + { + // we only support UDP right now + if ($transport !== 'udp') { + return Promise\reject(new \RuntimeException( + 'DNS query for ' . $name . ' failed: Requested transport "' . $transport . '" not available, only UDP is supported in this version' + )); + } + + $that = $this; + $parser = $this->parser; + $loop = $this->loop; + + // UDP connections are instant, so try this without a timer + try { + $conn = $this->createConnection($nameserver, $transport); + } catch (\Exception $e) { + return Promise\reject(new \RuntimeException('DNS query for ' . $name . ' failed: ' . $e->getMessage(), 0, $e)); + } + + $deferred = new Deferred(function ($resolve, $reject) use (&$timer, $loop, &$conn, $name) { + $reject(new CancellationException(sprintf('DNS query for %s has been cancelled', $name))); + + if ($timer !== null) { + $loop->cancelTimer($timer); + } + $conn->close(); + }); + + $timer = null; + if ($this->timeout !== null) { + $timer = $this->loop->addTimer($this->timeout, function () use (&$conn, $name, $deferred) { + $conn->close(); + $deferred->reject(new TimeoutException(sprintf("DNS query for %s timed out", $name))); + }); + } + + $conn->on('data', function ($data) use ($conn, $parser, $deferred, $timer, $loop, $name) { + $conn->end(); + if ($timer !== null) { + $loop->cancelTimer($timer); + } + + try { + $response = $parser->parseMessage($data); + } catch (\Exception $e) { + $deferred->reject($e); + return; + } + + if ($response->header->isTruncated()) { + $deferred->reject(new \RuntimeException('DNS query for ' . $name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported')); + return; + } + + $deferred->resolve($response); + }); + $conn->write($queryData); + + return $deferred->promise(); + } + + /** + * @deprecated unused, exists for BC only + */ + protected function generateId() + { + return mt_rand(0, 0xffff); + } + + /** + * @param string $nameserver + * @param string $transport + * @return \React\Stream\DuplexStreamInterface + */ + protected function createConnection($nameserver, $transport) + { + $fd = @stream_socket_client("$transport://$nameserver", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT); + if ($fd === false) { + throw new \RuntimeException('Unable to connect to DNS server: ' . $errstr, $errno); + } + + // Instantiate stream instance around this stream resource. + // This ought to be replaced with a datagram socket in the future. + // Temporary work around for Windows 10: buffer whole UDP response + // @coverageIgnoreStart + if (!class_exists('React\Stream\Stream')) { + // prefer DuplexResourceStream as of react/stream v0.7.0 + $conn = new DuplexResourceStream($fd, $this->loop, -1); + } else { + // use legacy Stream class for react/stream < v0.7.0 + $conn = new Stream($fd, $this->loop); + $conn->bufferSize = null; + } + // @coverageIgnoreEnd + + return $conn; + } +} diff --git a/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php b/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php new file mode 100644 index 0000000..2f7a635 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php @@ -0,0 +1,8 @@ +<?php + +namespace React\Dns\Query; + +interface ExecutorInterface +{ + public function query($nameserver, Query $query); +} diff --git a/assets/php/vendor/react/dns/src/Query/HostsFileExecutor.php b/assets/php/vendor/react/dns/src/Query/HostsFileExecutor.php new file mode 100644 index 0000000..0ca58be --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/HostsFileExecutor.php @@ -0,0 +1,89 @@ +<?php + +namespace React\Dns\Query; + +use React\Dns\Config\HostsFile; +use React\Dns\Model\Message; +use React\Dns\Model\Record; +use React\Promise; + +/** + * Resolves hosts from the givne HostsFile or falls back to another executor + * + * If the host is found in the hosts file, it will not be passed to the actual + * DNS executor. If the host is not found in the hosts file, it will be passed + * to the DNS executor as a fallback. + */ +class HostsFileExecutor implements ExecutorInterface +{ + private $hosts; + private $fallback; + + public function __construct(HostsFile $hosts, ExecutorInterface $fallback) + { + $this->hosts = $hosts; + $this->fallback = $fallback; + } + + public function query($nameserver, Query $query) + { + if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) { + // forward lookup for type A or AAAA + $records = array(); + $expectsColon = $query->type === Message::TYPE_AAAA; + foreach ($this->hosts->getIpsForHost($query->name) as $ip) { + // ensure this is an IPv4/IPV6 address according to query type + if ((strpos($ip, ':') !== false) === $expectsColon) { + $records[] = new Record($query->name, $query->type, $query->class, 0, $ip); + } + } + + if ($records) { + return Promise\resolve( + Message::createResponseWithAnswersForQuery($query, $records) + ); + } + } elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) { + // reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain + $ip = $this->getIpFromHost($query->name); + + if ($ip !== null) { + $records = array(); + foreach ($this->hosts->getHostsForIp($ip) as $host) { + $records[] = new Record($query->name, $query->type, $query->class, 0, $host); + } + + if ($records) { + return Promise\resolve( + Message::createResponseWithAnswersForQuery($query, $records) + ); + } + } + } + + return $this->fallback->query($nameserver, $query); + } + + private function getIpFromHost($host) + { + if (substr($host, -13) === '.in-addr.arpa') { + // IPv4: read as IP and reverse bytes + $ip = @inet_pton(substr($host, 0, -13)); + if ($ip === false || isset($ip[4])) { + return null; + } + + return inet_ntop(strrev($ip)); + } elseif (substr($host, -9) === '.ip6.arpa') { + // IPv6: replace dots, reverse nibbles and interpret as hexadecimal string + $ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9))))); + if ($ip === false) { + return null; + } + + return $ip; + } else { + return null; + } + } +} diff --git a/assets/php/vendor/react/dns/src/Query/Query.php b/assets/php/vendor/react/dns/src/Query/Query.php new file mode 100644 index 0000000..aef6e05 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/Query.php @@ -0,0 +1,19 @@ +<?php + +namespace React\Dns\Query; + +class Query +{ + public $name; + public $type; + public $class; + public $currentTime; + + public function __construct($name, $type, $class, $currentTime) + { + $this->name = $name; + $this->type = $type; + $this->class = $class; + $this->currentTime = $currentTime; + } +} diff --git a/assets/php/vendor/react/dns/src/Query/RecordBag.php b/assets/php/vendor/react/dns/src/Query/RecordBag.php new file mode 100644 index 0000000..358cf5d --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/RecordBag.php @@ -0,0 +1,27 @@ +<?php + +namespace React\Dns\Query; + +use React\Dns\Model\Message; +use React\Dns\Model\Record; + +class RecordBag +{ + private $records = array(); + + public function set($currentTime, Record $record) + { + $this->records[$record->data] = array($currentTime + $record->ttl, $record); + } + + public function all() + { + return array_values(array_map( + function ($value) { + list($expiresAt, $record) = $value; + return $record; + }, + $this->records + )); + } +} diff --git a/assets/php/vendor/react/dns/src/Query/RecordCache.php b/assets/php/vendor/react/dns/src/Query/RecordCache.php new file mode 100644 index 0000000..b8142d3 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/RecordCache.php @@ -0,0 +1,82 @@ +<?php + +namespace React\Dns\Query; + +use React\Cache\CacheInterface; +use React\Dns\Model\Message; +use React\Dns\Model\Record; +use React\Promise; + +class RecordCache +{ + private $cache; + private $expiredAt; + + public function __construct(CacheInterface $cache) + { + $this->cache = $cache; + } + + public function lookup(Query $query) + { + $id = $this->serializeQueryToIdentity($query); + + $expiredAt = $this->expiredAt; + + return $this->cache + ->get($id) + ->then(function ($value) use ($query, $expiredAt) { + $recordBag = unserialize($value); + + if (null !== $expiredAt && $expiredAt <= $query->currentTime) { + return Promise\reject(); + } + + return $recordBag->all(); + }); + } + + public function storeResponseMessage($currentTime, Message $message) + { + foreach ($message->answers as $record) { + $this->storeRecord($currentTime, $record); + } + } + + public function storeRecord($currentTime, Record $record) + { + $id = $this->serializeRecordToIdentity($record); + + $cache = $this->cache; + + $this->cache + ->get($id) + ->then( + function ($value) { + return unserialize($value); + }, + function ($e) { + return new RecordBag(); + } + ) + ->then(function ($recordBag) use ($id, $currentTime, $record, $cache) { + $recordBag->set($currentTime, $record); + $cache->set($id, serialize($recordBag)); + }); + } + + public function expire($currentTime) + { + $this->expiredAt = $currentTime; + } + + public function serializeQueryToIdentity(Query $query) + { + return sprintf('%s:%s:%s', $query->name, $query->type, $query->class); + } + + public function serializeRecordToIdentity(Record $record) + { + return sprintf('%s:%s:%s', $record->name, $record->type, $record->class); + } +} diff --git a/assets/php/vendor/react/dns/src/Query/RetryExecutor.php b/assets/php/vendor/react/dns/src/Query/RetryExecutor.php new file mode 100644 index 0000000..90353e5 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/RetryExecutor.php @@ -0,0 +1,44 @@ +<?php + +namespace React\Dns\Query; + +use React\Promise\Deferred; + +class RetryExecutor implements ExecutorInterface +{ + private $executor; + private $retries; + + public function __construct(ExecutorInterface $executor, $retries = 2) + { + $this->executor = $executor; + $this->retries = $retries; + } + + public function query($nameserver, Query $query) + { + return $this->tryQuery($nameserver, $query, $this->retries); + } + + public function tryQuery($nameserver, Query $query, $retries) + { + $that = $this; + $errorback = function ($error) use ($nameserver, $query, $retries, $that) { + if (!$error instanceof TimeoutException) { + throw $error; + } + if (0 >= $retries) { + throw new \RuntimeException( + sprintf("DNS query for %s failed: too many retries", $query->name), + 0, + $error + ); + } + return $that->tryQuery($nameserver, $query, $retries-1); + }; + + return $this->executor + ->query($nameserver, $query) + ->then(null, $errorback); + } +} diff --git a/assets/php/vendor/react/dns/src/Query/TimeoutException.php b/assets/php/vendor/react/dns/src/Query/TimeoutException.php new file mode 100644 index 0000000..90bf806 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/TimeoutException.php @@ -0,0 +1,7 @@ +<?php + +namespace React\Dns\Query; + +class TimeoutException extends \Exception +{ +} diff --git a/assets/php/vendor/react/dns/src/Query/TimeoutExecutor.php b/assets/php/vendor/react/dns/src/Query/TimeoutExecutor.php new file mode 100644 index 0000000..6a44888 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/TimeoutExecutor.php @@ -0,0 +1,32 @@ +<?php + +namespace React\Dns\Query; + +use React\EventLoop\LoopInterface; +use React\Promise\Deferred; +use React\Promise\CancellablePromiseInterface; +use React\Promise\Timer; + +class TimeoutExecutor implements ExecutorInterface +{ + private $executor; + private $loop; + private $timeout; + + public function __construct(ExecutorInterface $executor, $timeout, LoopInterface $loop) + { + $this->executor = $executor; + $this->loop = $loop; + $this->timeout = $timeout; + } + + public function query($nameserver, Query $query) + { + return Timer\timeout($this->executor->query($nameserver, $query), $this->timeout, $this->loop)->then(null, function ($e) use ($query) { + if ($e instanceof Timer\TimeoutException) { + $e = new TimeoutException(sprintf("DNS query for %s timed out", $query->name), 0, $e); + } + throw $e; + }); + } +} diff --git a/assets/php/vendor/react/dns/src/RecordNotFoundException.php b/assets/php/vendor/react/dns/src/RecordNotFoundException.php new file mode 100644 index 0000000..0028413 --- /dev/null +++ b/assets/php/vendor/react/dns/src/RecordNotFoundException.php @@ -0,0 +1,7 @@ +<?php + +namespace React\Dns; + +class RecordNotFoundException extends \Exception +{ +} diff --git a/assets/php/vendor/react/dns/src/Resolver/Factory.php b/assets/php/vendor/react/dns/src/Resolver/Factory.php new file mode 100644 index 0000000..12a912f --- /dev/null +++ b/assets/php/vendor/react/dns/src/Resolver/Factory.php @@ -0,0 +1,103 @@ +<?php + +namespace React\Dns\Resolver; + +use React\Cache\ArrayCache; +use React\Cache\CacheInterface; +use React\Dns\Config\HostsFile; +use React\Dns\Protocol\Parser; +use React\Dns\Protocol\BinaryDumper; +use React\Dns\Query\CachedExecutor; +use React\Dns\Query\Executor; +use React\Dns\Query\ExecutorInterface; +use React\Dns\Query\HostsFileExecutor; +use React\Dns\Query\RecordCache; +use React\Dns\Query\RetryExecutor; +use React\Dns\Query\TimeoutExecutor; +use React\EventLoop\LoopInterface; + +class Factory +{ + public function create($nameserver, LoopInterface $loop) + { + $nameserver = $this->addPortToServerIfMissing($nameserver); + $executor = $this->decorateHostsFileExecutor($this->createRetryExecutor($loop)); + + return new Resolver($nameserver, $executor); + } + + public function createCached($nameserver, LoopInterface $loop, CacheInterface $cache = null) + { + if (!($cache instanceof CacheInterface)) { + $cache = new ArrayCache(); + } + + $nameserver = $this->addPortToServerIfMissing($nameserver); + $executor = $this->decorateHostsFileExecutor($this->createCachedExecutor($loop, $cache)); + + return new Resolver($nameserver, $executor); + } + + /** + * Tries to load the hosts file and decorates the given executor on success + * + * @param ExecutorInterface $executor + * @return ExecutorInterface + * @codeCoverageIgnore + */ + private function decorateHostsFileExecutor(ExecutorInterface $executor) + { + try { + $executor = new HostsFileExecutor( + HostsFile::loadFromPathBlocking(), + $executor + ); + } catch (\RuntimeException $e) { + // ignore this file if it can not be loaded + } + + // Windows does not store localhost in hosts file by default but handles this internally + // To compensate for this, we explicitly use hard-coded defaults for localhost + if (DIRECTORY_SEPARATOR === '\\') { + $executor = new HostsFileExecutor( + new HostsFile("127.0.0.1 localhost\n::1 localhost"), + $executor + ); + } + + return $executor; + } + + protected function createExecutor(LoopInterface $loop) + { + return new TimeoutExecutor( + new Executor($loop, new Parser(), new BinaryDumper(), null), + 5.0, + $loop + ); + } + + protected function createRetryExecutor(LoopInterface $loop) + { + return new RetryExecutor($this->createExecutor($loop)); + } + + protected function createCachedExecutor(LoopInterface $loop, CacheInterface $cache) + { + return new CachedExecutor($this->createRetryExecutor($loop), new RecordCache($cache)); + } + + protected function addPortToServerIfMissing($nameserver) + { + if (strpos($nameserver, '[') === false && substr_count($nameserver, ':') >= 2) { + // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets + $nameserver = '[' . $nameserver . ']'; + } + // assume a dummy scheme when checking for the port, otherwise parse_url() fails + if (parse_url('dummy://' . $nameserver, PHP_URL_PORT) === null) { + $nameserver .= ':53'; + } + + return $nameserver; + } +} diff --git a/assets/php/vendor/react/dns/src/Resolver/Resolver.php b/assets/php/vendor/react/dns/src/Resolver/Resolver.php new file mode 100644 index 0000000..4a4983a --- /dev/null +++ b/assets/php/vendor/react/dns/src/Resolver/Resolver.php @@ -0,0 +1,100 @@ +<?php + +namespace React\Dns\Resolver; + +use React\Dns\Query\ExecutorInterface; +use React\Dns\Query\Query; +use React\Dns\RecordNotFoundException; +use React\Dns\Model\Message; + +class Resolver +{ + private $nameserver; + private $executor; + + public function __construct($nameserver, ExecutorInterface $executor) + { + $this->nameserver = $nameserver; + $this->executor = $executor; + } + + public function resolve($domain) + { + $query = new Query($domain, Message::TYPE_A, Message::CLASS_IN, time()); + $that = $this; + + return $this->executor + ->query($this->nameserver, $query) + ->then(function (Message $response) use ($query, $that) { + return $that->extractAddress($query, $response); + }); + } + + public function extractAddress(Query $query, Message $response) + { + $answers = $response->answers; + + $addresses = $this->resolveAliases($answers, $query->name); + + if (0 === count($addresses)) { + $message = 'DNS Request did not return valid answer.'; + throw new RecordNotFoundException($message); + } + + $address = $addresses[array_rand($addresses)]; + return $address; + } + + public function resolveAliases(array $answers, $name) + { + $named = $this->filterByName($answers, $name); + $aRecords = $this->filterByType($named, Message::TYPE_A); + $cnameRecords = $this->filterByType($named, Message::TYPE_CNAME); + + if ($aRecords) { + return $this->mapRecordData($aRecords); + } + + if ($cnameRecords) { + $aRecords = array(); + + $cnames = $this->mapRecordData($cnameRecords); + foreach ($cnames as $cname) { + $targets = $this->filterByName($answers, $cname); + $aRecords = array_merge( + $aRecords, + $this->resolveAliases($answers, $cname) + ); + } + + return $aRecords; + } + + return array(); + } + + private function filterByName(array $answers, $name) + { + return $this->filterByField($answers, 'name', $name); + } + + private function filterByType(array $answers, $type) + { + return $this->filterByField($answers, 'type', $type); + } + + private function filterByField(array $answers, $field, $value) + { + $value = strtolower($value); + return array_filter($answers, function ($answer) use ($field, $value) { + return $value === strtolower($answer->$field); + }); + } + + private function mapRecordData(array $records) + { + return array_map(function ($record) { + return $record->data; + }, $records); + } +} diff --git a/assets/php/vendor/react/dns/tests/CallableStub.php b/assets/php/vendor/react/dns/tests/CallableStub.php new file mode 100644 index 0000000..a34a263 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/CallableStub.php @@ -0,0 +1,10 @@ +<?php + +namespace React\Tests\Dns; + +class CallableStub +{ + public function __invoke() + { + } +} diff --git a/assets/php/vendor/react/dns/tests/Config/ConfigTest.php b/assets/php/vendor/react/dns/tests/Config/ConfigTest.php new file mode 100644 index 0000000..8020408 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Config/ConfigTest.php @@ -0,0 +1,189 @@ +<?php + +namespace React\Tests\Dns\Config; + +use React\Tests\Dns\TestCase; +use React\Dns\Config\Config; + +class ConfigTest extends TestCase +{ + public function testLoadsSystemDefault() + { + $config = Config::loadSystemConfigBlocking(); + + $this->assertInstanceOf('React\Dns\Config\Config', $config); + } + + public function testLoadsDefaultPath() + { + if (DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('Not supported on Windows'); + } + + $config = Config::loadResolvConfBlocking(); + + $this->assertInstanceOf('React\Dns\Config\Config', $config); + } + + public function testLoadsFromExplicitPath() + { + $config = Config::loadResolvConfBlocking(__DIR__ . '/../Fixtures/etc/resolv.conf'); + + $this->assertEquals(array('8.8.8.8'), $config->nameservers); + } + + /** + * @expectedException RuntimeException + */ + public function testLoadThrowsWhenPathIsInvalid() + { + Config::loadResolvConfBlocking(__DIR__ . '/invalid.conf'); + } + + public function testParsesSingleEntryFile() + { + $contents = 'nameserver 8.8.8.8'; + $expected = array('8.8.8.8'); + + $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents)); + $this->assertEquals($expected, $config->nameservers); + } + + public function testParsesNameserverEntriesFromAverageFileCorrectly() + { + $contents = '# +# Mac OS X Notice +# +# This file is not used by the host name and address resolution +# or the DNS query routing mechanisms used by most processes on +# this Mac OS X system. +# +# This file is automatically generated. +# +domain v.cablecom.net +nameserver 127.0.0.1 +nameserver ::1 +'; + $expected = array('127.0.0.1', '::1'); + + $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents)); + $this->assertEquals($expected, $config->nameservers); + } + + public function testParsesEmptyFileWithoutNameserverEntries() + { + $contents = ''; + $expected = array(); + + $config = Config::loadResolvConfBlocking('data://text/plain;base64,'); + $this->assertEquals($expected, $config->nameservers); + } + + public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries() + { + $contents = ' +# nameserver 1.2.3.4 +; nameserver 2.3.4.5 + +nameserver 3.4.5.6 # nope +nameserver 4.5.6.7 5.6.7.8 + nameserver 6.7.8.9 +NameServer 7.8.9.10 +'; + $expected = array(); + + $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents)); + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsFromWmicOnWindows() + { + if (DIRECTORY_SEPARATOR !== '\\') { + $this->markTestSkipped('Only on Windows'); + } + + $config = Config::loadWmicBlocking(); + + $this->assertInstanceOf('React\Dns\Config\Config', $config); + } + + public function testLoadsSingleEntryFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +ACE,{192.168.2.1} +ACE, +'; + $expected = array('192.168.2.1'); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsEmptyListFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +'; + $expected = array(); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsSingleEntryForMultipleNicsFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +ACE,{192.168.2.1} +ACE, +ACE,{192.168.2.2} +ACE, +'; + $expected = array('192.168.2.1', '192.168.2.2'); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsMultipleEntriesForSingleNicWithSemicolonFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +ACE,{192.168.2.1;192.168.2.2} +ACE, +'; + $expected = array('192.168.2.1', '192.168.2.2'); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsMultipleEntriesForSingleNicWithQuotesFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +ACE,{"192.168.2.1","192.168.2.2"} +ACE, +'; + $expected = array('192.168.2.1', '192.168.2.2'); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + private function echoCommand($output) + { + return 'echo ' . escapeshellarg($output); + } +} diff --git a/assets/php/vendor/react/dns/tests/Config/FilesystemFactoryTest.php b/assets/php/vendor/react/dns/tests/Config/FilesystemFactoryTest.php new file mode 100644 index 0000000..bb9eac7 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Config/FilesystemFactoryTest.php @@ -0,0 +1,70 @@ +<?php + +namespace React\Test\Dns\Config; + +use PHPUnit\Framework\TestCase; +use React\Dns\Config\FilesystemFactory; + +class FilesystemFactoryTest extends TestCase +{ + /** @test */ + public function parseEtcResolvConfShouldParseCorrectly() + { + $contents = '# +# Mac OS X Notice +# +# This file is not used by the host name and address resolution +# or the DNS query routing mechanisms used by most processes on +# this Mac OS X system. +# +# This file is automatically generated. +# +domain v.cablecom.net +nameserver 127.0.0.1 +nameserver 8.8.8.8 +'; + $expected = array('127.0.0.1', '8.8.8.8'); + + $capturedConfig = null; + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $factory = new FilesystemFactory($loop); + $factory->parseEtcResolvConf($contents)->then(function ($config) use (&$capturedConfig) { + $capturedConfig = $config; + }); + + $this->assertNotNull($capturedConfig); + $this->assertSame($expected, $capturedConfig->nameservers); + } + + /** @test */ + public function createShouldLoadStuffFromFilesystem() + { + $this->markTestIncomplete('Filesystem API is incomplete'); + + $expected = array('8.8.8.8'); + + $triggerListener = null; + $capturedConfig = null; + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop + ->expects($this->once()) + ->method('addReadStream') + ->will($this->returnCallback(function ($stream, $listener) use (&$triggerListener) { + $triggerListener = function () use ($stream, $listener) { + call_user_func($listener, $stream); + }; + })); + + $factory = new FilesystemFactory($loop); + $factory->create(__DIR__.'/../Fixtures/etc/resolv.conf')->then(function ($config) use (&$capturedConfig) { + $capturedConfig = $config; + }); + + $triggerListener(); + + $this->assertNotNull($capturedConfig); + $this->assertSame($expected, $capturedConfig->nameservers); + } +} diff --git a/assets/php/vendor/react/dns/tests/Config/HostsFileTest.php b/assets/php/vendor/react/dns/tests/Config/HostsFileTest.php new file mode 100644 index 0000000..ff74ad2 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Config/HostsFileTest.php @@ -0,0 +1,170 @@ +<?php + +namespace React\Tests\Dns\Config; + +use React\Tests\Dns\TestCase; +use React\Dns\Config\HostsFile; + +class HostsFileTest extends TestCase +{ + public function testLoadsFromDefaultPath() + { + $hosts = HostsFile::loadFromPathBlocking(); + + $this->assertInstanceOf('React\Dns\Config\HostsFile', $hosts); + } + + public function testDefaultShouldHaveLocalhostMapped() + { + if (DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('Not supported on Windows'); + } + + $hosts = HostsFile::loadFromPathBlocking(); + + $this->assertContains('127.0.0.1', $hosts->getIpsForHost('localhost')); + } + + /** + * @expectedException RuntimeException + */ + public function testLoadThrowsForInvalidPath() + { + HostsFile::loadFromPathBlocking('does/not/exist'); + } + + public function testContainsSingleLocalhostEntry() + { + $hosts = new HostsFile('127.0.0.1 localhost'); + + $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost')); + $this->assertEquals(array(), $hosts->getIpsForHost('example.com')); + } + + public function testNonIpReturnsNothingForInvalidHosts() + { + $hosts = new HostsFile('a b'); + + $this->assertEquals(array(), $hosts->getIpsForHost('a')); + $this->assertEquals(array(), $hosts->getIpsForHost('b')); + } + + public function testIgnoresIpv6ZoneId() + { + $hosts = new HostsFile('fe80::1%lo0 localhost'); + + $this->assertEquals(array('fe80::1'), $hosts->getIpsForHost('localhost')); + } + + public function testSkipsComments() + { + $hosts = new HostsFile('# start' . PHP_EOL .'#127.0.0.1 localhost' . PHP_EOL . '127.0.0.2 localhost # example.com'); + + $this->assertEquals(array('127.0.0.2'), $hosts->getIpsForHost('localhost')); + $this->assertEquals(array(), $hosts->getIpsForHost('example.com')); + } + + public function testContainsSingleLocalhostEntryWithCaseIgnored() + { + $hosts = new HostsFile('127.0.0.1 LocalHost'); + + $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('LOCALHOST')); + } + + public function testEmptyFileContainsNothing() + { + $hosts = new HostsFile(''); + + $this->assertEquals(array(), $hosts->getIpsForHost('example.com')); + } + + public function testSingleEntryWithMultipleNames() + { + $hosts = new HostsFile('127.0.0.1 localhost example.com'); + + $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('example.com')); + $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost')); + } + + public function testMergesEntriesOverMultipleLines() + { + $hosts = new HostsFile("127.0.0.1 localhost\n127.0.0.2 localhost\n127.0.0.3 a localhost b\n127.0.0.4 a localhost"); + + $this->assertEquals(array('127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'), $hosts->getIpsForHost('localhost')); + } + + public function testMergesIpv4AndIpv6EntriesOverMultipleLines() + { + $hosts = new HostsFile("127.0.0.1 localhost\n::1 localhost"); + + $this->assertEquals(array('127.0.0.1', '::1'), $hosts->getIpsForHost('localhost')); + } + + public function testReverseLookup() + { + $hosts = new HostsFile('127.0.0.1 localhost'); + + $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1')); + $this->assertEquals(array(), $hosts->getHostsForIp('192.168.1.1')); + } + + public function testReverseSkipsComments() + { + $hosts = new HostsFile("# start\n#127.0.0.1 localhosted\n127.0.0.2\tlocalhost\t# example.com\n\t127.0.0.3\t\texample.org\t\t"); + + $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1')); + $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.2')); + $this->assertEquals(array('example.org'), $hosts->getHostsForIp('127.0.0.3')); + } + + public function testReverseNonIpReturnsNothing() + { + $hosts = new HostsFile('127.0.0.1 localhost'); + + $this->assertEquals(array(), $hosts->getHostsForIp('localhost')); + $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1.1')); + } + + public function testReverseNonIpReturnsNothingForInvalidHosts() + { + $hosts = new HostsFile('a b'); + + $this->assertEquals(array(), $hosts->getHostsForIp('a')); + $this->assertEquals(array(), $hosts->getHostsForIp('b')); + } + + public function testReverseLookupReturnsLowerCaseHost() + { + $hosts = new HostsFile('127.0.0.1 LocalHost'); + + $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1')); + } + + public function testReverseLookupChecksNormalizedIpv6() + { + $hosts = new HostsFile('FE80::00a1 localhost'); + + $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::A1')); + } + + public function testReverseLookupIgnoresIpv6ZoneId() + { + $hosts = new HostsFile('fe80::1%lo0 localhost'); + + $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::1')); + } + + public function testReverseLookupReturnsMultipleHostsOverSingleLine() + { + $hosts = new HostsFile("::1 ip6-localhost ip6-loopback"); + + $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1')); + } + + public function testReverseLookupReturnsMultipleHostsOverMultipleLines() + { + $hosts = new HostsFile("::1 ip6-localhost\n::1 ip6-loopback"); + + $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1')); + } +} diff --git a/assets/php/vendor/react/dns/tests/Fixtures/etc/resolv.conf b/assets/php/vendor/react/dns/tests/Fixtures/etc/resolv.conf new file mode 100644 index 0000000..cae093a --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Fixtures/etc/resolv.conf @@ -0,0 +1 @@ +nameserver 8.8.8.8 diff --git a/assets/php/vendor/react/dns/tests/FunctionalResolverTest.php b/assets/php/vendor/react/dns/tests/FunctionalResolverTest.php new file mode 100644 index 0000000..0807e86 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/FunctionalResolverTest.php @@ -0,0 +1,71 @@ +<?php + +namespace React\Tests\Dns; + +use React\Tests\Dns\TestCase; +use React\EventLoop\Factory as LoopFactory; +use React\Dns\Resolver\Resolver; +use React\Dns\Resolver\Factory; + +class FunctionalTest extends TestCase +{ + public function setUp() + { + $this->loop = LoopFactory::create(); + + $factory = new Factory(); + $this->resolver = $factory->create('8.8.8.8', $this->loop); + } + + public function testResolveLocalhostResolves() + { + $promise = $this->resolver->resolve('localhost'); + $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); + + $this->loop->run(); + } + + /** + * @group internet + */ + public function testResolveGoogleResolves() + { + $promise = $this->resolver->resolve('google.com'); + $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); + + $this->loop->run(); + } + + /** + * @group internet + */ + public function testResolveInvalidRejects() + { + $promise = $this->resolver->resolve('example.invalid'); + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + + $this->loop->run(); + } + + public function testResolveCancelledRejectsImmediately() + { + $promise = $this->resolver->resolve('google.com'); + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + $promise->cancel(); + + $time = microtime(true); + $this->loop->run(); + $time = microtime(true) - $time; + + $this->assertLessThan(0.1, $time); + } + + public function testInvalidResolverDoesNotResolveGoogle() + { + $factory = new Factory(); + $this->resolver = $factory->create('255.255.255.255', $this->loop); + + $promise = $this->resolver->resolve('google.com'); + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + } +} diff --git a/assets/php/vendor/react/dns/tests/Model/MessageTest.php b/assets/php/vendor/react/dns/tests/Model/MessageTest.php new file mode 100644 index 0000000..53d6b28 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Model/MessageTest.php @@ -0,0 +1,30 @@ +<?php + +namespace React\Tests\Dns\Model; + +use PHPUnit\Framework\TestCase; +use React\Dns\Query\Query; +use React\Dns\Model\Message; + +class MessageTest extends TestCase +{ + public function testCreateRequestDesiresRecusion() + { + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $request = Message::createRequestForQuery($query); + + $this->assertTrue($request->header->isQuery()); + $this->assertSame(1, $request->header->get('rd')); + } + + public function testCreateResponseWithNoAnswers() + { + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $answers = array(); + $request = Message::createResponseWithAnswersForQuery($query, $answers); + + $this->assertFalse($request->header->isQuery()); + $this->assertTrue($request->header->isResponse()); + $this->assertEquals(0, $request->header->get('anCount')); + } +} diff --git a/assets/php/vendor/react/dns/tests/Protocol/BinaryDumperTest.php b/assets/php/vendor/react/dns/tests/Protocol/BinaryDumperTest.php new file mode 100644 index 0000000..bf60ca9 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Protocol/BinaryDumperTest.php @@ -0,0 +1,48 @@ +<?php + +namespace React\Tests\Dns\Protocol; + +use PHPUnit\Framework\TestCase; +use React\Dns\Protocol\BinaryDumper; +use React\Dns\Model\Message; + +class BinaryDumperTest extends TestCase +{ + public function testRequestToBinary() + { + $data = ""; + $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header + $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io + $data .= "00 01 00 01"; // question: type A, class IN + + $expected = $this->formatHexDump(str_replace(' ', '', $data), 2); + + $request = new Message(); + $request->header->set('id', 0x7262); + $request->header->set('rd', 1); + + $request->questions[] = array( + 'name' => 'igor.io', + 'type' => Message::TYPE_A, + 'class' => Message::CLASS_IN, + ); + + $request->prepare(); + + $dumper = new BinaryDumper(); + $data = $dumper->toBinary($request); + $data = $this->convertBinaryToHexDump($data); + + $this->assertSame($expected, $data); + } + + private function convertBinaryToHexDump($input) + { + return $this->formatHexDump(implode('', unpack('H*', $input))); + } + + private function formatHexDump($input) + { + return implode(' ', str_split($input, 2)); + } +} diff --git a/assets/php/vendor/react/dns/tests/Protocol/ParserTest.php b/assets/php/vendor/react/dns/tests/Protocol/ParserTest.php new file mode 100644 index 0000000..195fad2 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Protocol/ParserTest.php @@ -0,0 +1,343 @@ +<?php + +namespace React\Tests\Dns\Protocol; + +use PHPUnit\Framework\TestCase; +use React\Dns\Protocol\Parser; +use React\Dns\Model\Message; + +class ParserTest extends TestCase +{ + public function setUp() + { + $this->parser = new Parser(); + } + + /** + * @dataProvider provideConvertTcpDumpToBinary + */ + public function testConvertTcpDumpToBinary($expected, $data) + { + $this->assertSame($expected, $this->convertTcpDumpToBinary($data)); + } + + public function provideConvertTcpDumpToBinary() + { + return array( + array(chr(0x72).chr(0x62), "72 62"), + array(chr(0x72).chr(0x62).chr(0x01).chr(0x00), "72 62 01 00"), + array(chr(0x72).chr(0x62).chr(0x01).chr(0x00).chr(0x00).chr(0x01), "72 62 01 00 00 01"), + array(chr(0x01).chr(0x00).chr(0x01), "01 00 01"), + ); + } + + public function testParseRequest() + { + $data = ""; + $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header + $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io + $data .= "00 01 00 01"; // question: type A, class IN + + $data = $this->convertTcpDumpToBinary($data); + + $request = $this->parser->parseMessage($data); + + $header = $request->header; + $this->assertSame(0x7262, $header->get('id')); + $this->assertSame(1, $header->get('qdCount')); + $this->assertSame(0, $header->get('anCount')); + $this->assertSame(0, $header->get('nsCount')); + $this->assertSame(0, $header->get('arCount')); + $this->assertSame(0, $header->get('qr')); + $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode')); + $this->assertSame(0, $header->get('aa')); + $this->assertSame(0, $header->get('tc')); + $this->assertSame(1, $header->get('rd')); + $this->assertSame(0, $header->get('ra')); + $this->assertSame(0, $header->get('z')); + $this->assertSame(Message::RCODE_OK, $header->get('rcode')); + + $this->assertCount(1, $request->questions); + $this->assertSame('igor.io', $request->questions[0]['name']); + $this->assertSame(Message::TYPE_A, $request->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']); + } + + public function testParseResponse() + { + $data = ""; + $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header + $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io + $data .= "00 01 00 01"; // question: type A, class IN + $data .= "c0 0c"; // answer: offset pointer to igor.io + $data .= "00 01 00 01"; // answer: type A, class IN + $data .= "00 01 51 80"; // answer: ttl 86400 + $data .= "00 04"; // answer: rdlength 4 + $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131 + + $data = $this->convertTcpDumpToBinary($data); + + $response = $this->parser->parseMessage($data); + + $header = $response->header; + $this->assertSame(0x7262, $header->get('id')); + $this->assertSame(1, $header->get('qdCount')); + $this->assertSame(1, $header->get('anCount')); + $this->assertSame(0, $header->get('nsCount')); + $this->assertSame(0, $header->get('arCount')); + $this->assertSame(1, $header->get('qr')); + $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode')); + $this->assertSame(0, $header->get('aa')); + $this->assertSame(0, $header->get('tc')); + $this->assertSame(1, $header->get('rd')); + $this->assertSame(1, $header->get('ra')); + $this->assertSame(0, $header->get('z')); + $this->assertSame(Message::RCODE_OK, $header->get('rcode')); + + $this->assertCount(1, $response->questions); + $this->assertSame('igor.io', $response->questions[0]['name']); + $this->assertSame(Message::TYPE_A, $response->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']); + + $this->assertCount(1, $response->answers); + $this->assertSame('igor.io', $response->answers[0]->name); + $this->assertSame(Message::TYPE_A, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(86400, $response->answers[0]->ttl); + $this->assertSame('178.79.169.131', $response->answers[0]->data); + } + + public function testParseQuestionWithTwoQuestions() + { + $data = ""; + $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io + $data .= "00 01 00 01"; // question: type A, class IN + $data .= "03 77 77 77 04 69 67 6f 72 02 69 6f 00"; // question: www.igor.io + $data .= "00 01 00 01"; // question: type A, class IN + + $data = $this->convertTcpDumpToBinary($data); + + $request = new Message(); + $request->header->set('qdCount', 2); + $request->data = $data; + + $this->parser->parseQuestion($request); + + $this->assertCount(2, $request->questions); + $this->assertSame('igor.io', $request->questions[0]['name']); + $this->assertSame(Message::TYPE_A, $request->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']); + $this->assertSame('www.igor.io', $request->questions[1]['name']); + $this->assertSame(Message::TYPE_A, $request->questions[1]['type']); + $this->assertSame(Message::CLASS_IN, $request->questions[1]['class']); + } + + public function testParseAnswerWithInlineData() + { + $data = ""; + $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io + $data .= "00 01 00 01"; // answer: type A, class IN + $data .= "00 01 51 80"; // answer: ttl 86400 + $data .= "00 04"; // answer: rdlength 4 + $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131 + + $data = $this->convertTcpDumpToBinary($data); + + $response = new Message(); + $response->header->set('anCount', 1); + $response->data = $data; + + $this->parser->parseAnswer($response); + + $this->assertCount(1, $response->answers); + $this->assertSame('igor.io', $response->answers[0]->name); + $this->assertSame(Message::TYPE_A, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(86400, $response->answers[0]->ttl); + $this->assertSame('178.79.169.131', $response->answers[0]->data); + } + + public function testParseResponseWithCnameAndOffsetPointers() + { + $data = ""; + $data .= "9e 8d 81 80 00 01 00 01 00 00 00 00"; // header + $data .= "04 6d 61 69 6c 06 67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: mail.google.com + $data .= "00 05 00 01"; // question: type CNAME, class IN + $data .= "c0 0c"; // answer: offset pointer to mail.google.com + $data .= "00 05 00 01"; // answer: type CNAME, class IN + $data .= "00 00 a8 9c"; // answer: ttl 43164 + $data .= "00 0f"; // answer: rdlength 15 + $data .= "0a 67 6f 6f 67 6c 65 6d 61 69 6c 01 6c"; // answer: rdata googlemail.l. + $data .= "c0 11"; // answer: rdata offset pointer to google.com + + $data = $this->convertTcpDumpToBinary($data); + + $response = $this->parser->parseMessage($data); + + $this->assertCount(1, $response->questions); + $this->assertSame('mail.google.com', $response->questions[0]['name']); + $this->assertSame(Message::TYPE_CNAME, $response->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']); + + $this->assertCount(1, $response->answers); + $this->assertSame('mail.google.com', $response->answers[0]->name); + $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(43164, $response->answers[0]->ttl); + $this->assertSame('googlemail.l.google.com', $response->answers[0]->data); + } + + public function testParseAAAAResponse() + { + $data = ""; + $data .= "cd 72 81 80 00 01 00 01 00 00 00 00 06"; // header + $data .= "67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: google.com + $data .= "00 1c 00 01"; // question: type AAAA, class IN + $data .= "c0 0c"; // answer: offset pointer to google.com + $data .= "00 1c 00 01"; // answer: type AAAA, class IN + $data .= "00 00 01 2b"; // answer: ttl 299 + $data .= "00 10"; // answer: rdlength 16 + $data .= "2a 00 14 50 40 09 08 09 00 00 00 00 00 00 20 0e"; // answer: 2a00:1450:4009:809::200e + + $data = $this->convertTcpDumpToBinary($data); + + $response = $this->parser->parseMessage($data); + + $header = $response->header; + $this->assertSame(0xcd72, $header->get('id')); + $this->assertSame(1, $header->get('qdCount')); + $this->assertSame(1, $header->get('anCount')); + $this->assertSame(0, $header->get('nsCount')); + $this->assertSame(0, $header->get('arCount')); + $this->assertSame(1, $header->get('qr')); + $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode')); + $this->assertSame(0, $header->get('aa')); + $this->assertSame(0, $header->get('tc')); + $this->assertSame(1, $header->get('rd')); + $this->assertSame(1, $header->get('ra')); + $this->assertSame(0, $header->get('z')); + $this->assertSame(Message::RCODE_OK, $header->get('rcode')); + + $this->assertCount(1, $response->questions); + $this->assertSame('google.com', $response->questions[0]['name']); + $this->assertSame(Message::TYPE_AAAA, $response->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']); + + $this->assertCount(1, $response->answers); + $this->assertSame('google.com', $response->answers[0]->name); + $this->assertSame(Message::TYPE_AAAA, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(299, $response->answers[0]->ttl); + $this->assertSame('2a00:1450:4009:809::200e', $response->answers[0]->data); + } + + public function testParseResponseWithTwoAnswers() + { + $data = ""; + $data .= "bc 73 81 80 00 01 00 02 00 00 00 00"; // header + $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00"; + // question: io.whois-servers.net + $data .= "00 01 00 01"; // question: type A, class IN + $data .= "c0 0c"; // answer: offset pointer to io.whois-servers.net + $data .= "00 05 00 01"; // answer: type CNAME, class IN + $data .= "00 00 00 29"; // answer: ttl 41 + $data .= "00 0e"; // answer: rdlength 14 + $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // answer: rdata whois.nic.io + $data .= "c0 32"; // answer: offset pointer to whois.nic.io + $data .= "00 01 00 01"; // answer: type CNAME, class IN + $data .= "00 00 0d f7"; // answer: ttl 3575 + $data .= "00 04"; // answer: rdlength 4 + $data .= "c1 df 4e 98"; // answer: rdata 193.223.78.152 + + $data = $this->convertTcpDumpToBinary($data); + + $response = $this->parser->parseMessage($data); + + $this->assertCount(1, $response->questions); + $this->assertSame('io.whois-servers.net', $response->questions[0]['name']); + $this->assertSame(Message::TYPE_A, $response->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']); + + $this->assertCount(2, $response->answers); + + $this->assertSame('io.whois-servers.net', $response->answers[0]->name); + $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(41, $response->answers[0]->ttl); + $this->assertSame('whois.nic.io', $response->answers[0]->data); + + $this->assertSame('whois.nic.io', $response->answers[1]->name); + $this->assertSame(Message::TYPE_A, $response->answers[1]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[1]->class); + $this->assertSame(3575, $response->answers[1]->ttl); + $this->assertSame('193.223.78.152', $response->answers[1]->data); + } + + public function testParsePTRResponse() + { + $data = ""; + $data .= "5d d8 81 80 00 01 00 01 00 00 00 00"; // header + $data .= "01 34 01 34 01 38 01 38 07 69 6e"; // question: 4.4.8.8.in-addr.arpa + $data .= "2d 61 64 64 72 04 61 72 70 61 00"; // question (continued) + $data .= "00 0c 00 01"; // question: type PTR, class IN + $data .= "c0 0c"; // answer: offset pointer to rdata + $data .= "00 0c 00 01"; // answer: type PTR, class IN + $data .= "00 01 51 7f"; // answer: ttl 86399 + $data .= "00 20"; // answer: rdlength 32 + $data .= "13 67 6f 6f 67 6c 65 2d 70 75 62 6c 69 63 2d 64"; // answer: rdata google-public-dns-b.google.com. + $data .= "6e 73 2d 62 06 67 6f 6f 67 6c 65 03 63 6f 6d 00"; + + $data = $this->convertTcpDumpToBinary($data); + + $response = $this->parser->parseMessage($data); + + $header = $response->header; + $this->assertSame(0x5dd8, $header->get('id')); + $this->assertSame(1, $header->get('qdCount')); + $this->assertSame(1, $header->get('anCount')); + $this->assertSame(0, $header->get('nsCount')); + $this->assertSame(0, $header->get('arCount')); + $this->assertSame(1, $header->get('qr')); + $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode')); + $this->assertSame(0, $header->get('aa')); + $this->assertSame(0, $header->get('tc')); + $this->assertSame(1, $header->get('rd')); + $this->assertSame(1, $header->get('ra')); + $this->assertSame(0, $header->get('z')); + $this->assertSame(Message::RCODE_OK, $header->get('rcode')); + + $this->assertCount(1, $response->questions); + $this->assertSame('4.4.8.8.in-addr.arpa', $response->questions[0]['name']); + $this->assertSame(Message::TYPE_PTR, $response->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']); + + $this->assertCount(1, $response->answers); + $this->assertSame('4.4.8.8.in-addr.arpa', $response->answers[0]->name); + $this->assertSame(Message::TYPE_PTR, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(86399, $response->answers[0]->ttl); + $this->assertSame('google-public-dns-b.google.com', $response->answers[0]->data); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testParseIncomplete() + { + $data = ""; + $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header + $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io + //$data .= "00 01 00 01"; // question: type A, class IN + + $data = $this->convertTcpDumpToBinary($data); + + $this->parser->parseMessage($data); + } + + private function convertTcpDumpToBinary($input) + { + // sudo ngrep -d en1 -x port 53 + + return pack('H*', str_replace(' ', '', $input)); + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/CachedExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/CachedExecutorTest.php new file mode 100644 index 0000000..d08ed05 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/CachedExecutorTest.php @@ -0,0 +1,100 @@ +<?php + +namespace React\Tests\Dns\Query; + +use React\Tests\Dns\TestCase; +use React\Dns\Query\CachedExecutor; +use React\Dns\Query\Query; +use React\Dns\Model\Message; +use React\Dns\Model\Record; +use React\Promise; + +class CachedExecutorTest extends TestCase +{ + /** + * @covers React\Dns\Query\CachedExecutor + * @test + */ + public function queryShouldDelegateToDecoratedExecutor() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnValue($this->createPromiseMock())); + + $cache = $this->getMockBuilder('React\Dns\Query\RecordCache') + ->disableOriginalConstructor() + ->getMock(); + $cache + ->expects($this->once()) + ->method('lookup') + ->will($this->returnValue(Promise\reject())); + $cachedExecutor = new CachedExecutor($executor, $cache); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $cachedExecutor->query('8.8.8.8', $query); + } + + /** + * @covers React\Dns\Query\CachedExecutor + * @test + */ + public function callingQueryTwiceShouldUseCachedResult() + { + $cachedRecords = array(new Record('igor.io', Message::TYPE_A, Message::CLASS_IN)); + + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->will($this->callQueryCallbackWithAddress('178.79.169.131')); + + $cache = $this->getMockBuilder('React\Dns\Query\RecordCache') + ->disableOriginalConstructor() + ->getMock(); + $cache + ->expects($this->at(0)) + ->method('lookup') + ->with($this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnValue(Promise\reject())); + $cache + ->expects($this->at(1)) + ->method('storeResponseMessage') + ->with($this->isType('integer'), $this->isInstanceOf('React\Dns\Model\Message')); + $cache + ->expects($this->at(2)) + ->method('lookup') + ->with($this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnValue(Promise\resolve($cachedRecords))); + + $cachedExecutor = new CachedExecutor($executor, $cache); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {}); + $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {}); + } + + private function callQueryCallbackWithAddress($address) + { + return $this->returnCallback(function ($nameserver, $query) use ($address) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record($query->name, $query->type, $query->class); + $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, $address); + + return Promise\resolve($response); + }); + } + + private function createExecutorMock() + { + return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + } + + private function createPromiseMock() + { + return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock(); + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/ExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/ExecutorTest.php new file mode 100644 index 0000000..0d7ac1d --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/ExecutorTest.php @@ -0,0 +1,308 @@ +<?php + +namespace React\Tests\Dns\Query; + +use Clue\React\Block; +use React\Dns\Query\Executor; +use React\Dns\Query\Query; +use React\Dns\Model\Message; +use React\Dns\Model\Record; +use React\Dns\Protocol\BinaryDumper; +use React\Tests\Dns\TestCase; + +class ExecutorTest extends TestCase +{ + private $loop; + private $parser; + private $dumper; + private $executor; + + public function setUp() + { + $this->loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $this->parser = $this->getMockBuilder('React\Dns\Protocol\Parser')->getMock(); + $this->dumper = new BinaryDumper(); + + $this->executor = new Executor($this->loop, $this->parser, $this->dumper); + } + + /** @test */ + public function queryShouldCreateUdpRequest() + { + $timer = $this->createTimerMock(); + $this->loop + ->expects($this->any()) + ->method('addTimer') + ->will($this->returnValue($timer)); + + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->once()) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->returnNewConnectionMock(false)); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $this->executor->query('8.8.8.8:53', $query); + } + + /** @test */ + public function resolveShouldRejectIfRequestIsLargerThan512Bytes() + { + $query = new Query(str_repeat('a', 512).'.igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $this->setExpectedException('RuntimeException', 'DNS query for ' . $query->name . ' failed: Requested transport "tcp" not available, only UDP is supported in this version'); + Block\await($promise, $this->loop); + } + + /** @test */ + public function resolveShouldCloseConnectionWhenCancelled() + { + $conn = $this->createConnectionMock(false); + $conn->expects($this->once())->method('close'); + + $timer = $this->createTimerMock(); + $this->loop + ->expects($this->any()) + ->method('addTimer') + ->will($this->returnValue($timer)); + + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->once()) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->returnValue($conn)); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $promise->cancel(); + + $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled'); + Block\await($promise, $this->loop); + } + + /** @test */ + public function resolveShouldNotStartOrCancelTimerWhenCancelledWithTimeoutIsNull() + { + $this->loop + ->expects($this->never()) + ->method('addTimer'); + + $this->executor = new Executor($this->loop, $this->parser, $this->dumper, null); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('127.0.0.1:53', $query); + + $promise->cancel(); + + $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled'); + Block\await($promise, $this->loop); + } + + /** @test */ + public function resolveShouldRejectIfResponseIsTruncated() + { + $timer = $this->createTimerMock(); + + $this->loop + ->expects($this->any()) + ->method('addTimer') + ->will($this->returnValue($timer)); + + $this->parser + ->expects($this->once()) + ->method('parseMessage') + ->will($this->returnTruncatedResponse()); + + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->once()) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->returnNewConnectionMock()); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $this->executor->query('8.8.8.8:53', $query); + } + + /** @test */ + public function resolveShouldFailIfUdpThrow() + { + $this->loop + ->expects($this->never()) + ->method('addTimer'); + + $this->parser + ->expects($this->never()) + ->method('parseMessage'); + + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->once()) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->throwException(new \Exception('Nope'))); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $this->setExpectedException('RuntimeException', 'DNS query for igor.io failed: Nope'); + Block\await($promise, $this->loop); + } + + /** @test */ + public function resolveShouldCancelTimerWhenFullResponseIsReceived() + { + $conn = $this->createConnectionMock(); + + $this->parser + ->expects($this->once()) + ->method('parseMessage') + ->will($this->returnStandardResponse()); + + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->at(0)) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->returnNewConnectionMock()); + + + $timer = $this->createTimerMock(); + + $this->loop + ->expects($this->once()) + ->method('addTimer') + ->with(5, $this->isInstanceOf('Closure')) + ->will($this->returnValue($timer)); + + $this->loop + ->expects($this->once()) + ->method('cancelTimer') + ->with($timer); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $this->executor->query('8.8.8.8:53', $query); + } + + /** @test */ + public function resolveShouldCloseConnectionOnTimeout() + { + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->at(0)) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->returnNewConnectionMock(false)); + + $timer = $this->createTimerMock(); + + $this->loop + ->expects($this->never()) + ->method('cancelTimer'); + + $this->loop + ->expects($this->once()) + ->method('addTimer') + ->with(5, $this->isInstanceOf('Closure')) + ->will($this->returnCallback(function ($time, $callback) use (&$timerCallback, $timer) { + $timerCallback = $callback; + return $timer; + })); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $this->assertNotNull($timerCallback); + $timerCallback(); + + $this->setExpectedException('React\Dns\Query\TimeoutException', 'DNS query for igor.io timed out'); + Block\await($promise, $this->loop); + } + + private function returnStandardResponse() + { + $that = $this; + $callback = function ($data) use ($that) { + $response = new Message(); + $that->convertMessageToStandardResponse($response); + return $response; + }; + + return $this->returnCallback($callback); + } + + private function returnTruncatedResponse() + { + $that = $this; + $callback = function ($data) use ($that) { + $response = new Message(); + $that->convertMessageToTruncatedResponse($response); + return $response; + }; + + return $this->returnCallback($callback); + } + + public function convertMessageToStandardResponse(Message $response) + { + $response->header->set('qr', 1); + $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN); + $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'); + $response->prepare(); + + return $response; + } + + public function convertMessageToTruncatedResponse(Message $response) + { + $this->convertMessageToStandardResponse($response); + $response->header->set('tc', 1); + $response->prepare(); + + return $response; + } + + private function returnNewConnectionMock($emitData = true) + { + $conn = $this->createConnectionMock($emitData); + + $callback = function () use ($conn) { + return $conn; + }; + + return $this->returnCallback($callback); + } + + private function createConnectionMock($emitData = true) + { + $conn = $this->getMockBuilder('React\Stream\DuplexStreamInterface')->getMock(); + $conn + ->expects($this->any()) + ->method('on') + ->with('data', $this->isInstanceOf('Closure')) + ->will($this->returnCallback(function ($name, $callback) use ($emitData) { + $emitData && $callback(null); + })); + + return $conn; + } + + private function createTimerMock() + { + return $this->getMockBuilder( + interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface' + )->getMock(); + } + + private function createExecutorMock() + { + return $this->getMockBuilder('React\Dns\Query\Executor') + ->setConstructorArgs(array($this->loop, $this->parser, $this->dumper)) + ->setMethods(array('createConnection')) + ->getMock(); + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/HostsFileExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/HostsFileExecutorTest.php new file mode 100644 index 0000000..70d877e --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/HostsFileExecutorTest.php @@ -0,0 +1,126 @@ +<?php + +namespace React\Tests\Dns\Query; + +use React\Tests\Dns\TestCase; +use React\Dns\Query\HostsFileExecutor; +use React\Dns\Query\Query; +use React\Dns\Model\Message; + +class HostsFileExecutorTest extends TestCase +{ + private $hosts; + private $fallback; + private $executor; + + public function setUp() + { + $this->hosts = $this->getMockBuilder('React\Dns\Config\HostsFile')->disableOriginalConstructor()->getMock(); + $this->fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + $this->executor = new HostsFileExecutor($this->hosts, $this->fallback); + } + + public function testDoesNotTryToGetIpsForMxQuery() + { + $this->hosts->expects($this->never())->method('getIpsForHost'); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_MX, Message::CLASS_IN, 0)); + } + + public function testFallsBackIfNoIpsWereFound() + { + $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array()); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0)); + } + + public function testReturnsResponseMessageIfIpsWereFound() + { + $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1')); + $this->fallback->expects($this->never())->method('query'); + + $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0)); + } + + public function testFallsBackIfNoIpv4Matches() + { + $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1')); + $this->fallback->expects($this->once())->method('query'); + + $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0)); + } + + public function testReturnsResponseMessageIfIpv6AddressesWereFound() + { + $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1')); + $this->fallback->expects($this->never())->method('query'); + + $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0)); + } + + public function testFallsBackIfNoIpv6Matches() + { + $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1')); + $this->fallback->expects($this->once())->method('query'); + + $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0)); + } + + public function testDoesReturnReverseIpv4Lookup() + { + $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array('localhost')); + $this->fallback->expects($this->never())->method('query'); + + $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testFallsBackIfNoReverseIpv4Matches() + { + $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array()); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testDoesReturnReverseIpv6Lookup() + { + $this->hosts->expects($this->once())->method('getHostsForIp')->with('2a02:2e0:3fe:100::6')->willReturn(array('ip6-localhost')); + $this->fallback->expects($this->never())->method('query'); + + $this->executor->query('8.8.8.8', new Query('6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.e.f.3.0.0.e.2.0.2.0.a.2.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testFallsBackForInvalidAddress() + { + $this->hosts->expects($this->never())->method('getHostsForIp'); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('example.com', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testReverseFallsBackForInvalidIpv4Address() + { + $this->hosts->expects($this->never())->method('getHostsForIp'); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('::1.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testReverseFallsBackForInvalidLengthIpv6Address() + { + $this->hosts->expects($this->never())->method('getHostsForIp'); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('abcd.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testReverseFallsBackForInvalidHexIpv6Address() + { + $this->hosts->expects($this->never())->method('getHostsForIp'); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('zZz.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/RecordBagTest.php b/assets/php/vendor/react/dns/tests/Query/RecordBagTest.php new file mode 100644 index 0000000..83b8934 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/RecordBagTest.php @@ -0,0 +1,64 @@ +<?php + +namespace React\Tests\Dns\Query; + +use PHPUnit\Framework\TestCase; +use React\Dns\Query\RecordBag; +use React\Dns\Model\Message; +use React\Dns\Model\Record; + +class RecordBagTest extends TestCase +{ + /** + * @covers React\Dns\Query\RecordBag + * @test + */ + public function emptyBagShouldBeEmpty() + { + $recordBag = new RecordBag(); + + $this->assertSame(array(), $recordBag->all()); + } + + /** + * @covers React\Dns\Query\RecordBag + * @test + */ + public function setShouldSetTheValue() + { + $currentTime = 1345656451; + + $recordBag = new RecordBag(); + $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600)); + + $records = $recordBag->all(); + $this->assertCount(1, $records); + $this->assertSame('igor.io', $records[0]->name); + $this->assertSame(Message::TYPE_A, $records[0]->type); + $this->assertSame(Message::CLASS_IN, $records[0]->class); + } + + /** + * @covers React\Dns\Query\RecordBag + * @test + */ + public function setShouldSetManyValues() + { + $currentTime = 1345656451; + + $recordBag = new RecordBag(); + $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131')); + $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132')); + + $records = $recordBag->all(); + $this->assertCount(2, $records); + $this->assertSame('igor.io', $records[0]->name); + $this->assertSame(Message::TYPE_A, $records[0]->type); + $this->assertSame(Message::CLASS_IN, $records[0]->class); + $this->assertSame('178.79.169.131', $records[0]->data); + $this->assertSame('igor.io', $records[1]->name); + $this->assertSame(Message::TYPE_A, $records[1]->type); + $this->assertSame(Message::CLASS_IN, $records[1]->class); + $this->assertSame('178.79.169.132', $records[1]->data); + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/RecordCacheTest.php b/assets/php/vendor/react/dns/tests/Query/RecordCacheTest.php new file mode 100644 index 0000000..399fbe8 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/RecordCacheTest.php @@ -0,0 +1,123 @@ +<?php + +namespace React\Tests\Dns\Query; + +use PHPUnit\Framework\TestCase; +use React\Cache\ArrayCache; +use React\Dns\Model\Message; +use React\Dns\Model\Record; +use React\Dns\Query\RecordCache; +use React\Dns\Query\Query; +use React\Promise\PromiseInterface; + +class RecordCacheTest extends TestCase +{ + /** + * @covers React\Dns\Query\RecordCache + * @test + */ + public function lookupOnEmptyCacheShouldReturnNull() + { + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + + $cache = new RecordCache(new ArrayCache()); + $promise = $cache->lookup($query); + + $this->assertInstanceOf('React\Promise\RejectedPromise', $promise); + } + + /** + * @covers React\Dns\Query\RecordCache + * @test + */ + public function storeRecordShouldMakeLookupSucceed() + { + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + + $cache = new RecordCache(new ArrayCache()); + $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131')); + $promise = $cache->lookup($query); + + $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise); + $cachedRecords = $this->getPromiseValue($promise); + + $this->assertCount(1, $cachedRecords); + $this->assertSame('178.79.169.131', $cachedRecords[0]->data); + } + + /** + * @covers React\Dns\Query\RecordCache + * @test + */ + public function storeTwoRecordsShouldReturnBoth() + { + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + + $cache = new RecordCache(new ArrayCache()); + $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131')); + $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132')); + $promise = $cache->lookup($query); + + $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise); + $cachedRecords = $this->getPromiseValue($promise); + + $this->assertCount(2, $cachedRecords); + $this->assertSame('178.79.169.131', $cachedRecords[0]->data); + $this->assertSame('178.79.169.132', $cachedRecords[1]->data); + } + + /** + * @covers React\Dns\Query\RecordCache + * @test + */ + public function storeResponseMessageShouldStoreAllAnswerValues() + { + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + + $response = new Message(); + $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'); + $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'); + $response->prepare(); + + $cache = new RecordCache(new ArrayCache()); + $cache->storeResponseMessage($query->currentTime, $response); + $promise = $cache->lookup($query); + + $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise); + $cachedRecords = $this->getPromiseValue($promise); + + $this->assertCount(2, $cachedRecords); + $this->assertSame('178.79.169.131', $cachedRecords[0]->data); + $this->assertSame('178.79.169.132', $cachedRecords[1]->data); + } + + /** + * @covers React\Dns\Query\RecordCache + * @test + */ + public function expireShouldExpireDeadRecords() + { + $cachedTime = 1345656451; + $currentTime = $cachedTime + 3605; + + $cache = new RecordCache(new ArrayCache()); + $cache->storeRecord($cachedTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131')); + $cache->expire($currentTime); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, $currentTime); + $promise = $cache->lookup($query); + + $this->assertInstanceOf('React\Promise\RejectedPromise', $promise); + } + + private function getPromiseValue(PromiseInterface $promise) + { + $capturedValue = null; + + $promise->then(function ($value) use (&$capturedValue) { + $capturedValue = $value; + }); + + return $capturedValue; + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/RetryExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/RetryExecutorTest.php new file mode 100644 index 0000000..8950f84 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/RetryExecutorTest.php @@ -0,0 +1,197 @@ +<?php + +namespace React\Tests\Dns\Query; + +use React\Tests\Dns\TestCase; +use React\Dns\Query\RetryExecutor; +use React\Dns\Query\Query; +use React\Dns\Model\Message; +use React\Dns\Query\TimeoutException; +use React\Dns\Model\Record; +use React\Promise; +use React\Promise\Deferred; +use React\Dns\Query\CancellationException; + +class RetryExecutorTest extends TestCase +{ + /** + * @covers React\Dns\Query\RetryExecutor + * @test + */ + public function queryShouldDelegateToDecoratedExecutor() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnValue($this->expectPromiseOnce())); + + $retryExecutor = new RetryExecutor($executor, 2); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $retryExecutor->query('8.8.8.8', $query); + } + + /** + * @covers React\Dns\Query\RetryExecutor + * @test + */ + public function queryShouldRetryQueryOnTimeout() + { + $response = $this->createStandardResponse(); + + $executor = $this->createExecutorMock(); + $executor + ->expects($this->exactly(2)) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->onConsecutiveCalls( + $this->returnCallback(function ($domain, $query) { + return Promise\reject(new TimeoutException("timeout")); + }), + $this->returnCallback(function ($domain, $query) use ($response) { + return Promise\resolve($response); + }) + )); + + $callback = $this->createCallableMock(); + $callback + ->expects($this->once()) + ->method('__invoke') + ->with($this->isInstanceOf('React\Dns\Model\Message')); + + $errorback = $this->expectCallableNever(); + + $retryExecutor = new RetryExecutor($executor, 2); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback); + } + + /** + * @covers React\Dns\Query\RetryExecutor + * @test + */ + public function queryShouldStopRetryingAfterSomeAttempts() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->exactly(3)) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($domain, $query) { + return Promise\reject(new TimeoutException("timeout")); + })); + + $callback = $this->expectCallableNever(); + + $errorback = $this->createCallableMock(); + $errorback + ->expects($this->once()) + ->method('__invoke') + ->with($this->isInstanceOf('RuntimeException')); + + $retryExecutor = new RetryExecutor($executor, 2); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback); + } + + /** + * @covers React\Dns\Query\RetryExecutor + * @test + */ + public function queryShouldForwardNonTimeoutErrors() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($domain, $query) { + return Promise\reject(new \Exception); + })); + + $callback = $this->expectCallableNever(); + + $errorback = $this->createCallableMock(); + $errorback + ->expects($this->once()) + ->method('__invoke') + ->with($this->isInstanceOf('Exception')); + + $retryExecutor = new RetryExecutor($executor, 2); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback); + } + + /** + * @covers React\Dns\Query\RetryExecutor + * @test + */ + public function queryShouldCancelQueryOnCancel() + { + $cancelled = 0; + + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) { + $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) { + ++$cancelled; + $reject(new CancellationException('Cancelled')); + }); + + return $deferred->promise(); + }) + ); + + $retryExecutor = new RetryExecutor($executor, 2); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $retryExecutor->query('8.8.8.8', $query); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + + $this->assertEquals(0, $cancelled); + $promise->cancel(); + $this->assertEquals(1, $cancelled); + } + + protected function expectPromiseOnce($return = null) + { + $mock = $this->createPromiseMock(); + $mock + ->expects($this->once()) + ->method('then') + ->will($this->returnValue($return)); + + return $mock; + } + + protected function createExecutorMock() + { + return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + } + + protected function createPromiseMock() + { + return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock(); + } + + protected function createStandardResponse() + { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN); + $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'); + $response->prepare(); + + return $response; + } +} + diff --git a/assets/php/vendor/react/dns/tests/Query/TimeoutExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/TimeoutExecutorTest.php new file mode 100644 index 0000000..0d37fb4 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/TimeoutExecutorTest.php @@ -0,0 +1,115 @@ +<?php + +namespace React\Tests\Dns\Query; + +use React\Dns\Query\TimeoutExecutor; +use React\Dns\Query\Query; +use React\Dns\Model\Message; +use React\Promise\Deferred; +use React\Dns\Query\CancellationException; +use React\Tests\Dns\TestCase; +use React\EventLoop\Factory; +use React\Promise; + +class TimeoutExecutorTest extends TestCase +{ + public function setUp() + { + $this->loop = Factory::create(); + + $this->wrapped = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + + $this->executor = new TimeoutExecutor($this->wrapped, 5.0, $this->loop); + } + + public function testCancellingPromiseWillCancelWrapped() + { + $cancelled = 0; + + $this->wrapped + ->expects($this->once()) + ->method('query') + ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) { + $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) { + ++$cancelled; + $reject(new CancellationException('Cancelled')); + }); + + return $deferred->promise(); + })); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $this->assertEquals(0, $cancelled); + $promise->cancel(); + $this->assertEquals(1, $cancelled); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + } + + public function testResolvesPromiseWhenWrappedResolves() + { + $this->wrapped + ->expects($this->once()) + ->method('query') + ->willReturn(Promise\resolve('0.0.0.0')); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); + } + + public function testRejectsPromiseWhenWrappedRejects() + { + $this->wrapped + ->expects($this->once()) + ->method('query') + ->willReturn(Promise\reject(new \RuntimeException())); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith(new \RuntimeException())); + } + + public function testWrappedWillBeCancelledOnTimeout() + { + $this->executor = new TimeoutExecutor($this->wrapped, 0, $this->loop); + + $cancelled = 0; + + $this->wrapped + ->expects($this->once()) + ->method('query') + ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) { + $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) { + ++$cancelled; + $reject(new CancellationException('Cancelled')); + }); + + return $deferred->promise(); + })); + + $callback = $this->expectCallableNever(); + + $errorback = $this->createCallableMock(); + $errorback + ->expects($this->once()) + ->method('__invoke') + ->with($this->logicalAnd( + $this->isInstanceOf('React\Dns\Query\TimeoutException'), + $this->attribute($this->equalTo('DNS query for igor.io timed out'), 'message') + )); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $this->executor->query('8.8.8.8:53', $query)->then($callback, $errorback); + + $this->assertEquals(0, $cancelled); + + $this->loop->run(); + + $this->assertEquals(1, $cancelled); + } +} diff --git a/assets/php/vendor/react/dns/tests/Resolver/FactoryTest.php b/assets/php/vendor/react/dns/tests/Resolver/FactoryTest.php new file mode 100644 index 0000000..acaeac0 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Resolver/FactoryTest.php @@ -0,0 +1,131 @@ +<?php + +namespace React\Tests\Dns\Resolver; + +use React\Dns\Resolver\Factory; +use React\Tests\Dns\TestCase; +use React\Dns\Query\HostsFileExecutor; + +class FactoryTest extends TestCase +{ + /** @test */ + public function createShouldCreateResolver() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->create('8.8.8.8:53', $loop); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + } + + /** @test */ + public function createWithoutPortShouldCreateResolverWithDefaultPort() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->create('8.8.8.8', $loop); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + $this->assertSame('8.8.8.8:53', $this->getResolverPrivateMemberValue($resolver, 'nameserver')); + } + + /** @test */ + public function createCachedShouldCreateResolverWithCachedExecutor() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->createCached('8.8.8.8:53', $loop); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + $executor = $this->getResolverPrivateExecutor($resolver); + $this->assertInstanceOf('React\Dns\Query\CachedExecutor', $executor); + $recordCache = $this->getCachedExecutorPrivateMemberValue($executor, 'cache'); + $recordCacheCache = $this->getRecordCachePrivateMemberValue($recordCache, 'cache'); + $this->assertInstanceOf('React\Cache\CacheInterface', $recordCacheCache); + $this->assertInstanceOf('React\Cache\ArrayCache', $recordCacheCache); + } + + /** @test */ + public function createCachedShouldCreateResolverWithCachedExecutorWithCustomCache() + { + $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock(); + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->createCached('8.8.8.8:53', $loop, $cache); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + $executor = $this->getResolverPrivateExecutor($resolver); + $this->assertInstanceOf('React\Dns\Query\CachedExecutor', $executor); + $recordCache = $this->getCachedExecutorPrivateMemberValue($executor, 'cache'); + $recordCacheCache = $this->getRecordCachePrivateMemberValue($recordCache, 'cache'); + $this->assertInstanceOf('React\Cache\CacheInterface', $recordCacheCache); + $this->assertSame($cache, $recordCacheCache); + } + + /** + * @test + * @dataProvider factoryShouldAddDefaultPortProvider + */ + public function factoryShouldAddDefaultPort($input, $expected) + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->create($input, $loop); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + $this->assertSame($expected, $this->getResolverPrivateMemberValue($resolver, 'nameserver')); + } + + public static function factoryShouldAddDefaultPortProvider() + { + return array( + array('8.8.8.8', '8.8.8.8:53'), + array('1.2.3.4:5', '1.2.3.4:5'), + array('localhost', 'localhost:53'), + array('localhost:1234', 'localhost:1234'), + array('::1', '[::1]:53'), + array('[::1]:53', '[::1]:53') + ); + } + + private function getResolverPrivateExecutor($resolver) + { + $executor = $this->getResolverPrivateMemberValue($resolver, 'executor'); + + // extract underlying executor that may be wrapped in multiple layers of hosts file executors + while ($executor instanceof HostsFileExecutor) { + $reflector = new \ReflectionProperty('React\Dns\Query\HostsFileExecutor', 'fallback'); + $reflector->setAccessible(true); + + $executor = $reflector->getValue($executor); + } + + return $executor; + } + + private function getResolverPrivateMemberValue($resolver, $field) + { + $reflector = new \ReflectionProperty('React\Dns\Resolver\Resolver', $field); + $reflector->setAccessible(true); + return $reflector->getValue($resolver); + } + + private function getCachedExecutorPrivateMemberValue($resolver, $field) + { + $reflector = new \ReflectionProperty('React\Dns\Query\CachedExecutor', $field); + $reflector->setAccessible(true); + return $reflector->getValue($resolver); + } + + private function getRecordCachePrivateMemberValue($resolver, $field) + { + $reflector = new \ReflectionProperty('React\Dns\Query\RecordCache', $field); + $reflector->setAccessible(true); + return $reflector->getValue($resolver); + } +} diff --git a/assets/php/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php b/assets/php/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php new file mode 100644 index 0000000..b5175e3 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php @@ -0,0 +1,100 @@ +<?php + +namespace React\Tests\Dns\Resolver; + +use PHPUnit\Framework\TestCase; +use React\Dns\Resolver\Resolver; +use React\Dns\Query\Query; +use React\Dns\Model\Message; +use React\Dns\Model\Record; + +class ResolveAliasesTest extends TestCase +{ + /** + * @covers React\Dns\Resolver\Resolver::resolveAliases + * @dataProvider provideAliasedAnswers + */ + public function testResolveAliases(array $expectedAnswers, array $answers, $name) + { + $executor = $this->createExecutorMock(); + $resolver = new Resolver('8.8.8.8:53', $executor); + + $answers = $resolver->resolveAliases($answers, $name); + + $this->assertEquals($expectedAnswers, $answers); + } + + public function provideAliasedAnswers() + { + return array( + array( + array('178.79.169.131'), + array( + new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + ), + 'igor.io', + ), + array( + array('178.79.169.131', '178.79.169.132', '178.79.169.133'), + array( + new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'), + new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'), + ), + 'igor.io', + ), + array( + array('178.79.169.131'), + array( + new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + ), + 'igor.io', + ), + array( + array(), + array( + new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN), + new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN), + ), + 'igor.io', + ), + array( + array('178.79.169.131'), + array( + new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'), + new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + ), + 'igor.io', + ), + array( + array('178.79.169.131'), + array( + new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'), + new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'), + new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + ), + 'igor.io', + ), + array( + array('178.79.169.131', '178.79.169.132', '178.79.169.133'), + array( + new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'), + new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'), + new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'baz.igor.io'), + new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'qux.igor.io'), + new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'), + new Record('qux.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'), + ), + 'igor.io', + ), + ); + } + + private function createExecutorMock() + { + return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + } +} diff --git a/assets/php/vendor/react/dns/tests/Resolver/ResolverTest.php b/assets/php/vendor/react/dns/tests/Resolver/ResolverTest.php new file mode 100644 index 0000000..e11509b --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Resolver/ResolverTest.php @@ -0,0 +1,129 @@ +<?php + +namespace React\Tests\Dns\Resolver; + +use React\Dns\Resolver\Resolver; +use React\Dns\Query\Query; +use React\Dns\Model\Message; +use React\Dns\Model\Record; +use React\Promise; +use React\Tests\Dns\TestCase; + +class ResolverTest extends TestCase +{ + /** @test */ + public function resolveShouldQueryARecords() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($nameserver, $query) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record($query->name, $query->type, $query->class); + $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '178.79.169.131'); + + return Promise\resolve($response); + })); + + $resolver = new Resolver('8.8.8.8:53', $executor); + $resolver->resolve('igor.io')->then($this->expectCallableOnceWith('178.79.169.131')); + } + + /** @test */ + public function resolveShouldQueryARecordsAndIgnoreCase() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($nameserver, $query) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class); + $response->answers[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class, 3600, '178.79.169.131'); + + return Promise\resolve($response); + })); + + $resolver = new Resolver('8.8.8.8:53', $executor); + $resolver->resolve('blog.wyrihaximus.net')->then($this->expectCallableOnceWith('178.79.169.131')); + } + + /** @test */ + public function resolveShouldFilterByName() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($nameserver, $query) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record($query->name, $query->type, $query->class); + $response->answers[] = new Record('foo.bar', $query->type, $query->class, 3600, '178.79.169.131'); + + return Promise\resolve($response); + })); + + $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException')); + + $resolver = new Resolver('8.8.8.8:53', $executor); + $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback); + } + + /** @test */ + public function resolveWithNoAnswersShouldThrowException() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($nameserver, $query) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record($query->name, $query->type, $query->class); + + return Promise\resolve($response); + })); + + $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException')); + + $resolver = new Resolver('8.8.8.8:53', $executor); + $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback); + } + + /** + * @test + */ + public function resolveWithNoAnswersShouldCallErrbackIfGiven() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($nameserver, $query) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record($query->name, $query->type, $query->class); + + return Promise\resolve($response); + })); + + $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException')); + + $resolver = new Resolver('8.8.8.8:53', $executor); + $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback); + } + + private function createExecutorMock() + { + return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + } +} diff --git a/assets/php/vendor/react/dns/tests/TestCase.php b/assets/php/vendor/react/dns/tests/TestCase.php new file mode 100644 index 0000000..a5a22bf --- /dev/null +++ b/assets/php/vendor/react/dns/tests/TestCase.php @@ -0,0 +1,61 @@ +<?php + +namespace React\Tests\Dns; + +use PHPUnit\Framework\TestCase as BaseTestCase; + +abstract class TestCase extends BaseTestCase +{ + protected function expectCallableOnce() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableOnceWith($value) + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($value); + + return $mock; + } + + protected function expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + protected function createCallableMock() + { + return $this->getMockBuilder('React\Tests\Dns\CallableStub')->getMock(); + } + + public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null) + { + if (method_exists($this, 'expectException')) { + // PHPUnit 5 + $this->expectException($exception); + if ($exceptionMessage !== '') { + $this->expectExceptionMessage($exceptionMessage); + } + if ($exceptionCode !== null) { + $this->expectExceptionCode($exceptionCode); + } + } else { + // legacy PHPUnit 4 + parent::setExpectedException($exception, $exceptionMessage, $exceptionCode); + } + } +} |