This commit is contained in:
2022-10-23 01:39:27 +02:00
parent 8c17aab483
commit 1929b84685
4130 changed files with 479334 additions and 0 deletions

2
vendor/react/dns/.gitignore vendored Executable file
View File

@@ -0,0 +1,2 @@
composer.lock
vendor

32
vendor/react/dns/.travis.yml vendored Executable file
View File

@@ -0,0 +1,32 @@
language: php
php:
# - 5.3 # requires old distro, see below
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- 7.3
# - hhvm # requires legacy phpunit & ignore errors, see below
# lock distro so new future defaults will not break the build
dist: trusty
matrix:
include:
- php: 5.3
dist: precise
- php: hhvm
install: composer require phpunit/phpunit:^5 --dev --no-interaction
allow_failures:
- php: hhvm
sudo: false
install:
- composer install --no-interaction
script:
- vendor/bin/phpunit --coverage-text

261
vendor/react/dns/CHANGELOG.md vendored Executable file
View File

@@ -0,0 +1,261 @@
# Changelog
## 0.4.19 (2019-07-10)
* Feature: Avoid garbage references when DNS resolution rejects on legacy PHP <= 5.6.
(#133 by @clue)
## 0.4.18 (2019-09-07)
* Feature / Fix: Implement `CachingExecutor` using cache TTL, deprecate old `CachedExecutor`,
respect TTL from response records when caching and do not cache truncated responses.
(#129 by @clue)
* Feature: Limit cache size to 256 last responses by default.
(#127 by @clue)
* Feature: Cooperatively resolve hosts to avoid running same query concurrently.
(#125 by @clue)
## 0.4.17 (2019-04-01)
* Feature: Support parsing `authority` and `additional` records from DNS response.
(#123 by @clue)
* Feature: Support dumping records as part of outgoing binary DNS message.
(#124 by @clue)
* Feature: Forward compatibility with upcoming Cache v0.6 and Cache v1.0
(#121 by @clue)
* Improve test suite to add forward compatibility with PHPUnit 7,
test against PHP 7.3 and use legacy PHPUnit 5 on legacy HHVM.
(#122 by @clue)
## 0.4.16 (2018-11-11)
* Feature: Improve promise cancellation for DNS lookup retries and clean up any garbage references.
(#118 by @clue)
* Fix: Reject parsing malformed DNS response messages such as incomplete DNS response messages,
malformed record data or malformed compressed domain name labels.
(#115 and #117 by @clue)
* Fix: Fix interpretation of TTL as UINT32 with most significant bit unset.
(#116 by @clue)
* Fix: Fix caching advanced MX/SRV/TXT/SOA structures.
(#112 by @clue)
## 0.4.15 (2018-07-02)
* Feature: Add `resolveAll()` method to support custom query types in `Resolver`.
(#110 by @clue and @WyriHaximus)
```php
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
});
```
* Feature: Support parsing `NS`, `TXT`, `MX`, `SOA` and `SRV` records.
(#104, #105, #106, #107 and #108 by @clue)
* Feature: Add support for `Message::TYPE_ANY` and parse unknown types as binary data.
(#104 by @clue)
* Feature: Improve error messages for failed queries and improve documentation.
(#109 by @clue)
* Feature: Add reverse DNS lookup example.
(#111 by @clue)
## 0.4.14 (2018-06-26)
* Feature: Add `UdpTransportExecutor`, validate incoming DNS response messages
to avoid cache poisoning attacks and deprecate legacy `Executor`.
(#101 and #103 by @clue)
* Feature: Forward compatibility with Cache 0.5
(#102 by @clue)
* Deprecate legacy `Query::$currentTime` and binary parser data attributes to clean up and simplify API.
(#99 by @clue)
## 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

19
vendor/react/dns/LICENSE vendored Executable file
View File

@@ -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.

344
vendor/react/dns/README.md vendored Executable file
View File

@@ -0,0 +1,344 @@
# Dns
[![Build Status](https://travis-ci.org/reactphp/dns.svg?branch=master)](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)
* [Resolver](#resolver)
* [resolve()](#resolve)
* [resolveAll()](#resolveall)
* [Advanced usage](#advanced-usage)
* [UdpTransportExecutor](#udptransportexecutor)
* [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.
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).
## Resolver
### resolve()
The `resolve(string $domain): PromiseInterface<string,Exception>` method can be used to
resolve the given $domain name to a single IPv4 address (type `A` query).
```php
$resolver->resolve('reactphp.org')->then(function ($ip) {
echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
});
```
This is one of the main methods in this package. It sends a DNS query
for the given $domain name to your DNS server and returns a single IP
address on success.
If the DNS server sends a DNS response message that contains more than
one IP address for this query, it will randomly pick one of the IP
addresses from the response. If you want the full list of IP addresses
or want to send a different type of query, you should use the
[`resolveAll()`](#resolveall) method instead.
If the DNS server sends a DNS response message that indicates an error
code, this method will reject with a `RecordNotFoundException`. Its
message and code can be used to check for the response code.
If the DNS communication fails and the server does not respond with a
valid response message, this message will reject with an `Exception`.
Pending DNS queries can be cancelled by cancelling its pending promise like so:
```php
$promise = $resolver->resolve('reactphp.org');
$promise->cancel();
```
### resolveAll()
The `resolveAll(string $host, int $type): PromiseInterface<array,Exception>` method can be used to
resolve all record values for the given $domain name and query $type.
```php
$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
});
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
});
```
This is one of the main methods in this package. It sends a DNS query
for the given $domain name to your DNS server and returns a list with all
record values on success.
If the DNS server sends a DNS response message that contains one or more
records for this query, it will return a list with all record values
from the response. You can use the `Message::TYPE_*` constants to control
which type of query will be sent. Note that this method always returns a
list of record values, but each record value type depends on the query
type. For example, it returns the IPv4 addresses for type `A` queries,
the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
`CNAME` and `PTR` queries and structured data for other queries. See also
the `Record` documentation for more details.
If the DNS server sends a DNS response message that indicates an error
code, this method will reject with a `RecordNotFoundException`. Its
message and code can be used to check for the response code.
If the DNS communication fails and the server does not respond with a
valid response message, this message will reject with an `Exception`.
Pending DNS queries can be cancelled by cancelling its pending promise like so:
```php
$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
$promise->cancel();
```
## Advanced Usage
### UdpTransportExecutor
The `UdpTransportExecutor` can be used to
send DNS queries over a UDP transport.
This is the main class that sends a DNS query to your DNS server and is used
internally by the `Resolver` for the actual message transport.
For more advanced usages one can utilize this class directly.
The following example looks up the `IPv6` address for `igor.io`.
```php
$loop = Factory::create();
$executor = new UdpTransportExecutor($loop);
$executor->query(
'8.8.8.8:53',
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
)->then(function (Message $message) {
foreach ($message->answers as $answer) {
echo 'IPv6: ' . $answer->data . PHP_EOL;
}
}, 'printf');
$loop->run();
```
See also the [fourth example](examples).
Note that this executor does not implement a timeout, so you will very likely
want to use this in combination with a `TimeoutExecutor` like this:
```php
$executor = new TimeoutExecutor(
new UdpTransportExecutor($loop),
3.0,
$loop
);
```
Also note that this executor uses an unreliable UDP transport and that it
does not implement any retry logic, so you will likely want to use this in
combination with a `RetryExecutor` like this:
```php
$executor = new RetryExecutor(
new TimeoutExecutor(
new UdpTransportExecutor($loop),
3.0,
$loop
)
);
```
Note that this executor is entirely async and as such allows you to execute
any number of queries concurrently. You should probably limit the number of
concurrent queries in your application or you're very likely going to face
rate limitations and bans on the resolver end. For many common applications,
you may want to avoid sending the same query multiple times when the first
one is still pending, so you will likely want to use this in combination with
a `CoopExecutor` like this:
```php
$executor = new CoopExecutor(
new RetryExecutor(
new TimeoutExecutor(
new UdpTransportExecutor($loop),
3.0,
$loop
)
)
);
```
> Internally, this class uses PHP's UDP sockets and does not take advantage
of [react/datagram](https://github.com/reactphp/datagram) purely for
organizational reasons to avoid a cyclic dependency between the two
packages. Higher-level components should take advantage of the Datagram
component instead of reimplementing this socket logic from scratch.
### HostsFileExecutor
Note that the above `UdpTransportExecutor` 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 UdpTransportExecutor($loop);
$executor = new HostsFileExecutor($hosts, $executor);
$executor->query(
'8.8.8.8:53',
new Query('localhost', Message::TYPE_A, Message::CLASS_IN)
);
```
## 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.19
```
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

24
vendor/react/dns/composer.json vendored Executable file
View File

@@ -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": "^1.0 || ^0.6 || ^0.5",
"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": "^7.0 || ^6.4 || ^5.7 || ^4.8.35"
},
"autoload": {
"psr-4": { "React\\Dns\\": "src" }
},
"autoload-dev": {
"psr-4": { "React\\Tests\\Dns\\": "tests" }
}
}

22
vendor/react/dns/examples/01-one.php vendored Executable file
View File

@@ -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();

27
vendor/react/dns/examples/02-concurrent.php vendored Executable file
View File

@@ -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();

40
vendor/react/dns/examples/03-cached.php vendored Executable file
View File

@@ -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();

31
vendor/react/dns/examples/11-all-ips.php vendored Executable file
View File

@@ -0,0 +1,31 @@
<?php
use React\Dns\Config\Config;
use React\Dns\Resolver\Factory;
use React\Dns\Model\Message;
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->resolveAll($name, Message::TYPE_A)->then(function (array $ips) use ($name) {
echo 'IPv4 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
}, function (Exception $e) use ($name) {
echo 'No IPv4 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
});
$resolver->resolveAll($name, Message::TYPE_AAAA)->then(function (array $ips) use ($name) {
echo 'IPv6 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
}, function (Exception $e) use ($name) {
echo 'No IPv6 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
});
$loop->run();

25
vendor/react/dns/examples/12-all-types.php vendored Executable file
View File

@@ -0,0 +1,25 @@
<?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] : 'google.com';
$type = constant('React\Dns\Model\Message::TYPE_' . (isset($argv[2]) ? $argv[2] : 'TXT'));
$resolver->resolveAll($name, $type)->then(function (array $values) {
var_dump($values);
}, function (Exception $e) {
echo $e->getMessage() . PHP_EOL;
});
$loop->run();

35
vendor/react/dns/examples/13-reverse-dns.php vendored Executable file
View File

@@ -0,0 +1,35 @@
<?php
use React\Dns\Config\Config;
use React\Dns\Resolver\Factory;
use React\Dns\Model\Message;
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);
$ip = isset($argv[1]) ? $argv[1] : '8.8.8.8';
if (@inet_pton($ip) === false) {
exit('Error: Given argument is not a valid IP' . PHP_EOL);
}
if (strpos($ip, ':') === false) {
$name = inet_ntop(strrev(inet_pton($ip))) . '.in-addr.arpa';
} else {
$name = wordwrap(strrev(bin2hex(inet_pton($ip))), 1, '.', true) . '.ip6.arpa';
}
$resolver->resolveAll($name, Message::TYPE_PTR)->then(function (array $names) {
var_dump($names);
}, function (Exception $e) {
echo $e->getMessage() . PHP_EOL;
});
$loop->run();

View File

@@ -0,0 +1,29 @@
<?php
use React\Dns\Model\Message;
use React\Dns\Query\Query;
use React\Dns\Query\UdpTransportExecutor;
use React\EventLoop\Factory;
require __DIR__ . '/../vendor/autoload.php';
$loop = Factory::create();
$executor = new UdpTransportExecutor($loop);
$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
$ipv4Query = new Query($name, Message::TYPE_A, Message::CLASS_IN);
$ipv6Query = new Query($name, Message::TYPE_AAAA, Message::CLASS_IN);
$executor->query('8.8.8.8:53', $ipv4Query)->then(function (Message $message) {
foreach ($message->answers as $answer) {
echo 'IPv4: ' . $answer->data . PHP_EOL;
}
}, 'printf');
$executor->query('8.8.8.8:53', $ipv6Query)->then(function (Message $message) {
foreach ($message->answers as $answer) {
echo 'IPv6: ' . $answer->data . PHP_EOL;
}
}, 'printf');
$loop->run();

71
vendor/react/dns/examples/92-query-any.php vendored Executable file
View File

@@ -0,0 +1,71 @@
<?php
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use React\Dns\Query\Query;
use React\Dns\Query\UdpTransportExecutor;
use React\EventLoop\Factory;
require __DIR__ . '/../vendor/autoload.php';
$loop = Factory::create();
$executor = new UdpTransportExecutor($loop);
$name = isset($argv[1]) ? $argv[1] : 'google.com';
$any = new Query($name, Message::TYPE_ANY, Message::CLASS_IN);
$executor->query('8.8.8.8:53', $any)->then(function (Message $message) {
foreach ($message->answers as $answer) {
/* @var $answer Record */
$data = $answer->data;
switch ($answer->type) {
case Message::TYPE_A:
$type = 'A';
break;
case Message::TYPE_AAAA:
$type = 'AAAA';
break;
case Message::TYPE_NS:
$type = 'NS';
break;
case Message::TYPE_PTR:
$type = 'PTR';
break;
case Message::TYPE_CNAME:
$type = 'CNAME';
break;
case Message::TYPE_TXT:
// TXT records can contain a list of (binary) strings for each record.
// here, we assume this is printable ASCII and simply concatenate output
$type = 'TXT';
$data = implode('', $data);
break;
case Message::TYPE_MX:
// MX records contain "priority" and "target", only dump its values here
$type = 'MX';
$data = implode(' ', $data);
break;
case Message::TYPE_SRV:
// SRV records contains priority, weight, port and target, dump structure here
$type = 'SRV';
$data = json_encode($data);
break;
case Message::TYPE_SOA:
// SOA records contain structured data, dump structure here
$type = 'SOA';
$data = json_encode($data);
break;
default:
// unknown type uses HEX format
$type = 'Type ' . $answer->type;
$data = wordwrap(strtoupper(bin2hex($data)), 2, ' ', true);
}
echo $type . ': ' . $data . PHP_EOL;
}
}, 'printf');
$loop->run();

24
vendor/react/dns/phpunit.xml.dist vendored Executable file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="React Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>

7
vendor/react/dns/src/BadServerException.php vendored Executable file
View File

@@ -0,0 +1,7 @@
<?php
namespace React\Dns;
class BadServerException extends \Exception
{
}

127
vendor/react/dns/src/Config/Config.php vendored Executable file
View File

@@ -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();
}

View File

@@ -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);
}
}
}

151
vendor/react/dns/src/Config/HostsFile.php vendored Executable file
View File

@@ -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;
}
}

59
vendor/react/dns/src/Model/HeaderBag.php vendored Executable file
View File

@@ -0,0 +1,59 @@
<?php
namespace React\Dns\Model;
class HeaderBag
{
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,
);
/**
* @deprecated unused, exists for BC only
*/
public $data = '';
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);
}
}

188
vendor/react/dns/src/Model/Message.php vendored Executable file
View File

@@ -0,0 +1,188 @@
<?php
namespace React\Dns\Model;
use React\Dns\Query\Query;
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 TYPE_SRV = 33;
const TYPE_ANY = 255;
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;
}
/**
* generates a random 16 bit message ID
*
* This uses a CSPRNG so that an outside attacker that is sending spoofed
* DNS response messages can not guess the message ID to avoid possible
* cache poisoning attacks.
*
* The `random_int()` function is only available on PHP 7+ or when
* https://github.com/paragonie/random_compat is installed. As such, using
* the latest supported PHP version is highly recommended. This currently
* falls back to a less secure random number generator on older PHP versions
* in the hope that this system is properly protected against outside
* attackers, for example by using one of the common local DNS proxy stubs.
*
* @return int
* @see self::getId()
* @codeCoverageIgnore
*/
private static function generateId()
{
if (function_exists('random_int')) {
return random_int(0, 0xffff);
}
return mt_rand(0, 0xffff);
}
/**
* @var HeaderBag
*/
public $header;
/**
* This should be an array of Query objects. For BC reasons, this currently
* references a nested array with a structure that results from casting the
* Query objects to an array:
*
* ```php
* $questions = array(
* array(
* 'name' => 'reactphp.org',
* 'type' => Message::TYPE_A,
* 'class' => Message::CLASS_IN
* )
* );
* ```
*
* @var array
* @see Query
*/
public $questions = array();
/**
* @var Record[]
*/
public $answers = array();
/**
* @var Record[]
*/
public $authority = array();
/**
* @var Record[]
*/
public $additional = array();
/**
* @deprecated still used internally for BC reasons, should not be used externally.
*/
public $data = '';
/**
* @deprecated still used internally for BC reasons, should not be used externally.
*/
public $consumed = 0;
public function __construct()
{
$this->header = new HeaderBag();
}
/**
* Returns the 16 bit message ID
*
* The response message ID has to match the request message ID. This allows
* the receiver to verify this is the correct response message. An outside
* attacker may try to inject fake responses by "guessing" the message ID,
* so this should use a proper CSPRNG to avoid possible cache poisoning.
*
* @return int
* @see self::generateId()
*/
public function getId()
{
return $this->header->get('id');
}
/**
* Returns the response code (RCODE)
*
* @return int see self::RCODE_* constants
*/
public function getResponseCode()
{
return $this->header->get('rcode');
}
public function prepare()
{
$this->header->populateCounts($this);
}
}

105
vendor/react/dns/src/Model/Record.php vendored Executable file
View File

@@ -0,0 +1,105 @@
<?php
namespace React\Dns\Model;
class Record
{
/**
* @var string hostname without trailing dot, for example "reactphp.org"
*/
public $name;
/**
* @var int see Message::TYPE_* constants (UINT16)
*/
public $type;
/**
* @var int see Message::CLASS_IN constant (UINT16)
*/
public $class;
/**
* @var int maximum TTL in seconds (UINT32, most significant bit always unset)
* @link https://tools.ietf.org/html/rfc2181#section-8
*/
public $ttl;
/**
* The payload data for this record
*
* The payload data format depends on the record type. As a rule of thumb,
* this library will try to express this in a way that can be consumed
* easily without having to worry about DNS internals and its binary transport:
*
* - A:
* IPv4 address string, for example "192.168.1.1".
* - AAAA:
* IPv6 address string, for example "::1".
* - CNAME / PTR / NS:
* The hostname without trailing dot, for example "reactphp.org".
* - TXT:
* List of string values, for example `["v=spf1 include:example.com"]`.
* This is commonly a list with only a single string value, but this
* technically allows multiple strings (0-255 bytes each) in a single
* record. This is rarely used and depending on application you may want
* to join these together or handle them separately. Each string can
* transport any binary data, its character encoding is not defined (often
* ASCII/UTF-8 in practice). [RFC 1464](https://tools.ietf.org/html/rfc1464)
* suggests using key-value pairs such as `["name=test","version=1"]`, but
* interpretation of this is not enforced and left up to consumers of this
* library (used for DNS-SD/Zeroconf and others).
* - MX:
* Mail server priority (UINT16) and target hostname without trailing dot,
* for example `{"priority":10,"target":"mx.example.com"}`.
* The payload data uses an associative array with fixed keys "priority"
* (also commonly referred to as weight or preference) and "target" (also
* referred to as exchange). If a response message contains multiple
* records of this type, targets should be sorted by priority (lowest
* first) - this is left up to consumers of this library (used for SMTP).
* - SRV:
* Service priority (UINT16), service weight (UINT16), service port (UINT16)
* and target hostname without trailing dot, for example
* `{"priority":10,"weight":50,"port":8080,"target":"example.com"}`.
* The payload data uses an associative array with fixed keys "priority",
* "weight", "port" and "target" (also referred to as name).
* The target may be an empty host name string if the service is decidedly
* not available. If a response message contains multiple records of this
* type, targets should be sorted by priority (lowest first) and selected
* randomly according to their weight - this is left up to consumers of
* this library, see also [RFC 2782](https://tools.ietf.org/html/rfc2782)
* for more details.
* - SOA:
* Includes master hostname without trailing dot, responsible person email
* as hostname without trailing dot and serial, refresh, retry, expire and
* minimum times in seconds (UINT32 each), for example:
* `{"mname":"ns.example.com","rname":"hostmaster.example.com","serial":
* 2018082601,"refresh":3600,"retry":1800,"expire":60000,"minimum":3600}`.
* - Any other unknown type:
* An opaque binary string containing the RDATA as transported in the DNS
* record. For forwards compatibility, you should not rely on this format
* for unknown types. Future versions may add support for new types and
* this may then parse the payload data appropriately - this will not be
* considered a BC break. See the format definition of known types above
* for more details.
*
* @var string|string[]|array
*/
public $data;
/**
* @param string $name
* @param int $type
* @param int $class
* @param int $ttl
* @param string|string[]|array $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;
}
}

163
vendor/react/dns/src/Protocol/BinaryDumper.php vendored Executable file
View File

@@ -0,0 +1,163 @@
<?php
namespace React\Dns\Protocol;
use React\Dns\Model\HeaderBag;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
class BinaryDumper
{
/**
* @param Message $message
* @return string
*/
public function toBinary(Message $message)
{
$data = '';
$data .= $this->headerToBinary($message->header);
$data .= $this->questionToBinary($message->questions);
$data .= $this->recordsToBinary($message->answers);
$data .= $this->recordsToBinary($message->authority);
$data .= $this->recordsToBinary($message->additional);
return $data;
}
/**
* @param HeaderBag $header
* @return string
*/
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;
}
/**
* @param array $questions
* @return string
*/
private function questionToBinary(array $questions)
{
$data = '';
foreach ($questions as $question) {
$data .= $this->domainNameToBinary($question['name']);
$data .= pack('n*', $question['type'], $question['class']);
}
return $data;
}
/**
* @param Record[] $records
* @return string
*/
private function recordsToBinary(array $records)
{
$data = '';
foreach ($records as $record) {
/* @var $record Record */
switch ($record->type) {
case Message::TYPE_A:
case Message::TYPE_AAAA:
$binary = \inet_pton($record->data);
break;
case Message::TYPE_CNAME:
case Message::TYPE_NS:
case Message::TYPE_PTR:
$binary = $this->domainNameToBinary($record->data);
break;
case Message::TYPE_TXT:
$binary = $this->textsToBinary($record->data);
break;
case Message::TYPE_MX:
$binary = \pack(
'n',
$record->data['priority']
);
$binary .= $this->domainNameToBinary($record->data['target']);
break;
case Message::TYPE_SRV:
$binary = \pack(
'n*',
$record->data['priority'],
$record->data['weight'],
$record->data['port']
);
$binary .= $this->domainNameToBinary($record->data['target']);
break;
case Message::TYPE_SOA:
$binary = $this->domainNameToBinary($record->data['mname']);
$binary .= $this->domainNameToBinary($record->data['rname']);
$binary .= \pack(
'N*',
$record->data['serial'],
$record->data['refresh'],
$record->data['retry'],
$record->data['expire'],
$record->data['minimum']
);
break;
default:
// RDATA is already stored as binary value for unknown record types
$binary = $record->data;
}
$data .= $this->domainNameToBinary($record->name);
$data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary));
$data .= $binary;
}
return $data;
}
/**
* @param string[] $texts
* @return string
*/
private function textsToBinary(array $texts)
{
$data = '';
foreach ($texts as $text) {
$data .= \chr(\strlen($text)) . $text;
}
return $data;
}
/**
* @param string $host
* @return string
*/
private function domainNameToBinary($host)
{
if ($host === '') {
return "\0";
}
return $this->textsToBinary(\explode('.', $host . '.'));
}
}

395
vendor/react/dns/src/Protocol/Parser.php vendored Executable file
View File

@@ -0,0 +1,395 @@
<?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
* @codeCoverageIgnore
*/
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;
}
}
// parse all answer records
for ($i = $message->header->get('anCount'); $i > 0; --$i) {
$record = $this->parseRecord($message);
if ($record === null) {
return;
} else {
$message->answers[] = $record;
}
}
// parse all authority records
for ($i = $message->header->get('nsCount'); $i > 0; --$i) {
$record = $this->parseRecord($message);
if ($record === null) {
return;
} else {
$message->authority[] = $record;
}
}
// parse all additional records
for ($i = $message->header->get('arCount'); $i > 0; --$i) {
$record = $this->parseRecord($message);
if ($record === null) {
return;
} else {
$message->additional[] = $record;
}
}
return $message;
}
public function parseHeader(Message $message)
{
if (!isset($message->data[12 - 1])) {
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)
{
$consumed = $message->consumed;
list($labels, $consumed) = $this->readLabels($message->data, $consumed);
if ($labels === null || !isset($message->data[$consumed + 4 - 1])) {
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;
}
/**
* recursively parse all answers from the message data into message answer records
*
* @param Message $message
* @return ?Message returns the updated message on success or null if the data is invalid/incomplete
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function parseAnswer(Message $message)
{
$record = $this->parseRecord($message);
if ($record === null) {
return null;
}
$message->answers[] = $record;
if ($message->header->get('anCount') != count($message->answers)) {
return $this->parseAnswer($message);
}
return $message;
}
/**
* @param Message $message
* @return ?Record returns parsed Record on success or null if data is invalid/incomplete
*/
private function parseRecord(Message $message)
{
$consumed = $message->consumed;
list($name, $consumed) = $this->readDomain($message->data, $consumed);
if ($name === null || !isset($message->data[$consumed + 10 - 1])) {
return null;
}
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;
// TTL is a UINT32 that must not have most significant bit set for BC reasons
if ($ttl < 0 || $ttl >= 1 << 31) {
$ttl = 0;
}
list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2)));
$consumed += 2;
if (!isset($message->data[$consumed + $rdLength - 1])) {
return null;
}
$rdata = null;
$expected = $consumed + $rdLength;
if (Message::TYPE_A === $type) {
if ($rdLength === 4) {
$rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
$consumed += $rdLength;
}
} elseif (Message::TYPE_AAAA === $type) {
if ($rdLength === 16) {
$rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
$consumed += $rdLength;
}
} elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
list($rdata, $consumed) = $this->readDomain($message->data, $consumed);
} elseif (Message::TYPE_TXT === $type) {
$rdata = array();
while ($consumed < $expected) {
$len = ord($message->data[$consumed]);
$rdata[] = (string)substr($message->data, $consumed + 1, $len);
$consumed += $len + 1;
}
} elseif (Message::TYPE_MX === $type) {
if ($rdLength > 2) {
list($priority) = array_values(unpack('n', substr($message->data, $consumed, 2)));
list($target, $consumed) = $this->readDomain($message->data, $consumed + 2);
$rdata = array(
'priority' => $priority,
'target' => $target
);
}
} elseif (Message::TYPE_SRV === $type) {
if ($rdLength > 6) {
list($priority, $weight, $port) = array_values(unpack('n*', substr($message->data, $consumed, 6)));
list($target, $consumed) = $this->readDomain($message->data, $consumed + 6);
$rdata = array(
'priority' => $priority,
'weight' => $weight,
'port' => $port,
'target' => $target
);
}
} elseif (Message::TYPE_SOA === $type) {
list($mname, $consumed) = $this->readDomain($message->data, $consumed);
list($rname, $consumed) = $this->readDomain($message->data, $consumed);
if ($mname !== null && $rname !== null && isset($message->data[$consumed + 20 - 1])) {
list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($message->data, $consumed, 20)));
$consumed += 20;
$rdata = array(
'mname' => $mname,
'rname' => $rname,
'serial' => $serial,
'refresh' => $refresh,
'retry' => $retry,
'expire' => $expire,
'minimum' => $minimum
);
}
} else {
// unknown types simply parse rdata as an opaque binary string
$rdata = substr($message->data, $consumed, $rdLength);
$consumed += $rdLength;
}
// ensure parsing record data consumes expact number of bytes indicated in record length
if ($consumed !== $expected || $rdata === null) {
return null;
}
$message->consumed = $consumed;
return new Record($name, $type, $class, $ttl, $rdata);
}
private function readDomain($data, $consumed)
{
list ($labels, $consumed) = $this->readLabels($data, $consumed);
if ($labels === null) {
return array(null, null);
}
return array(implode('.', $labels), $consumed);
}
private function readLabels($data, $consumed)
{
$labels = array();
while (true) {
if (!isset($data[$consumed])) {
return array(null, null);
}
$length = \ord($data[$consumed]);
// end of labels reached
if ($length === 0) {
$consumed += 1;
break;
}
// first two bits set? this is a compressed label (14 bit pointer offset)
if (($length & 0xc0) === 0xc0 && isset($data[$consumed + 1])) {
$offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
if ($offset >= $consumed) {
return array(null, null);
}
$consumed += 2;
list($newLabels) = $this->readLabels($data, $offset);
if ($newLabels === null) {
return array(null, null);
}
$labels = array_merge($labels, $newLabels);
break;
}
// length MUST be 0-63 (6 bits only) and data has to be large enough
if ($length & 0xc0 || !isset($data[$consumed + $length - 1])) {
return array(null, null);
}
$labels[] = substr($data, $consumed + 1, $length);
$consumed += $length + 1;
}
return array($labels, $consumed);
}
/**
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function isEndOfLabels($data, $consumed)
{
$length = ord(substr($data, $consumed, 1));
return 0 === $length;
}
/**
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function getCompressedLabel($data, $consumed)
{
list($nameOffset, $consumed) = $this->getCompressedLabelOffset($data, $consumed);
list($labels) = $this->readLabels($data, $nameOffset);
return array($labels, $consumed);
}
/**
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function isCompressedLabel($data, $consumed)
{
$mask = 0xc000; // 1100000000000000
list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
return (bool) ($peek & $mask);
}
/**
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function getCompressedLabelOffset($data, $consumed)
{
$mask = 0x3fff; // 0011111111111111
list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
return array($peek & $mask, $consumed + 2);
}
/**
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function signedLongToUnsignedLong($i)
{
return $i & 0x80000000 ? $i - 0xffffffff : $i;
}
}

59
vendor/react/dns/src/Query/CachedExecutor.php vendored Executable file
View File

@@ -0,0 +1,59 @@
<?php
namespace React\Dns\Query;
use React\Dns\Model\Message;
/**
* @deprecated unused, exists for BC only
* @see CachingExecutor
*/
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);
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace React\Dns\Query;
use React\Cache\CacheInterface;
use React\Dns\Model\Message;
use React\Promise\Promise;
class CachingExecutor implements ExecutorInterface
{
/**
* Default TTL for negative responses (NXDOMAIN etc.).
*
* @internal
*/
const TTL = 60;
private $executor;
private $cache;
public function __construct(ExecutorInterface $executor, CacheInterface $cache)
{
$this->executor = $executor;
$this->cache = $cache;
}
public function query($nameserver, Query $query)
{
$id = $query->name . ':' . $query->type . ':' . $query->class;
$cache = $this->cache;
$that = $this;
$executor = $this->executor;
$pending = $cache->get($id);
return new Promise(function ($resolve, $reject) use ($nameserver, $query, $id, $cache, $executor, &$pending, $that) {
$pending->then(
function ($message) use ($nameserver, $query, $id, $cache, $executor, &$pending, $that) {
// return cached response message on cache hit
if ($message !== null) {
return $message;
}
// perform DNS lookup if not already cached
return $pending = $executor->query($nameserver, $query)->then(
function (Message $message) use ($cache, $id, $that) {
// DNS response message received => store in cache when not truncated and return
if (!$message->header->isTruncated()) {
$cache->set($id, $message, $that->ttl($message));
}
return $message;
}
);
}
)->then($resolve, function ($e) use ($reject, &$pending) {
$reject($e);
$pending = null;
});
}, function ($_, $reject) use (&$pending, $query) {
$reject(new \RuntimeException('DNS query for ' . $query->name . ' has been cancelled'));
$pending->cancel();
$pending = null;
});
}
/**
* @param Message $message
* @return int
* @internal
*/
public function ttl(Message $message)
{
// select TTL from answers (should all be the same), use smallest value if available
// @link https://tools.ietf.org/html/rfc2181#section-5.2
$ttl = null;
foreach ($message->answers as $answer) {
if ($ttl === null || $answer->ttl < $ttl) {
$ttl = $answer->ttl;
}
}
if ($ttl === null) {
$ttl = self::TTL;
}
return $ttl;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace React\Dns\Query;
class CancellationException extends \RuntimeException
{
}

92
vendor/react/dns/src/Query/CoopExecutor.php vendored Executable file
View File

@@ -0,0 +1,92 @@
<?php
namespace React\Dns\Query;
use React\Promise\Promise;
/**
* Cooperatively resolves hosts via the given base executor to ensure same query is not run concurrently
*
* Wraps an existing `ExecutorInterface` to keep tracking of pending queries
* and only starts a new query when the same query is not already pending. Once
* the underlying query is fulfilled/rejected, it will forward its value to all
* promises awaiting the same query.
*
* This means it will not limit concurrency for queries that differ, for example
* when sending many queries for different host names or types.
*
* This is useful because all executors are entirely async and as such allow you
* to execute any number of queries concurrently. You should probably limit the
* number of concurrent queries in your application or you're very likely going
* to face rate limitations and bans on the resolver end. For many common
* applications, you may want to avoid sending the same query multiple times
* when the first one is still pending, so you will likely want to use this in
* combination with some other executor like this:
*
* ```php
* $executor = new CoopExecutor(
* new RetryExecutor(
* new TimeoutExecutor(
* new UdpTransportExecutor($loop),
* 3.0,
* $loop
* )
* )
* );
* ```
*/
class CoopExecutor implements ExecutorInterface
{
private $executor;
private $pending = array();
private $counts = array();
public function __construct(ExecutorInterface $base)
{
$this->executor = $base;
}
public function query($nameserver, Query $query)
{
$key = $this->serializeQueryToIdentity($query);
if (isset($this->pending[$key])) {
// same query is already pending, so use shared reference to pending query
$promise = $this->pending[$key];
++$this->counts[$key];
} else {
// no such query pending, so start new query and keep reference until it's fulfilled or rejected
$promise = $this->executor->query($nameserver, $query);
$this->pending[$key] = $promise;
$this->counts[$key] = 1;
$pending =& $this->pending;
$counts =& $this->counts;
$promise->then(function () use ($key, &$pending, &$counts) {
unset($pending[$key], $counts[$key]);
}, function () use ($key, &$pending, &$counts) {
unset($pending[$key], $counts[$key]);
});
}
// Return a child promise awaiting the pending query.
// Cancelling this child promise should only cancel the pending query
// when no other child promise is awaiting the same query.
$pending =& $this->pending;
$counts =& $this->counts;
return new Promise(function ($resolve, $reject) use ($promise) {
$promise->then($resolve, $reject);
}, function () use (&$promise, $key, $query, &$pending, &$counts) {
if (--$counts[$key] < 1) {
unset($pending[$key], $counts[$key]);
$promise->cancel();
$promise = null;
}
throw new \RuntimeException('DNS query for ' . $query->name . ' has been cancelled');
});
}
private function serializeQueryToIdentity(Query $query)
{
return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
}
}

160
vendor/react/dns/src/Query/Executor.php vendored Executable file
View File

@@ -0,0 +1,160 @@
<?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;
/**
* @deprecated unused, exists for BC only
* @see UdpTransportExecutor
*/
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;
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace React\Dns\Query;
interface ExecutorInterface
{
public function query($nameserver, Query $query);
}

View File

@@ -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 given 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;
}
}
}

33
vendor/react/dns/src/Query/Query.php vendored Executable file
View File

@@ -0,0 +1,33 @@
<?php
namespace React\Dns\Query;
class Query
{
public $name;
public $type;
public $class;
/**
* @deprecated still used internally for BC reasons, should not be used externally.
*/
public $currentTime;
/**
* @param string $name query name, i.e. hostname to look up
* @param int $type query type, see Message::TYPE_* constants
* @param int $class query class, see Message::CLASS_IN constant
* @param int|null $currentTime (deprecated) still used internally, should not be passed explicitly anymore.
*/
public function __construct($name, $type, $class, $currentTime = null)
{
if($currentTime === null) {
$currentTime = time();
}
$this->name = $name;
$this->type = $type;
$this->class = $class;
$this->currentTime = $currentTime;
}
}

30
vendor/react/dns/src/Query/RecordBag.php vendored Executable file
View File

@@ -0,0 +1,30 @@
<?php
namespace React\Dns\Query;
use React\Dns\Model\Record;
/**
* @deprecated unused, exists for BC only
* @see CachingExecutor
*/
class RecordBag
{
private $records = array();
public function set($currentTime, Record $record)
{
$this->records[] = array($currentTime + $record->ttl, $record);
}
public function all()
{
return array_values(array_map(
function ($value) {
list($expiresAt, $record) = $value;
return $record;
},
$this->records
));
}
}

122
vendor/react/dns/src/Query/RecordCache.php vendored Executable file
View File

@@ -0,0 +1,122 @@
<?php
namespace React\Dns\Query;
use React\Cache\CacheInterface;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use React\Promise;
use React\Promise\PromiseInterface;
/**
* Wraps an underlying cache interface and exposes only cached DNS data
*
* @deprecated unused, exists for BC only
* @see CachingExecutor
*/
class RecordCache
{
private $cache;
private $expiredAt;
public function __construct(CacheInterface $cache)
{
$this->cache = $cache;
}
/**
* Looks up the cache if there's a cached answer for the given query
*
* @param Query $query
* @return PromiseInterface Promise<Record[],mixed> resolves with array of Record objects on sucess
* or rejects with mixed values when query is not cached already.
*/
public function lookup(Query $query)
{
$id = $this->serializeQueryToIdentity($query);
$expiredAt = $this->expiredAt;
return $this->cache
->get($id)
->then(function ($value) use ($query, $expiredAt) {
// reject on cache miss
if ($value === null) {
return Promise\reject();
}
/* @var $recordBag RecordBag */
$recordBag = unserialize($value);
// reject this cache hit if the query was started before the time we expired the cache?
// todo: this is a legacy left over, this value is never actually set, so this never applies.
// todo: this should probably validate the cache time instead.
if (null !== $expiredAt && $expiredAt <= $query->currentTime) {
return Promise\reject();
}
return $recordBag->all();
});
}
/**
* Stores all records from this response message in the cache
*
* @param int $currentTime
* @param Message $message
* @uses self::storeRecord()
*/
public function storeResponseMessage($currentTime, Message $message)
{
foreach ($message->answers as $record) {
$this->storeRecord($currentTime, $record);
}
}
/**
* Stores a single record from a response message in the cache
*
* @param int $currentTime
* @param Record $record
*/
public function storeRecord($currentTime, Record $record)
{
$id = $this->serializeRecordToIdentity($record);
$cache = $this->cache;
$this->cache
->get($id)
->then(
function ($value) {
// return empty bag on cache miss
if ($value === null) {
return new RecordBag();
}
// reuse existing bag on cache hit to append new record to it
return unserialize($value);
}
)
->then(function (RecordBag $recordBag) use ($id, $currentTime, $record, $cache) {
// add a record to the existing (possibly empty) record bag and save to 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);
}
}

79
vendor/react/dns/src/Query/RetryExecutor.php vendored Executable file
View File

@@ -0,0 +1,79 @@
<?php
namespace React\Dns\Query;
use React\Promise\CancellablePromiseInterface;
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)
{
$deferred = new Deferred(function () use (&$promise) {
if ($promise instanceof CancellablePromiseInterface) {
$promise->cancel();
}
});
$success = function ($value) use ($deferred, &$errorback) {
$errorback = null;
$deferred->resolve($value);
};
$executor = $this->executor;
$errorback = function ($e) use ($deferred, &$promise, $nameserver, $query, $success, &$errorback, &$retries, $executor) {
if (!$e instanceof TimeoutException) {
$errorback = null;
$deferred->reject($e);
} elseif ($retries <= 0) {
$errorback = null;
$deferred->reject($e = new \RuntimeException(
'DNS query for ' . $query->name . ' failed: too many retries',
0,
$e
));
// avoid garbage references by replacing all closures in call stack.
// what a lovely piece of code!
$r = new \ReflectionProperty('Exception', 'trace');
$r->setAccessible(true);
$trace = $r->getValue($e);
foreach ($trace as &$one) {
foreach ($one['args'] as &$arg) {
if ($arg instanceof \Closure) {
$arg = 'Object(' . \get_class($arg) . ')';
}
}
}
$r->setValue($e, $trace);
} else {
--$retries;
$promise = $executor->query($nameserver, $query)->then(
$success,
$errorback
);
}
};
$promise = $this->executor->query($nameserver, $query)->then(
$success,
$errorback
);
return $deferred->promise();
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace React\Dns\Query;
class TimeoutException extends \Exception
{
}

View File

@@ -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;
});
}
}

View File

@@ -0,0 +1,181 @@
<?php
namespace React\Dns\Query;
use React\Dns\Model\Message;
use React\Dns\Protocol\BinaryDumper;
use React\Dns\Protocol\Parser;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
/**
* Send DNS queries over a UDP transport.
*
* This is the main class that sends a DNS query to your DNS server and is used
* internally by the `Resolver` for the actual message transport.
*
* For more advanced usages one can utilize this class directly.
* The following example looks up the `IPv6` address for `igor.io`.
*
* ```php
* $loop = Factory::create();
* $executor = new UdpTransportExecutor($loop);
*
* $executor->query(
* '8.8.8.8:53',
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
* )->then(function (Message $message) {
* foreach ($message->answers as $answer) {
* echo 'IPv6: ' . $answer->data . PHP_EOL;
* }
* }, 'printf');
*
* $loop->run();
* ```
*
* See also the [fourth example](examples).
*
* Note that this executor does not implement a timeout, so you will very likely
* want to use this in combination with a `TimeoutExecutor` like this:
*
* ```php
* $executor = new TimeoutExecutor(
* new UdpTransportExecutor($loop),
* 3.0,
* $loop
* );
* ```
*
* Also note that this executor uses an unreliable UDP transport and that it
* does not implement any retry logic, so you will likely want to use this in
* combination with a `RetryExecutor` like this:
*
* ```php
* $executor = new RetryExecutor(
* new TimeoutExecutor(
* new UdpTransportExecutor($loop),
* 3.0,
* $loop
* )
* );
* ```
*
* Note that this executor is entirely async and as such allows you to execute
* any number of queries concurrently. You should probably limit the number of
* concurrent queries in your application or you're very likely going to face
* rate limitations and bans on the resolver end. For many common applications,
* you may want to avoid sending the same query multiple times when the first
* one is still pending, so you will likely want to use this in combination with
* a `CoopExecutor` like this:
*
* ```php
* $executor = new CoopExecutor(
* new RetryExecutor(
* new TimeoutExecutor(
* new UdpTransportExecutor($loop),
* 3.0,
* $loop
* )
* )
* );
* ```
*
* > Internally, this class uses PHP's UDP sockets and does not take advantage
* of [react/datagram](https://github.com/reactphp/datagram) purely for
* organizational reasons to avoid a cyclic dependency between the two
* packages. Higher-level components should take advantage of the Datagram
* component instead of reimplementing this socket logic from scratch.
*/
class UdpTransportExecutor implements ExecutorInterface
{
private $loop;
private $parser;
private $dumper;
/**
* @param LoopInterface $loop
* @param null|Parser $parser optional/advanced: DNS protocol parser to use
* @param null|BinaryDumper $dumper optional/advanced: DNS protocol dumper to use
*/
public function __construct(LoopInterface $loop, Parser $parser = null, BinaryDumper $dumper = null)
{
if ($parser === null) {
$parser = new Parser();
}
if ($dumper === null) {
$dumper = new BinaryDumper();
}
$this->loop = $loop;
$this->parser = $parser;
$this->dumper = $dumper;
}
public function query($nameserver, Query $query)
{
$request = Message::createRequestForQuery($query);
$queryData = $this->dumper->toBinary($request);
if (isset($queryData[512])) {
return \React\Promise\reject(new \RuntimeException(
'DNS query for ' . $query->name . ' failed: Query too large for UDP transport'
));
}
// UDP connections are instant, so try connection without a loop or timeout
$socket = @\stream_socket_client("udp://$nameserver", $errno, $errstr, 0);
if ($socket === false) {
return \React\Promise\reject(new \RuntimeException(
'DNS query for ' . $query->name . ' failed: Unable to connect to DNS server (' . $errstr . ')',
$errno
));
}
// set socket to non-blocking and immediately try to send (fill write buffer)
\stream_set_blocking($socket, false);
\fwrite($socket, $queryData);
$loop = $this->loop;
$deferred = new Deferred(function () use ($loop, $socket, $query) {
// cancellation should remove socket from loop and close socket
$loop->removeReadStream($socket);
\fclose($socket);
throw new CancellationException('DNS query for ' . $query->name . ' has been cancelled');
});
$parser = $this->parser;
$loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request) {
// try to read a single data packet from the DNS server
// ignoring any errors, this is uses UDP packets and not a stream of data
$data = @\fread($socket, 512);
try {
$response = $parser->parseMessage($data);
} catch (\Exception $e) {
// ignore and await next if we received an invalid message from remote server
// this may as well be a fake response from an attacker (possible DOS)
return;
}
// ignore and await next if we received an unexpected response ID
// this may as well be a fake response from an attacker (possible cache poisoning)
if ($response->getId() !== $request->getId()) {
return;
}
// we only react to the first valid message, so remove socket from loop and close
$loop->removeReadStream($socket);
\fclose($socket);
if ($response->header->isTruncated()) {
$deferred->reject(new \RuntimeException('DNS query for ' . $query->name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
return;
}
$deferred->resolve($response);
});
return $deferred->promise();
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace React\Dns;
class RecordNotFoundException extends \Exception
{
}

102
vendor/react/dns/src/Resolver/Factory.php vendored Executable file
View File

@@ -0,0 +1,102 @@
<?php
namespace React\Dns\Resolver;
use React\Cache\ArrayCache;
use React\Cache\CacheInterface;
use React\Dns\Config\HostsFile;
use React\Dns\Query\CachingExecutor;
use React\Dns\Query\CoopExecutor;
use React\Dns\Query\ExecutorInterface;
use React\Dns\Query\HostsFileExecutor;
use React\Dns\Query\RetryExecutor;
use React\Dns\Query\TimeoutExecutor;
use React\Dns\Query\UdpTransportExecutor;
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)
{
// default to keeping maximum of 256 responses in cache unless explicitly given
if (!($cache instanceof CacheInterface)) {
$cache = new ArrayCache(256);
}
$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 UdpTransportExecutor($loop),
5.0,
$loop
);
}
protected function createRetryExecutor(LoopInterface $loop)
{
return new CoopExecutor(new RetryExecutor($this->createExecutor($loop)));
}
protected function createCachedExecutor(LoopInterface $loop, CacheInterface $cache)
{
return new CachingExecutor($this->createRetryExecutor($loop), $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;
}
}

250
vendor/react/dns/src/Resolver/Resolver.php vendored Executable file
View File

@@ -0,0 +1,250 @@
<?php
namespace React\Dns\Resolver;
use React\Dns\Model\Message;
use React\Dns\Query\ExecutorInterface;
use React\Dns\Query\Query;
use React\Dns\RecordNotFoundException;
use React\Promise\PromiseInterface;
class Resolver
{
private $nameserver;
private $executor;
public function __construct($nameserver, ExecutorInterface $executor)
{
$this->nameserver = $nameserver;
$this->executor = $executor;
}
/**
* Resolves the given $domain name to a single IPv4 address (type `A` query).
*
* ```php
* $resolver->resolve('reactphp.org')->then(function ($ip) {
* echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
* });
* ```
*
* This is one of the main methods in this package. It sends a DNS query
* for the given $domain name to your DNS server and returns a single IP
* address on success.
*
* If the DNS server sends a DNS response message that contains more than
* one IP address for this query, it will randomly pick one of the IP
* addresses from the response. If you want the full list of IP addresses
* or want to send a different type of query, you should use the
* [`resolveAll()`](#resolveall) method instead.
*
* If the DNS server sends a DNS response message that indicates an error
* code, this method will reject with a `RecordNotFoundException`. Its
* message and code can be used to check for the response code.
*
* If the DNS communication fails and the server does not respond with a
* valid response message, this message will reject with an `Exception`.
*
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
*
* ```php
* $promise = $resolver->resolve('reactphp.org');
*
* $promise->cancel();
* ```
*
* @param string $domain
* @return PromiseInterface Returns a promise which resolves with a single IP address on success or
* rejects with an Exception on error.
*/
public function resolve($domain)
{
return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
return $ips[array_rand($ips)];
});
}
/**
* Resolves all record values for the given $domain name and query $type.
*
* ```php
* $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
* echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
* });
*
* $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
* echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
* });
* ```
*
* This is one of the main methods in this package. It sends a DNS query
* for the given $domain name to your DNS server and returns a list with all
* record values on success.
*
* If the DNS server sends a DNS response message that contains one or more
* records for this query, it will return a list with all record values
* from the response. You can use the `Message::TYPE_*` constants to control
* which type of query will be sent. Note that this method always returns a
* list of record values, but each record value type depends on the query
* type. For example, it returns the IPv4 addresses for type `A` queries,
* the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
* `CNAME` and `PTR` queries and structured data for other queries. See also
* the `Record` documentation for more details.
*
* If the DNS server sends a DNS response message that indicates an error
* code, this method will reject with a `RecordNotFoundException`. Its
* message and code can be used to check for the response code.
*
* If the DNS communication fails and the server does not respond with a
* valid response message, this message will reject with an `Exception`.
*
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
*
* ```php
* $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
*
* $promise->cancel();
* ```
*
* @param string $domain
* @return PromiseInterface Returns a promise which resolves with all record values on success or
* rejects with an Exception on error.
*/
public function resolveAll($domain, $type)
{
$query = new Query($domain, $type, Message::CLASS_IN);
$that = $this;
return $this->executor->query(
$this->nameserver,
$query
)->then(function (Message $response) use ($query, $that) {
return $that->extractValues($query, $response);
});
}
/**
* @deprecated unused, exists for BC only
*/
public function extractAddress(Query $query, Message $response)
{
$addresses = $this->extractValues($query, $response);
return $addresses[array_rand($addresses)];
}
/**
* [Internal] extract all resource record values from response for this query
*
* @param Query $query
* @param Message $response
* @return array
* @throws RecordNotFoundException when response indicates an error or contains no data
* @internal
*/
public function extractValues(Query $query, Message $response)
{
// reject if response code indicates this is an error response message
$code = $response->getResponseCode();
if ($code !== Message::RCODE_OK) {
switch ($code) {
case Message::RCODE_FORMAT_ERROR:
$message = 'Format Error';
break;
case Message::RCODE_SERVER_FAILURE:
$message = 'Server Failure';
break;
case Message::RCODE_NAME_ERROR:
$message = 'Non-Existent Domain / NXDOMAIN';
break;
case Message::RCODE_NOT_IMPLEMENTED:
$message = 'Not Implemented';
break;
case Message::RCODE_REFUSED:
$message = 'Refused';
break;
default:
$message = 'Unknown error response code ' . $code;
}
throw new RecordNotFoundException(
'DNS query for ' . $query->name . ' returned an error response (' . $message . ')',
$code
);
}
$answers = $response->answers;
$addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);
// reject if we did not receive a valid answer (domain is valid, but no record for this type could be found)
if (0 === count($addresses)) {
throw new RecordNotFoundException(
'DNS query for ' . $query->name . ' did not return a valid answer (NOERROR / NODATA)'
);
}
return array_values($addresses);
}
/**
* @deprecated unused, exists for BC only
*/
public function resolveAliases(array $answers, $name)
{
return $this->valuesByNameAndType($answers, $name, Message::TYPE_A);
}
/**
* @param \React\Dns\Model\Record[] $answers
* @param string $name
* @param int $type
* @return array
*/
private function valuesByNameAndType(array $answers, $name, $type)
{
// return all record values for this name and type (if any)
$named = $this->filterByName($answers, $name);
$records = $this->filterByType($named, $type);
if ($records) {
return $this->mapRecordData($records);
}
// no matching records found? check if there are any matching CNAMEs instead
$cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
if ($cnameRecords) {
$cnames = $this->mapRecordData($cnameRecords);
foreach ($cnames as $cname) {
$records = array_merge(
$records,
$this->valuesByNameAndType($answers, $cname, $type)
);
}
}
return $records;
}
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);
}
}

10
vendor/react/dns/tests/CallableStub.php vendored Executable file
View File

@@ -0,0 +1,10 @@
<?php
namespace React\Tests\Dns;
class CallableStub
{
public function __invoke()
{
}
}

189
vendor/react/dns/tests/Config/ConfigTest.php vendored Executable file
View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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'));
}
}

View File

@@ -0,0 +1 @@
nameserver 8.8.8.8

View File

@@ -0,0 +1,171 @@
<?php
namespace React\Tests\Dns;
use React\EventLoop\Factory as LoopFactory;
use React\Dns\Resolver\Factory;
use React\Dns\RecordNotFoundException;
use React\Dns\Model\Message;
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();
}
public function testResolveAllLocalhostResolvesWithArray()
{
$promise = $this->resolver->resolveAll('localhost', Message::TYPE_A);
$promise->then($this->expectCallableOnceWith($this->isType('array')), $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 testResolveAllGoogleMxResolvesWithCache()
{
$factory = new Factory();
$this->resolver = $factory->createCached('8.8.8.8', $this->loop);
$promise = $this->resolver->resolveAll('google.com', Message::TYPE_MX);
$promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
$this->loop->run();
}
/**
* @group internet
*/
public function testResolveInvalidRejects()
{
$ex = $this->callback(function ($param) {
return ($param instanceof RecordNotFoundException && $param->getCode() === Message::RCODE_NAME_ERROR);
});
$promise = $this->resolver->resolve('example.invalid');
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
$this->loop->run();
}
public function testResolveCancelledRejectsImmediately()
{
$ex = $this->callback(function ($param) {
return ($param instanceof \RuntimeException && $param->getMessage() === 'DNS query for google.com has been cancelled');
});
$promise = $this->resolver->resolve('google.com');
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
$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());
}
public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$factory = new Factory();
$this->resolver = $factory->create('255.255.255.255', $this->loop);
gc_collect_cycles();
$promise = $this->resolver->resolve('google.com');
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
public function testResolveCachedShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$factory = new Factory();
$this->resolver = $factory->createCached('255.255.255.255', $this->loop);
gc_collect_cycles();
$promise = $this->resolver->resolve('google.com');
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
public function testCancelResolveShouldNotCauseGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$factory = new Factory();
$this->resolver = $factory->create('127.0.0.1', $this->loop);
gc_collect_cycles();
$promise = $this->resolver->resolve('google.com');
$promise->cancel();
$promise = null;
$this->assertEquals(0, gc_collect_cycles());
}
public function testCancelResolveCachedShouldNotCauseGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$factory = new Factory();
$this->resolver = $factory->createCached('127.0.0.1', $this->loop);
gc_collect_cycles();
$promise = $this->resolver->resolve('google.com');
$promise->cancel();
$promise = null;
$this->assertEquals(0, gc_collect_cycles());
}
}

31
vendor/react/dns/tests/Model/MessageTest.php vendored Executable file
View File

@@ -0,0 +1,31 @@
<?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'));
$this->assertEquals(Message::RCODE_OK, $request->getResponseCode());
}
}

View File

@@ -0,0 +1,278 @@
<?php
namespace React\Tests\Dns\Protocol;
use PHPUnit\Framework\TestCase;
use React\Dns\Protocol\BinaryDumper;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
class BinaryDumperTest extends TestCase
{
public function testToBinaryRequestMessage()
{
$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($data);
$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);
}
public function testToBinaryRequestMessageWithCustomOptForEdns0()
{
$data = "";
$data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // 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 .= "00"; // additional: (empty hostname)
$data .= "00 29 03 e8 00 00 00 00 00 00 "; // additional: type OPT, class UDP size, TTL 0, no RDATA
$expected = $this->formatHexDump($data);
$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->additional[] = new Record('', 41, 1000, 0, '');
$request->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($request);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
public function testToBinaryResponseMessageWithoutRecords()
{
$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($data);
$response = new Message();
$response->header->set('id', 0x7262);
$response->header->set('rd', 1);
$response->header->set('rcode', Message::RCODE_OK);
$response->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_A,
'class' => Message::CLASS_IN
);
$response->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($response);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
public function testToBinaryForResponseWithSRVRecord()
{
$data = "";
$data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 21 00 01"; // question: type SRV, class IN
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 21 00 01"; // answer: type SRV, class IN
$data .= "00 01 51 80"; // answer: ttl 86400
$data .= "00 0c"; // answer: rdlength 12
$data .= "00 0a 00 14 1f 90 04 74 65 73 74 00"; // answer: rdata priority 10, weight 20, port 8080 test
$expected = $this->formatHexDump($data);
$response = new Message();
$response->header->set('id', 0x7262);
$response->header->set('rd', 1);
$response->header->set('rcode', Message::RCODE_OK);
$response->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_SRV,
'class' => Message::CLASS_IN
);
$response->answers[] = new Record('igor.io', Message::TYPE_SRV, Message::CLASS_IN, 86400, array(
'priority' => 10,
'weight' => 20,
'port' => 8080,
'target' => 'test'
));
$response->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($response);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
public function testToBinaryForResponseWithSOARecord()
{
$data = "";
$data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 06 00 01"; // question: type SOA, class IN
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 06 00 01"; // answer: type SOA, class IN
$data .= "00 01 51 80"; // answer: ttl 86400
$data .= "00 27"; // answer: rdlength 39
$data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
$data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
$data .= "78 49 28 d5 00 00 2a 30 00 00 0e 10"; // answer: rdata 2018060501, 10800, 3600
$data .= "00 09 3e 68 00 00 0e 10"; // answer: 605800, 3600
$expected = $this->formatHexDump($data);
$response = new Message();
$response->header->set('id', 0x7262);
$response->header->set('rd', 1);
$response->header->set('rcode', Message::RCODE_OK);
$response->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_SOA,
'class' => Message::CLASS_IN
);
$response->answers[] = new Record('igor.io', Message::TYPE_SOA, Message::CLASS_IN, 86400, array(
'mname' => 'ns.hello',
'rname' => 'e.hello',
'serial' => 2018060501,
'refresh' => 10800,
'retry' => 3600,
'expire' => 605800,
'minimum' => 3600
));
$response->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($response);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
public function testToBinaryForResponseWithMultipleAnswerRecords()
{
$data = "";
$data .= "72 62 01 00 00 01 00 04 00 00 00 00"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 ff 00 01"; // question: type ANY, class IN
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 01 00 01 00 00 00 00 00 04"; // answer: type A, class IN, TTL 0, 4 bytes
$data .= "7f 00 00 01"; // answer: 127.0.0.1
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 1c 00 01 00 00 00 00 00 10"; // question: type AAAA, class IN, TTL 0, 16 bytes
$data .= "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01"; // answer: ::1
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 10 00 01 00 00 00 00 00 0c"; // answer: type TXT, class IN, TTL 0, 12 bytes
$data .= "05 68 65 6c 6c 6f 05 77 6f 72 6c 64"; // answer: hello, world
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 0f 00 01 00 00 00 00 00 03"; // anwser: type MX, class IN, TTL 0, 3 bytes
$data .= "00 00 00"; // priority 0, no target
$expected = $this->formatHexDump($data);
$response = new Message();
$response->header->set('id', 0x7262);
$response->header->set('rd', 1);
$response->header->set('rcode', Message::RCODE_OK);
$response->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_ANY,
'class' => Message::CLASS_IN
);
$response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
$response->answers[] = new Record('igor.io', Message::TYPE_AAAA, Message::CLASS_IN, 0, '::1');
$response->answers[] = new Record('igor.io', Message::TYPE_TXT, Message::CLASS_IN, 0, array('hello', 'world'));
$response->answers[] = new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 0, array('priority' => 0, 'target' => ''));
$response->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($response);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
public function testToBinaryForResponseWithAnswerAndAdditionalRecord()
{
$data = "";
$data .= "72 62 01 00 00 01 00 01 00 00 00 01"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 02 00 01"; // question: type NS, class IN
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 02 00 01 00 00 00 00 00 0d"; // answer: type NS, class IN, TTL 0, 10 bytes
$data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // answer: example.com
$data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // additional: example.com
$data .= "00 01 00 01 00 00 00 00 00 04"; // additional: type A, class IN, TTL 0, 4 bytes
$data .= "7f 00 00 01"; // additional: 127.0.0.1
$expected = $this->formatHexDump($data);
$response = new Message();
$response->header->set('id', 0x7262);
$response->header->set('rd', 1);
$response->header->set('rcode', Message::RCODE_OK);
$response->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_NS,
'class' => Message::CLASS_IN
);
$response->answers[] = new Record('igor.io', Message::TYPE_NS, Message::CLASS_IN, 0, 'example.com');
$response->additional[] = new Record('example.com', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
$response->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($response);
$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(str_replace(' ', '', $input), 2));
}
}

1033
vendor/react/dns/tests/Protocol/ParserTest.php vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -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();
}
}

View File

@@ -0,0 +1,183 @@
<?php
namespace React\Tests\Dns\Query;
use React\Dns\Model\Message;
use React\Dns\Query\CachingExecutor;
use React\Dns\Query\Query;
use React\Promise\Promise;
use React\Tests\Dns\TestCase;
use React\Promise\Deferred;
use React\Dns\Model\Record;
class CachingExecutorTest extends TestCase
{
public function testQueryWillReturnPendingPromiseWhenCacheIsPendingWithoutSendingQueryToFallbackExecutor()
{
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->never())->method('query');
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(new Promise(function () { }));
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableNever(), $this->expectCallableNever());
}
public function testQueryWillReturnPendingPromiseWhenCacheReturnsMissAndWillSendSameQueryToFallbackExecutor()
{
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn(new Promise(function () { }));
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
$executor = new CachingExecutor($fallback, $cache);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableNever(), $this->expectCallableNever());
}
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsHitWithoutSendingQueryToFallbackExecutor()
{
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->never())->method('query');
$message = new Message();
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve($message));
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
}
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithMinimumTtlFromRecord()
{
$message = new Message();
$message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3700, '127.0.0.1');
$message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '127.0.0.1');
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
$cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 3600);
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
}
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithDefaultTtl()
{
$message = new Message();
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
$cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 60);
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
}
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesWithTruncatedResponseButShouldNotSaveTruncatedMessageToCache()
{
$message = new Message();
$message->header->set('tc', 1);
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
$cache->expects($this->never())->method('set');
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
}
public function testQueryWillReturnRejectedPromiseWhenCacheReturnsMissAndFallbackExecutorRejects()
{
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\reject(new \RuntimeException()));
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
$executor = new CachingExecutor($fallback, $cache);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
}
public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromCache()
{
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->never())->method('query');
$pending = new Promise(function () { }, $this->expectCallableOnce());
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($pending);
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->cancel();
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
}
public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromFallbackExecutorWhenCacheReturnsMiss()
{
$pending = new Promise(function () { }, $this->expectCallableOnce());
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->willReturn($pending);
$deferred = new Deferred();
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($deferred->promise());
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$deferred->resolve(null);
$promise->cancel();
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
}
}

View File

@@ -0,0 +1,233 @@
<?php
use React\Dns\Query\CoopExecutor;
use React\Dns\Model\Message;
use React\Dns\Query\Query;
use React\Promise\Promise;
use React\Tests\Dns\TestCase;
use React\Promise\Deferred;
class CoopExecutorTest extends TestCase
{
public function testQueryOnceWillPassExactQueryToBaseExecutor()
{
$pending = new Promise(function () { });
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
$connector = new CoopExecutor($base);
$connector->query('8.8.8.8', $query);
}
public function testQueryOnceWillResolveWhenBaseExecutorResolves()
{
$message = new Message();
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $connector->query('8.8.8.8', $query);
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
$promise->then($this->expectCallableOnceWith($message));
}
public function testQueryOnceWillRejectWhenBaseExecutorRejects()
{
$exception = new RuntimeException();
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn(\React\Promise\reject($exception));
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $connector->query('8.8.8.8', $query);
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
$promise->then(null, $this->expectCallableOnceWith($exception));
}
public function testQueryTwoDifferentQueriesWillPassExactQueryToBaseExecutorTwice()
{
$pending = new Promise(function () { });
$query1 = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$query2 = new Query('reactphp.org', Message::TYPE_AAAA, Message::CLASS_IN);
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->exactly(2))->method('query')->withConsecutive(
array('8.8.8.8', $query1),
array('8.8.8.8', $query2)
)->willReturn($pending);
$connector = new CoopExecutor($base);
$connector->query('8.8.8.8', $query1);
$connector->query('8.8.8.8', $query2);
}
public function testQueryTwiceWillPassExactQueryToBaseExecutorOnceWhenQueryIsStillPending()
{
$pending = new Promise(function () { });
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
$connector = new CoopExecutor($base);
$connector->query('8.8.8.8', $query);
$connector->query('8.8.8.8', $query);
}
public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyResolved()
{
$deferred = new Deferred();
$pending = new Promise(function () { });
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
$connector = new CoopExecutor($base);
$connector->query('8.8.8.8', $query);
$deferred->resolve(new Message());
$connector->query('8.8.8.8', $query);
}
public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyRejected()
{
$deferred = new Deferred();
$pending = new Promise(function () { });
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
$connector = new CoopExecutor($base);
$connector->query('8.8.8.8', $query);
$deferred->reject(new RuntimeException());
$connector->query('8.8.8.8', $query);
}
public function testCancelQueryWillCancelPromiseFromBaseExecutorAndReject()
{
$promise = new Promise(function () { }, $this->expectCallableOnce());
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn($promise);
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $connector->query('8.8.8.8', $query);
$promise->cancel();
$promise->then(null, $this->expectCallableOnce());
}
public function testCancelOneQueryWhenOtherQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
{
$promise = new Promise(function () { }, $this->expectCallableNever());
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn($promise);
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise1 = $connector->query('8.8.8.8', $query);
$promise2 = $connector->query('8.8.8.8', $query);
$promise1->cancel();
$promise1->then(null, $this->expectCallableOnce());
$promise2->then(null, $this->expectCallableNever());
}
public function testCancelSecondQueryWhenFirstQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
{
$promise = new Promise(function () { }, $this->expectCallableNever());
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn($promise);
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise1 = $connector->query('8.8.8.8', $query);
$promise2 = $connector->query('8.8.8.8', $query);
$promise2->cancel();
$promise2->then(null, $this->expectCallableOnce());
$promise1->then(null, $this->expectCallableNever());
}
public function testCancelAllPendingQueriesWillCancelPromiseFromBaseExecutorAndRejectCancelled()
{
$promise = new Promise(function () { }, $this->expectCallableOnce());
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn($promise);
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise1 = $connector->query('8.8.8.8', $query);
$promise2 = $connector->query('8.8.8.8', $query);
$promise1->cancel();
$promise2->cancel();
$promise1->then(null, $this->expectCallableOnce());
$promise2->then(null, $this->expectCallableOnce());
}
public function testQueryTwiceWillQueryBaseExecutorTwiceIfFirstQueryHasAlreadyBeenCancelledWhenSecondIsStarted()
{
$promise = new Promise(function () { }, $this->expectCallableOnce());
$pending = new Promise(function () { });
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->exactly(2))->method('query')->willReturnOnConsecutiveCalls($promise, $pending);
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise1 = $connector->query('8.8.8.8', $query);
$promise1->cancel();
$promise2 = $connector->query('8.8.8.8', $query);
$promise1->then(null, $this->expectCallableOnce());
$promise2->then(null, $this->expectCallableNever());
}
public function testCancelQueryShouldNotCauseGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$deferred = new Deferred(function () {
throw new \RuntimeException();
});
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn($deferred->promise());
$connector = new CoopExecutor($base);
gc_collect_cycles();
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $connector->query('8.8.8.8', $query);
$promise->cancel();
$promise = null;
$this->assertEquals(0, gc_collect_cycles());
}
}

308
vendor/react/dns/tests/Query/ExecutorTest.php vendored Executable file
View File

@@ -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();
}
}

View File

@@ -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));
}
}

View File

@@ -0,0 +1,83 @@
<?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 setShouldAcceptMxRecord()
{
$currentTime = 1345656451;
$recordBag = new RecordBag();
$recordBag->set($currentTime, new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 3600, array('priority' => 10, 'target' => 'igor.io')));
$records = $recordBag->all();
$this->assertCount(1, $records);
$this->assertSame('igor.io', $records[0]->name);
$this->assertSame(Message::TYPE_MX, $records[0]->type);
$this->assertSame(Message::CLASS_IN, $records[0]->class);
$this->assertSame(array('priority' => 10, 'target' => 'igor.io'), $records[0]->data);
}
/**
* @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);
}
}

View File

@@ -0,0 +1,160 @@
<?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;
use React\Promise\Promise;
class RecordCacheTest extends TestCase
{
/**
* @covers React\Dns\Query\RecordCache
* @test
*/
public function lookupOnCacheMissShouldReturnNull()
{
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
$cache = new RecordCache($base);
$promise = $cache->lookup($query);
$this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
}
/**
* @covers React\Dns\Query\RecordCache
* @test
*/
public function storeRecordPendingCacheDoesNotSetCache()
{
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$pending = new Promise(function () { });
$base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$base->expects($this->once())->method('get')->willReturn($pending);
$base->expects($this->never())->method('set');
$cache = new RecordCache($base);
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
}
/**
* @covers React\Dns\Query\RecordCache
* @test
*/
public function storeRecordOnCacheMissSetsCache()
{
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
$base->expects($this->once())->method('set')->with($this->isType('string'), $this->isType('string'));
$cache = new RecordCache($base);
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
}
/**
* @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;
}
}

View File

@@ -0,0 +1,350 @@
<?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);
}
/**
* @covers React\Dns\Query\RetryExecutor
* @test
*/
public function queryShouldCancelSecondQueryOnCancel()
{
$deferred = new Deferred();
$cancelled = 0;
$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->returnValue($deferred->promise()),
$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());
// first query will time out after a while and this sends the next query
$deferred->reject(new TimeoutException());
$this->assertEquals(0, $cancelled);
$promise->cancel();
$this->assertEquals(1, $cancelled);
}
/**
* @covers React\Dns\Query\RetryExecutor
* @test
*/
public function queryShouldNotCauseGarbageReferencesOnSuccess()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
->willReturn(Promise\resolve($this->createStandardResponse()));
$retryExecutor = new RetryExecutor($executor, 0);
gc_collect_cycles();
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$retryExecutor->query('8.8.8.8', $query);
$this->assertEquals(0, gc_collect_cycles());
}
/**
* @covers React\Dns\Query\RetryExecutor
* @test
*/
public function queryShouldNotCauseGarbageReferencesOnTimeoutErrors()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$executor = $this->createExecutorMock();
$executor
->expects($this->any())
->method('query')
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
->willReturn(Promise\reject(new TimeoutException("timeout")));
$retryExecutor = new RetryExecutor($executor, 0);
gc_collect_cycles();
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$retryExecutor->query('8.8.8.8', $query);
$this->assertEquals(0, gc_collect_cycles());
}
/**
* @covers React\Dns\Query\RetryExecutor
* @test
*/
public function queryShouldNotCauseGarbageReferencesOnCancellation()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$deferred = new Deferred(function () {
throw new \RuntimeException();
});
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
->willReturn($deferred->promise());
$retryExecutor = new RetryExecutor($executor, 0);
gc_collect_cycles();
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$promise = $retryExecutor->query('8.8.8.8', $query);
$promise->cancel();
$promise = null;
$this->assertEquals(0, gc_collect_cycles());
}
/**
* @covers React\Dns\Query\RetryExecutor
* @test
*/
public function queryShouldNotCauseGarbageReferencesOnNonTimeoutErrors()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$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);
}));
$retryExecutor = new RetryExecutor($executor, 2);
gc_collect_cycles();
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$retryExecutor->query('8.8.8.8', $query);
$this->assertEquals(0, gc_collect_cycles());
}
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;
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,215 @@
<?php
namespace React\Tests\Dns\Query;
use React\Dns\Model\Message;
use React\Dns\Protocol\BinaryDumper;
use React\Dns\Protocol\Parser;
use React\Dns\Query\Query;
use React\Dns\Query\UdpTransportExecutor;
use React\EventLoop\Factory;
use React\Tests\Dns\TestCase;
class UdpTransportExecutorTest extends TestCase
{
public function testQueryRejectsIfMessageExceedsUdpSize()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->never())->method('addReadStream');
$dumper = $this->getMockBuilder('React\Dns\Protocol\BinaryDumper')->getMock();
$dumper->expects($this->once())->method('toBinary')->willReturn(str_repeat('.', 513));
$executor = new UdpTransportExecutor($loop, null, $dumper);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8:53', $query);
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
$promise->then(null, $this->expectCallableOnce());
}
public function testQueryRejectsIfServerConnectionFails()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->never())->method('addReadStream');
$executor = new UdpTransportExecutor($loop);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('///', $query);
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
$promise->then(null, $this->expectCallableOnce());
}
/**
* @group internet
*/
public function testQueryRejectsOnCancellation()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('addReadStream');
$loop->expects($this->once())->method('removeReadStream');
$executor = new UdpTransportExecutor($loop);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8:53', $query);
$promise->cancel();
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
$promise->then(null, $this->expectCallableOnce());
}
public function testQueryKeepsPendingIfServerRejectsNetworkPacket()
{
$loop = Factory::create();
$executor = new UdpTransportExecutor($loop);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$wait = true;
$promise = $executor->query('127.0.0.1:1', $query)->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
\Clue\React\Block\sleep(0.2, $loop);
$this->assertTrue($wait);
}
public function testQueryKeepsPendingIfServerSendInvalidMessage()
{
$loop = Factory::create();
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
$loop->addReadStream($server, function ($server) {
$data = stream_socket_recvfrom($server, 512, 0, $peer);
stream_socket_sendto($server, 'invalid', 0, $peer);
});
$address = stream_socket_get_name($server, false);
$executor = new UdpTransportExecutor($loop);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$wait = true;
$promise = $executor->query($address, $query)->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
\Clue\React\Block\sleep(0.2, $loop);
$this->assertTrue($wait);
}
public function testQueryKeepsPendingIfServerSendInvalidId()
{
$parser = new Parser();
$dumper = new BinaryDumper();
$loop = Factory::create();
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
$loop->addReadStream($server, function ($server) use ($parser, $dumper) {
$data = stream_socket_recvfrom($server, 512, 0, $peer);
$message = $parser->parseMessage($data);
$message->header->set('id', 0);
stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
});
$address = stream_socket_get_name($server, false);
$executor = new UdpTransportExecutor($loop, $parser, $dumper);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$wait = true;
$promise = $executor->query($address, $query)->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
\Clue\React\Block\sleep(0.2, $loop);
$this->assertTrue($wait);
}
public function testQueryRejectsIfServerSendsTruncatedResponse()
{
$parser = new Parser();
$dumper = new BinaryDumper();
$loop = Factory::create();
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
$loop->addReadStream($server, function ($server) use ($parser, $dumper) {
$data = stream_socket_recvfrom($server, 512, 0, $peer);
$message = $parser->parseMessage($data);
$message->header->set('tc', 1);
stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
});
$address = stream_socket_get_name($server, false);
$executor = new UdpTransportExecutor($loop, $parser, $dumper);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$wait = true;
$promise = $executor->query($address, $query)->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
// run loop for short period to ensure we detect connection ICMP rejection error
\Clue\React\Block\sleep(0.01, $loop);
if ($wait) {
\Clue\React\Block\sleep(0.2, $loop);
}
$this->assertFalse($wait);
}
public function testQueryResolvesIfServerSendsValidResponse()
{
$parser = new Parser();
$dumper = new BinaryDumper();
$loop = Factory::create();
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
$loop->addReadStream($server, function ($server) use ($parser, $dumper) {
$data = stream_socket_recvfrom($server, 512, 0, $peer);
$message = $parser->parseMessage($data);
stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
});
$address = stream_socket_get_name($server, false);
$executor = new UdpTransportExecutor($loop, $parser, $dumper);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query($address, $query);
$response = \Clue\React\Block\await($promise, $loop, 0.2);
$this->assertInstanceOf('React\Dns\Model\Message', $response);
}
}

View File

@@ -0,0 +1,120 @@
<?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 createCachedShouldCreateResolverWithCachingExecutor()
{
$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\CachingExecutor', $executor);
$cache = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
$this->assertInstanceOf('React\Cache\ArrayCache', $cache);
}
/** @test */
public function createCachedShouldCreateResolverWithCachingExecutorWithCustomCache()
{
$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\CachingExecutor', $executor);
$cacheProperty = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
$this->assertSame($cache, $cacheProperty);
}
/**
* @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 getCachingExecutorPrivateMemberValue($resolver, $field)
{
$reflector = new \ReflectionProperty('React\Dns\Query\CachingExecutor', $field);
$reflector->setAccessible(true);
return $reflector->getValue($resolver);
}
}

View File

@@ -0,0 +1,101 @@
<?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
* @covers React\Dns\Resolver\Resolver::valuesByNameAndType
* @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();
}
}

View File

@@ -0,0 +1,251 @@
<?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;
use React\Dns\RecordNotFoundException;
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 resolveAllShouldQueryGivenRecords()
{
$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, '::1');
return Promise\resolve($response);
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
}
/** @test */
public function resolveAllShouldIgnoreRecordsWithOtherTypes()
{
$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, Message::TYPE_TXT, $query->class, 3600, array('ignored'));
$response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '::1');
return Promise\resolve($response);
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
}
/** @test */
public function resolveAllShouldReturnMultipleValuesForAlias()
{
$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, Message::TYPE_CNAME, $query->class, 3600, 'example.com');
$response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::1');
$response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::2');
$response->prepare();
return Promise\resolve($response);
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(
$this->expectCallableOnceWith($this->equalTo(array('::1', '::2')))
);
}
/** @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 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->callback(function ($param) {
return ($param instanceof RecordNotFoundException && $param->getCode() === 0 && $param->getMessage() === 'DNS query for igor.io did not return a valid answer (NOERROR / NODATA)');
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
}
public function provideRcodeErrors()
{
return array(
array(
Message::RCODE_FORMAT_ERROR,
'DNS query for example.com returned an error response (Format Error)',
),
array(
Message::RCODE_SERVER_FAILURE,
'DNS query for example.com returned an error response (Server Failure)',
),
array(
Message::RCODE_NAME_ERROR,
'DNS query for example.com returned an error response (Non-Existent Domain / NXDOMAIN)'
),
array(
Message::RCODE_NOT_IMPLEMENTED,
'DNS query for example.com returned an error response (Not Implemented)'
),
array(
Message::RCODE_REFUSED,
'DNS query for example.com returned an error response (Refused)'
),
array(
99,
'DNS query for example.com returned an error response (Unknown error response code 99)'
)
);
}
/**
* @test
* @dataProvider provideRcodeErrors
*/
public function resolveWithRcodeErrorShouldCallErrbackIfGiven($code, $expectedMessage)
{
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
->will($this->returnCallback(function ($nameserver, $query) use ($code) {
$response = new Message();
$response->header->set('qr', 1);
$response->header->set('rcode', $code);
$response->questions[] = new Record($query->name, $query->type, $query->class);
return Promise\resolve($response);
}));
$errback = $this->expectCallableOnceWith($this->callback(function ($param) use ($code, $expectedMessage) {
return ($param instanceof RecordNotFoundException && $param->getCode() === $code && $param->getMessage() === $expectedMessage);
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolve('example.com')->then($this->expectCallableNever(), $errback);
}
public function testLegacyExtractAddress()
{
$executor = $this->createExecutorMock();
$resolver = new Resolver('8.8.8.8:53', $executor);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$response = Message::createResponseWithAnswersForQuery($query, array(
new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '1.2.3.4')
));
$ret = $resolver->extractAddress($query, $response);
$this->assertEquals('1.2.3.4', $ret);
}
private function createExecutorMock()
{
return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
}
}

61
vendor/react/dns/tests/TestCase.php vendored Executable file
View File

@@ -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);
}
}
}