summaryrefslogtreecommitdiffstats
path: root/vendor/react/dns
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/react/dns')
-rw-r--r--vendor/react/dns/LICENSE21
-rw-r--r--vendor/react/dns/composer.json45
-rw-r--r--vendor/react/dns/src/BadServerException.php7
-rw-r--r--vendor/react/dns/src/Config/Config.php137
-rw-r--r--vendor/react/dns/src/Config/HostsFile.php153
-rw-r--r--vendor/react/dns/src/Model/Message.php230
-rw-r--r--vendor/react/dns/src/Model/Record.php153
-rw-r--r--vendor/react/dns/src/Protocol/BinaryDumper.php199
-rw-r--r--vendor/react/dns/src/Protocol/Parser.php356
-rw-r--r--vendor/react/dns/src/Query/CachingExecutor.php88
-rw-r--r--vendor/react/dns/src/Query/CancellationException.php7
-rw-r--r--vendor/react/dns/src/Query/CoopExecutor.php91
-rw-r--r--vendor/react/dns/src/Query/ExecutorInterface.php43
-rw-r--r--vendor/react/dns/src/Query/FallbackExecutor.php49
-rw-r--r--vendor/react/dns/src/Query/HostsFileExecutor.php89
-rw-r--r--vendor/react/dns/src/Query/Query.php69
-rw-r--r--vendor/react/dns/src/Query/RetryExecutor.php86
-rw-r--r--vendor/react/dns/src/Query/SelectiveTransportExecutor.php85
-rw-r--r--vendor/react/dns/src/Query/TcpTransportExecutor.php367
-rw-r--r--vendor/react/dns/src/Query/TimeoutException.php7
-rw-r--r--vendor/react/dns/src/Query/TimeoutExecutor.php31
-rw-r--r--vendor/react/dns/src/Query/UdpTransportExecutor.php208
-rw-r--r--vendor/react/dns/src/RecordNotFoundException.php7
-rw-r--r--vendor/react/dns/src/Resolver/Factory.php214
-rw-r--r--vendor/react/dns/src/Resolver/Resolver.php147
-rw-r--r--vendor/react/dns/src/Resolver/ResolverInterface.php94
26 files changed, 2983 insertions, 0 deletions
diff --git a/vendor/react/dns/LICENSE b/vendor/react/dns/LICENSE
new file mode 100644
index 0000000..d6f8901
--- /dev/null
+++ b/vendor/react/dns/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/dns/composer.json b/vendor/react/dns/composer.json
new file mode 100644
index 0000000..0126343
--- /dev/null
+++ b/vendor/react/dns/composer.json
@@ -0,0 +1,45 @@
+{
+ "name": "react/dns",
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "^1.0 || ^0.6 || ^0.5",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.0 || ^2.7 || ^1.2.1",
+ "react/promise-timer": "^1.8"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^9.3 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": { "React\\Dns\\": "src" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Dns\\": "tests" }
+ }
+}
diff --git a/vendor/react/dns/src/BadServerException.php b/vendor/react/dns/src/BadServerException.php
new file mode 100644
index 0000000..3d95213
--- /dev/null
+++ b/vendor/react/dns/src/BadServerException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns;
+
+final class BadServerException extends \Exception
+{
+}
diff --git a/vendor/react/dns/src/Config/Config.php b/vendor/react/dns/src/Config/Config.php
new file mode 100644
index 0000000..9677ee5
--- /dev/null
+++ b/vendor/react/dns/src/Config/Config.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace React\Dns\Config;
+
+use RuntimeException;
+
+final 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 . '"');
+ }
+
+ $matches = array();
+ preg_match_all('/^nameserver\s+(\S+)\s*$/m', $contents, $matches);
+
+ $config = new self();
+ foreach ($matches[1] as $ip) {
+ // 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) {
+ $config->nameservers[] = $ip;
+ }
+ }
+
+ return $config;
+ }
+
+ /**
+ * Loads the DNS configurations from Windows's WMIC (from the given command or default command)
+ *
+ * Note that this method blocks while loading the given command and should
+ * thus be used with care! While this should be relatively fast for normal
+ * WMIC commands, it remains unknown if this may block under certain
+ * circumstances. In particular, this method should only be executed before
+ * the loop starts, not while it is running.
+ *
+ * Note that this method will only try to execute the given command try to
+ * parse its output, irrespective of whether this command exists. In
+ * particular, this command is only available on Windows. Currently, this
+ * will only parse valid nameserver entries from the command output and will
+ * ignore all other output without complaining.
+ *
+ * Note that the previous section implies that this may return an empty
+ * `Config` object if no valid nameserver entries can be found.
+ *
+ * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
+ * @return self
+ * @link https://ss64.com/nt/wmic.html
+ */
+ public static function loadWmicBlocking($command = null)
+ {
+ $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
+ preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
+
+ $config = new self();
+ $config->nameservers = $matches[1];
+
+ return $config;
+ }
+
+ public $nameservers = array();
+}
diff --git a/vendor/react/dns/src/Config/HostsFile.php b/vendor/react/dns/src/Config/HostsFile.php
new file mode 100644
index 0000000..1060231
--- /dev/null
+++ b/vendor/react/dns/src/Config/HostsFile.php
@@ -0,0 +1,153 @@
+<?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);
+ }
+
+ private $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, -1, PREG_SPLIT_NO_EMPTY);
+ $addr = (string) array_shift($parts);
+
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
+ $addr = substr($addr, 0, $pos);
+ }
+
+ if (@inet_pton($addr) === $ip) {
+ foreach ($parts as $part) {
+ $names[] = $part;
+ }
+ }
+ }
+
+ return $names;
+ }
+}
diff --git a/vendor/react/dns/src/Model/Message.php b/vendor/react/dns/src/Model/Message.php
new file mode 100644
index 0000000..bac2b10
--- /dev/null
+++ b/vendor/react/dns/src/Model/Message.php
@@ -0,0 +1,230 @@
+<?php
+
+namespace React\Dns\Model;
+
+use React\Dns\Query\Query;
+
+/**
+ * This class represents an outgoing query message or an incoming response message
+ *
+ * @link https://tools.ietf.org/html/rfc1035#section-4.1.1
+ */
+final 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_SSHFP = 44;
+
+ /**
+ * pseudo-type for EDNS0
+ *
+ * These are included in the additional section and usually not in answer section.
+ * Defined in [RFC 6891](https://tools.ietf.org/html/rfc6891) (or older
+ * [RFC 2671](https://tools.ietf.org/html/rfc2671)).
+ *
+ * The OPT record uses the "class" field to store the maximum size.
+ *
+ * The OPT record uses the "ttl" field to store additional flags.
+ */
+ const TYPE_OPT = 41;
+
+ /**
+ * Sender Policy Framework (SPF) had a dedicated SPF type which has been
+ * deprecated in favor of reusing the existing TXT type.
+ *
+ * @deprecated https://datatracker.ietf.org/doc/html/rfc7208#section-3.1
+ * @see self::TYPE_TXT
+ */
+ const TYPE_SPF = 99;
+
+ const TYPE_ANY = 255;
+ const TYPE_CAA = 257;
+
+ 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;
+
+ /**
+ * The edns-tcp-keepalive EDNS0 Option
+ *
+ * Option value contains a `?float` with timeout in seconds (in 0.1s steps)
+ * for DNS response or `null` for DNS query.
+ *
+ * @link https://tools.ietf.org/html/rfc7828
+ */
+ const OPT_TCP_KEEPALIVE = 11;
+
+ /**
+ * The EDNS(0) Padding Option
+ *
+ * Option value contains a `string` with binary data (usually variable
+ * number of null bytes)
+ *
+ * @link https://tools.ietf.org/html/rfc7830
+ */
+ const OPT_PADDING = 12;
+
+ /**
+ * Creates a new request message for the given query
+ *
+ * @param Query $query
+ * @return self
+ */
+ public static function createRequestForQuery(Query $query)
+ {
+ $request = new Message();
+ $request->id = self::generateId();
+ $request->rd = true;
+ $request->questions[] = $query;
+
+ 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->id = self::generateId();
+ $response->qr = true;
+ $response->rd = true;
+
+ $response->questions[] = $query;
+
+ foreach ($answers as $record) {
+ $response->answers[] = $record;
+ }
+
+ 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);
+ }
+
+ /**
+ * 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.
+ *
+ * @var int 16 bit message ID
+ * @see self::generateId()
+ */
+ public $id = 0;
+
+ /**
+ * @var bool Query/Response flag, query=false or response=true
+ */
+ public $qr = false;
+
+ /**
+ * @var int specifies the kind of query (4 bit), see self::OPCODE_* constants
+ * @see self::OPCODE_QUERY
+ */
+ public $opcode = self::OPCODE_QUERY;
+
+ /**
+ *
+ * @var bool Authoritative Answer
+ */
+ public $aa = false;
+
+ /**
+ * @var bool TrunCation
+ */
+ public $tc = false;
+
+ /**
+ * @var bool Recursion Desired
+ */
+ public $rd = false;
+
+ /**
+ * @var bool Recursion Available
+ */
+ public $ra = false;
+
+ /**
+ * @var int response code (4 bit), see self::RCODE_* constants
+ * @see self::RCODE_OK
+ */
+ public $rcode = Message::RCODE_OK;
+
+ /**
+ * An array of Query objects
+ *
+ * ```php
+ * $questions = array(
+ * new Query(
+ * 'reactphp.org',
+ * Message::TYPE_A,
+ * Message::CLASS_IN
+ * )
+ * );
+ * ```
+ *
+ * @var Query[]
+ */
+ public $questions = array();
+
+ /**
+ * @var Record[]
+ */
+ public $answers = array();
+
+ /**
+ * @var Record[]
+ */
+ public $authority = array();
+
+ /**
+ * @var Record[]
+ */
+ public $additional = array();
+}
diff --git a/vendor/react/dns/src/Model/Record.php b/vendor/react/dns/src/Model/Record.php
new file mode 100644
index 0000000..c20403f
--- /dev/null
+++ b/vendor/react/dns/src/Model/Record.php
@@ -0,0 +1,153 @@
+<?php
+
+namespace React\Dns\Model;
+
+/**
+ * This class represents a single resulting record in a response message
+ *
+ * It uses a structure similar to `\React\Dns\Query\Query`, but does include
+ * fields for resulting TTL and resulting record data (IPs etc.).
+ *
+ * @link https://tools.ietf.org/html/rfc1035#section-4.1.3
+ * @see \React\Dns\Query\Query
+ */
+final class Record
+{
+ /**
+ * @var string hostname without trailing dot, for example "reactphp.org"
+ */
+ public $name;
+
+ /**
+ * @var int see Message::TYPE_* constants (UINT16)
+ */
+ public $type;
+
+ /**
+ * Defines the network class, usually `Message::CLASS_IN`.
+ *
+ * For `OPT` records (EDNS0), this defines the maximum message size instead.
+ *
+ * @var int see Message::CLASS_IN constant (UINT16)
+ * @see Message::CLASS_IN
+ */
+ public $class;
+
+ /**
+ * Defines the maximum time-to-live (TTL) in seconds
+ *
+ * For `OPT` records (EDNS0), this defines additional flags instead.
+ *
+ * @var int maximum TTL in seconds (UINT32, most significant bit always unset)
+ * @link https://tools.ietf.org/html/rfc2181#section-8
+ * @link https://tools.ietf.org/html/rfc6891#section-6.1.3 for `OPT` records (EDNS0)
+ */
+ 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.
+ *
+ * - SSHFP:
+ * Includes algorithm (UNIT8), fingerprint type (UNIT8) and fingerprint
+ * value as lower case hex string, for example:
+ * `{"algorithm":1,"type":1,"fingerprint":"0123456789abcdef..."}`
+ * See also https://www.iana.org/assignments/dns-sshfp-rr-parameters/dns-sshfp-rr-parameters.xhtml
+ * for algorithm and fingerprint type assignments.
+ *
+ * - 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}`.
+ *
+ * - CAA:
+ * Includes flag (UNIT8), tag string and value string, for example:
+ * `{"flag":128,"tag":"issue","value":"letsencrypt.org"}`
+ *
+ * - OPT:
+ * Special pseudo-type for EDNS0. Includes an array of additional opt codes
+ * with a value according to the respective OPT code. See `Message::OPT_*`
+ * for list of supported OPT codes. Any other OPT code not currently
+ * supported will be an opaque binary string containing the raw data
+ * 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 also
+ * [RFC 6891](https://tools.ietf.org/html/rfc6891) for more details.
+ *
+ * - 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, $data)
+ {
+ $this->name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->ttl = $ttl;
+ $this->data = $data;
+ }
+}
diff --git a/vendor/react/dns/src/Protocol/BinaryDumper.php b/vendor/react/dns/src/Protocol/BinaryDumper.php
new file mode 100644
index 0000000..6f4030f
--- /dev/null
+++ b/vendor/react/dns/src/Protocol/BinaryDumper.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace React\Dns\Protocol;
+
+use React\Dns\Model\Message;
+use React\Dns\Model\Record;
+use React\Dns\Query\Query;
+
+final class BinaryDumper
+{
+ /**
+ * @param Message $message
+ * @return string
+ */
+ public function toBinary(Message $message)
+ {
+ $data = '';
+
+ $data .= $this->headerToBinary($message);
+ $data .= $this->questionToBinary($message->questions);
+ $data .= $this->recordsToBinary($message->answers);
+ $data .= $this->recordsToBinary($message->authority);
+ $data .= $this->recordsToBinary($message->additional);
+
+ return $data;
+ }
+
+ /**
+ * @param Message $message
+ * @return string
+ */
+ private function headerToBinary(Message $message)
+ {
+ $data = '';
+
+ $data .= pack('n', $message->id);
+
+ $flags = 0x00;
+ $flags = ($flags << 1) | ($message->qr ? 1 : 0);
+ $flags = ($flags << 4) | $message->opcode;
+ $flags = ($flags << 1) | ($message->aa ? 1 : 0);
+ $flags = ($flags << 1) | ($message->tc ? 1 : 0);
+ $flags = ($flags << 1) | ($message->rd ? 1 : 0);
+ $flags = ($flags << 1) | ($message->ra ? 1 : 0);
+ $flags = ($flags << 3) | 0; // skip unused zero bit
+ $flags = ($flags << 4) | $message->rcode;
+
+ $data .= pack('n', $flags);
+
+ $data .= pack('n', count($message->questions));
+ $data .= pack('n', count($message->answers));
+ $data .= pack('n', count($message->authority));
+ $data .= pack('n', count($message->additional));
+
+ return $data;
+ }
+
+ /**
+ * @param Query[] $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:
+ case Message::TYPE_SPF:
+ $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;
+ case Message::TYPE_CAA:
+ $binary = \pack(
+ 'C*',
+ $record->data['flag'],
+ \strlen($record->data['tag'])
+ );
+ $binary .= $record->data['tag'];
+ $binary .= $record->data['value'];
+ break;
+ case Message::TYPE_SSHFP:
+ $binary = \pack(
+ 'CCH*',
+ $record->data['algorithm'],
+ $record->data['type'],
+ $record->data['fingerprint']
+ );
+ break;
+ case Message::TYPE_OPT:
+ $binary = '';
+ foreach ($record->data as $opt => $value) {
+ if ($opt === Message::OPT_TCP_KEEPALIVE && $value !== null) {
+ $value = \pack('n', round($value * 10));
+ }
+ $binary .= \pack('n*', $opt, \strlen((string) $value)) . $value;
+ }
+ 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";
+ }
+
+ // break up domain name at each dot that is not preceeded by a backslash (escaped notation)
+ return $this->textsToBinary(
+ \array_map(
+ 'stripcslashes',
+ \preg_split(
+ '/(?<!\\\\)\./',
+ $host . '.'
+ )
+ )
+ );
+ }
+}
diff --git a/vendor/react/dns/src/Protocol/Parser.php b/vendor/react/dns/src/Protocol/Parser.php
new file mode 100644
index 0000000..011a6e7
--- /dev/null
+++ b/vendor/react/dns/src/Protocol/Parser.php
@@ -0,0 +1,356 @@
+<?php
+
+namespace React\Dns\Protocol;
+
+use React\Dns\Model\Message;
+use React\Dns\Model\Record;
+use React\Dns\Query\Query;
+use InvalidArgumentException;
+
+/**
+ * DNS protocol parser
+ *
+ * Obsolete and uncommon types and classes are not implemented.
+ */
+final class Parser
+{
+ /**
+ * Parses the given raw binary message into a Message object
+ *
+ * @param string $data
+ * @throws InvalidArgumentException
+ * @return Message
+ */
+ public function parseMessage($data)
+ {
+ $message = $this->parse($data, 0);
+ if ($message === null) {
+ throw new InvalidArgumentException('Unable to parse binary message');
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param string $data
+ * @param int $consumed
+ * @return ?Message
+ */
+ private function parse($data, $consumed)
+ {
+ if (!isset($data[12 - 1])) {
+ return null;
+ }
+
+ list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', substr($data, 0, 12)));
+
+ $message = new Message();
+ $message->id = $id;
+ $message->rcode = $fields & 0xf;
+ $message->ra = (($fields >> 7) & 1) === 1;
+ $message->rd = (($fields >> 8) & 1) === 1;
+ $message->tc = (($fields >> 9) & 1) === 1;
+ $message->aa = (($fields >> 10) & 1) === 1;
+ $message->opcode = ($fields >> 11) & 0xf;
+ $message->qr = (($fields >> 15) & 1) === 1;
+ $consumed += 12;
+
+ // parse all questions
+ for ($i = $qdCount; $i > 0; --$i) {
+ list($question, $consumed) = $this->parseQuestion($data, $consumed);
+ if ($question === null) {
+ return null;
+ } else {
+ $message->questions[] = $question;
+ }
+ }
+
+ // parse all answer records
+ for ($i = $anCount; $i > 0; --$i) {
+ list($record, $consumed) = $this->parseRecord($data, $consumed);
+ if ($record === null) {
+ return null;
+ } else {
+ $message->answers[] = $record;
+ }
+ }
+
+ // parse all authority records
+ for ($i = $nsCount; $i > 0; --$i) {
+ list($record, $consumed) = $this->parseRecord($data, $consumed);
+ if ($record === null) {
+ return null;
+ } else {
+ $message->authority[] = $record;
+ }
+ }
+
+ // parse all additional records
+ for ($i = $arCount; $i > 0; --$i) {
+ list($record, $consumed) = $this->parseRecord($data, $consumed);
+ if ($record === null) {
+ return null;
+ } else {
+ $message->additional[] = $record;
+ }
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param string $data
+ * @param int $consumed
+ * @return array
+ */
+ private function parseQuestion($data, $consumed)
+ {
+ list($labels, $consumed) = $this->readLabels($data, $consumed);
+
+ if ($labels === null || !isset($data[$consumed + 4 - 1])) {
+ return array(null, null);
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
+ $consumed += 4;
+
+ return array(
+ new Query(
+ implode('.', $labels),
+ $type,
+ $class
+ ),
+ $consumed
+ );
+ }
+
+ /**
+ * @param string $data
+ * @param int $consumed
+ * @return array An array with a parsed Record on success or array with null if data is invalid/incomplete
+ */
+ private function parseRecord($data, $consumed)
+ {
+ list($name, $consumed) = $this->readDomain($data, $consumed);
+
+ if ($name === null || !isset($data[$consumed + 10 - 1])) {
+ return array(null, null);
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
+ $consumed += 4;
+
+ list($ttl) = array_values(unpack('N', substr($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($data, $consumed, 2)));
+ $consumed += 2;
+
+ if (!isset($data[$consumed + $rdLength - 1])) {
+ return array(null, null);
+ }
+
+ $rdata = null;
+ $expected = $consumed + $rdLength;
+
+ if (Message::TYPE_A === $type) {
+ if ($rdLength === 4) {
+ $rdata = inet_ntop(substr($data, $consumed, $rdLength));
+ $consumed += $rdLength;
+ }
+ } elseif (Message::TYPE_AAAA === $type) {
+ if ($rdLength === 16) {
+ $rdata = inet_ntop(substr($data, $consumed, $rdLength));
+ $consumed += $rdLength;
+ }
+ } elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
+ list($rdata, $consumed) = $this->readDomain($data, $consumed);
+ } elseif (Message::TYPE_TXT === $type || Message::TYPE_SPF === $type) {
+ $rdata = array();
+ while ($consumed < $expected) {
+ $len = ord($data[$consumed]);
+ $rdata[] = (string)substr($data, $consumed + 1, $len);
+ $consumed += $len + 1;
+ }
+ } elseif (Message::TYPE_MX === $type) {
+ if ($rdLength > 2) {
+ list($priority) = array_values(unpack('n', substr($data, $consumed, 2)));
+ list($target, $consumed) = $this->readDomain($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($data, $consumed, 6)));
+ list($target, $consumed) = $this->readDomain($data, $consumed + 6);
+
+ $rdata = array(
+ 'priority' => $priority,
+ 'weight' => $weight,
+ 'port' => $port,
+ 'target' => $target
+ );
+ }
+ } elseif (Message::TYPE_SSHFP === $type) {
+ if ($rdLength > 2) {
+ list($algorithm, $hash) = \array_values(\unpack('C*', \substr($data, $consumed, 2)));
+ $fingerprint = \bin2hex(\substr($data, $consumed + 2, $rdLength - 2));
+ $consumed += $rdLength;
+
+ $rdata = array(
+ 'algorithm' => $algorithm,
+ 'type' => $hash,
+ 'fingerprint' => $fingerprint
+ );
+ }
+ } elseif (Message::TYPE_SOA === $type) {
+ list($mname, $consumed) = $this->readDomain($data, $consumed);
+ list($rname, $consumed) = $this->readDomain($data, $consumed);
+
+ if ($mname !== null && $rname !== null && isset($data[$consumed + 20 - 1])) {
+ list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($data, $consumed, 20)));
+ $consumed += 20;
+
+ $rdata = array(
+ 'mname' => $mname,
+ 'rname' => $rname,
+ 'serial' => $serial,
+ 'refresh' => $refresh,
+ 'retry' => $retry,
+ 'expire' => $expire,
+ 'minimum' => $minimum
+ );
+ }
+ } elseif (Message::TYPE_OPT === $type) {
+ $rdata = array();
+ while (isset($data[$consumed + 4 - 1])) {
+ list($code, $length) = array_values(unpack('n*', substr($data, $consumed, 4)));
+ $value = (string) substr($data, $consumed + 4, $length);
+ if ($code === Message::OPT_TCP_KEEPALIVE && $value === '') {
+ $value = null;
+ } elseif ($code === Message::OPT_TCP_KEEPALIVE && $length === 2) {
+ list($value) = array_values(unpack('n', $value));
+ $value = round($value * 0.1, 1);
+ } elseif ($code === Message::OPT_TCP_KEEPALIVE) {
+ break;
+ }
+ $rdata[$code] = $value;
+ $consumed += 4 + $length;
+ }
+ } elseif (Message::TYPE_CAA === $type) {
+ if ($rdLength > 3) {
+ list($flag, $tagLength) = array_values(unpack('C*', substr($data, $consumed, 2)));
+
+ if ($tagLength > 0 && $rdLength - 2 - $tagLength > 0) {
+ $tag = substr($data, $consumed + 2, $tagLength);
+ $value = substr($data, $consumed + 2 + $tagLength, $rdLength - 2 - $tagLength);
+ $consumed += $rdLength;
+
+ $rdata = array(
+ 'flag' => $flag,
+ 'tag' => $tag,
+ 'value' => $value
+ );
+ }
+ }
+ } else {
+ // unknown types simply parse rdata as an opaque binary string
+ $rdata = substr($data, $consumed, $rdLength);
+ $consumed += $rdLength;
+ }
+
+ // ensure parsing record data consumes expact number of bytes indicated in record length
+ if ($consumed !== $expected || $rdata === null) {
+ return array(null, null);
+ }
+
+ return array(
+ new Record($name, $type, $class, $ttl, $rdata),
+ $consumed
+ );
+ }
+
+ private function readDomain($data, $consumed)
+ {
+ list ($labels, $consumed) = $this->readLabels($data, $consumed);
+
+ if ($labels === null) {
+ return array(null, null);
+ }
+
+ // use escaped notation for each label part, then join using dots
+ return array(
+ \implode(
+ '.',
+ \array_map(
+ function ($label) {
+ return \addcslashes($label, "\0..\40.\177");
+ },
+ $labels
+ )
+ ),
+ $consumed
+ );
+ }
+
+ /**
+ * @param string $data
+ * @param int $consumed
+ * @param int $compressionDepth maximum depth for compressed labels to avoid unreasonable recursion
+ * @return array
+ */
+ private function readLabels($data, $consumed, $compressionDepth = 127)
+ {
+ $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]) && $compressionDepth) {
+ $offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
+ if ($offset >= $consumed) {
+ return array(null, null);
+ }
+
+ $consumed += 2;
+ list($newLabels) = $this->readLabels($data, $offset, $compressionDepth - 1);
+
+ 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);
+ }
+}
diff --git a/vendor/react/dns/src/Query/CachingExecutor.php b/vendor/react/dns/src/Query/CachingExecutor.php
new file mode 100644
index 0000000..e530b24
--- /dev/null
+++ b/vendor/react/dns/src/Query/CachingExecutor.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Cache\CacheInterface;
+use React\Dns\Model\Message;
+use React\Promise\Promise;
+
+final 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(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 ($query, $id, $cache, $executor, &$pending, $that) {
+ $pending->then(
+ function ($message) use ($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($query)->then(
+ function (Message $message) use ($cache, $id, $that) {
+ // DNS response message received => store in cache when not truncated and return
+ if (!$message->tc) {
+ $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->describe() . ' 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;
+ }
+}
diff --git a/vendor/react/dns/src/Query/CancellationException.php b/vendor/react/dns/src/Query/CancellationException.php
new file mode 100644
index 0000000..5432b36
--- /dev/null
+++ b/vendor/react/dns/src/Query/CancellationException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns\Query;
+
+final class CancellationException extends \RuntimeException
+{
+}
diff --git a/vendor/react/dns/src/Query/CoopExecutor.php b/vendor/react/dns/src/Query/CoopExecutor.php
new file mode 100644
index 0000000..e3f913b
--- /dev/null
+++ b/vendor/react/dns/src/Query/CoopExecutor.php
@@ -0,0 +1,91 @@
+<?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($nameserver),
+ * 3.0
+ * )
+ * )
+ * );
+ * ```
+ */
+final class CoopExecutor implements ExecutorInterface
+{
+ private $executor;
+ private $pending = array();
+ private $counts = array();
+
+ public function __construct(ExecutorInterface $base)
+ {
+ $this->executor = $base;
+ }
+
+ public function query(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($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->describe() . ' has been cancelled');
+ });
+ }
+
+ private function serializeQueryToIdentity(Query $query)
+ {
+ return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
+ }
+}
diff --git a/vendor/react/dns/src/Query/ExecutorInterface.php b/vendor/react/dns/src/Query/ExecutorInterface.php
new file mode 100644
index 0000000..b356dc6
--- /dev/null
+++ b/vendor/react/dns/src/Query/ExecutorInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace React\Dns\Query;
+
+interface ExecutorInterface
+{
+ /**
+ * Executes a query and will return a response message
+ *
+ * It returns a Promise which either fulfills with a response
+ * `React\Dns\Model\Message` on success or rejects with an `Exception` if
+ * the query is not successful. A response message may indicate an error
+ * condition in its `rcode`, but this is considered a valid response message.
+ *
+ * ```php
+ * $executor->query($query)->then(
+ * function (React\Dns\Model\Message $response) {
+ * // response message successfully received
+ * var_dump($response->rcode, $response->answers);
+ * },
+ * function (Exception $error) {
+ * // failed to query due to $error
+ * }
+ * );
+ * ```
+ *
+ * The returned Promise MUST be implemented in such a way that it can be
+ * cancelled when it is still pending. Cancelling a pending promise MUST
+ * reject its value with an Exception. It SHOULD clean up any underlying
+ * resources and references as applicable.
+ *
+ * ```php
+ * $promise = $executor->query($query);
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param Query $query
+ * @return \React\Promise\PromiseInterface<\React\Dns\Model\Message,\Exception>
+ * resolves with response message on success or rejects with an Exception on error
+ */
+ public function query(Query $query);
+}
diff --git a/vendor/react/dns/src/Query/FallbackExecutor.php b/vendor/react/dns/src/Query/FallbackExecutor.php
new file mode 100644
index 0000000..83bd360
--- /dev/null
+++ b/vendor/react/dns/src/Query/FallbackExecutor.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Promise\Promise;
+
+final class FallbackExecutor implements ExecutorInterface
+{
+ private $executor;
+ private $fallback;
+
+ public function __construct(ExecutorInterface $executor, ExecutorInterface $fallback)
+ {
+ $this->executor = $executor;
+ $this->fallback = $fallback;
+ }
+
+ public function query(Query $query)
+ {
+ $cancelled = false;
+ $fallback = $this->fallback;
+ $promise = $this->executor->query($query);
+
+ return new Promise(function ($resolve, $reject) use (&$promise, $fallback, $query, &$cancelled) {
+ $promise->then($resolve, function (\Exception $e1) use ($fallback, $query, $resolve, $reject, &$cancelled, &$promise) {
+ // reject if primary resolution rejected due to cancellation
+ if ($cancelled) {
+ $reject($e1);
+ return;
+ }
+
+ // start fallback query if primary query rejected
+ $promise = $fallback->query($query)->then($resolve, function (\Exception $e2) use ($e1, $reject) {
+ $append = $e2->getMessage();
+ if (($pos = strpos($append, ':')) !== false) {
+ $append = substr($append, $pos + 2);
+ }
+
+ // reject with combined error message if both queries fail
+ $reject(new \RuntimeException($e1->getMessage() . '. ' . $append));
+ });
+ });
+ }, function () use (&$promise, &$cancelled) {
+ // cancel pending query (primary or fallback)
+ $cancelled = true;
+ $promise->cancel();
+ });
+ }
+}
diff --git a/vendor/react/dns/src/Query/HostsFileExecutor.php b/vendor/react/dns/src/Query/HostsFileExecutor.php
new file mode 100644
index 0000000..d6e2d93
--- /dev/null
+++ b/vendor/react/dns/src/Query/HostsFileExecutor.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Config\HostsFile;
+use React\Dns\Model\Message;
+use React\Dns\Model\Record;
+use React\Promise;
+
+/**
+ * Resolves hosts from the 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.
+ */
+final class HostsFileExecutor implements ExecutorInterface
+{
+ private $hosts;
+ private $fallback;
+
+ public function __construct(HostsFile $hosts, ExecutorInterface $fallback)
+ {
+ $this->hosts = $hosts;
+ $this->fallback = $fallback;
+ }
+
+ public function query(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($query);
+ }
+
+ private function getIpFromHost($host)
+ {
+ if (substr($host, -13) === '.in-addr.arpa') {
+ // IPv4: read as IP and reverse bytes
+ $ip = @inet_pton(substr($host, 0, -13));
+ if ($ip === false || isset($ip[4])) {
+ return null;
+ }
+
+ return inet_ntop(strrev($ip));
+ } elseif (substr($host, -9) === '.ip6.arpa') {
+ // IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
+ $ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
+ if ($ip === false) {
+ return null;
+ }
+
+ return $ip;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/vendor/react/dns/src/Query/Query.php b/vendor/react/dns/src/Query/Query.php
new file mode 100644
index 0000000..a3dcfb5
--- /dev/null
+++ b/vendor/react/dns/src/Query/Query.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Model\Message;
+
+/**
+ * This class represents a single question in a query/response message
+ *
+ * It uses a structure similar to `\React\Dns\Message\Record`, but does not
+ * contain fields for resulting TTL and resulting record data (IPs etc.).
+ *
+ * @link https://tools.ietf.org/html/rfc1035#section-4.1.2
+ * @see \React\Dns\Message\Record
+ */
+final class Query
+{
+ /**
+ * @var string query name, i.e. hostname to look up
+ */
+ public $name;
+
+ /**
+ * @var int query type (aka QTYPE), see Message::TYPE_* constants
+ */
+ public $type;
+
+ /**
+ * @var int query class (aka QCLASS), see Message::CLASS_IN constant
+ */
+ public $class;
+
+ /**
+ * @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
+ */
+ public function __construct($name, $type, $class)
+ {
+ $this->name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ }
+
+ /**
+ * Describes the hostname and query type/class for this query
+ *
+ * The output format is supposed to be human readable and is subject to change.
+ * The format is inspired by RFC 3597 when handling unkown types/classes.
+ *
+ * @return string "example.com (A)" or "example.com (CLASS0 TYPE1234)"
+ * @link https://tools.ietf.org/html/rfc3597
+ */
+ public function describe()
+ {
+ $class = $this->class !== Message::CLASS_IN ? 'CLASS' . $this->class . ' ' : '';
+
+ $type = 'TYPE' . $this->type;
+ $ref = new \ReflectionClass('React\Dns\Model\Message');
+ foreach ($ref->getConstants() as $name => $value) {
+ if ($value === $this->type && \strpos($name, 'TYPE_') === 0) {
+ $type = \substr($name, 5);
+ break;
+ }
+ }
+
+ return $this->name . ' (' . $class . $type . ')';
+ }
+}
diff --git a/vendor/react/dns/src/Query/RetryExecutor.php b/vendor/react/dns/src/Query/RetryExecutor.php
new file mode 100644
index 0000000..7efcacc
--- /dev/null
+++ b/vendor/react/dns/src/Query/RetryExecutor.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Promise\CancellablePromiseInterface;
+use React\Promise\Deferred;
+use React\Promise\PromiseInterface;
+
+final class RetryExecutor implements ExecutorInterface
+{
+ private $executor;
+ private $retries;
+
+ public function __construct(ExecutorInterface $executor, $retries = 2)
+ {
+ $this->executor = $executor;
+ $this->retries = $retries;
+ }
+
+ public function query(Query $query)
+ {
+ return $this->tryQuery($query, $this->retries);
+ }
+
+ public function tryQuery(Query $query, $retries)
+ {
+ $deferred = new Deferred(function () use (&$promise) {
+ if ($promise instanceof CancellablePromiseInterface || (!\interface_exists('React\Promise\CancellablePromiseInterface') && \method_exists($promise, 'cancel'))) {
+ $promise->cancel();
+ }
+ });
+
+ $success = function ($value) use ($deferred, &$errorback) {
+ $errorback = null;
+ $deferred->resolve($value);
+ };
+
+ $executor = $this->executor;
+ $errorback = function ($e) use ($deferred, &$promise, $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->describe() . ' 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);
+
+ // Exception trace arguments are not available on some PHP 7.4 installs
+ // @codeCoverageIgnoreStart
+ foreach ($trace as &$one) {
+ if (isset($one['args'])) {
+ foreach ($one['args'] as &$arg) {
+ if ($arg instanceof \Closure) {
+ $arg = 'Object(' . \get_class($arg) . ')';
+ }
+ }
+ }
+ }
+ // @codeCoverageIgnoreEnd
+ $r->setValue($e, $trace);
+ } else {
+ --$retries;
+ $promise = $executor->query($query)->then(
+ $success,
+ $errorback
+ );
+ }
+ };
+
+ $promise = $this->executor->query($query)->then(
+ $success,
+ $errorback
+ );
+
+ return $deferred->promise();
+ }
+}
diff --git a/vendor/react/dns/src/Query/SelectiveTransportExecutor.php b/vendor/react/dns/src/Query/SelectiveTransportExecutor.php
new file mode 100644
index 0000000..0f0ca5d
--- /dev/null
+++ b/vendor/react/dns/src/Query/SelectiveTransportExecutor.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Promise\Promise;
+
+/**
+ * Send DNS queries over a UDP or TCP/IP stream transport.
+ *
+ * This class will automatically choose the correct transport protocol to send
+ * a DNS query to your DNS server. It will always try to send it over the more
+ * efficient UDP transport first. If this query yields a size related issue
+ * (truncated messages), it will retry over a streaming TCP/IP transport.
+ *
+ * For more advanced usages one can utilize this class directly.
+ * The following example looks up the `IPv6` address for `reactphp.org`.
+ *
+ * ```php
+ * $executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);
+ *
+ * $executor->query(
+ * 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');
+ * ```
+ *
+ * Note that this executor only implements the logic to select the correct
+ * transport for the given DNS query. Implementing the correct transport logic,
+ * implementing timeouts and any retry logic is left up to the given executors,
+ * see also [`UdpTransportExecutor`](#udptransportexecutor) and
+ * [`TcpTransportExecutor`](#tcptransportexecutor) for more details.
+ *
+ * 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 SelectiveTransportExecutor(
+ * $datagramExecutor,
+ * $streamExecutor
+ * )
+ * );
+ * ```
+ */
+class SelectiveTransportExecutor implements ExecutorInterface
+{
+ private $datagramExecutor;
+ private $streamExecutor;
+
+ public function __construct(ExecutorInterface $datagramExecutor, ExecutorInterface $streamExecutor)
+ {
+ $this->datagramExecutor = $datagramExecutor;
+ $this->streamExecutor = $streamExecutor;
+ }
+
+ public function query(Query $query)
+ {
+ $stream = $this->streamExecutor;
+ $pending = $this->datagramExecutor->query($query);
+
+ return new Promise(function ($resolve, $reject) use (&$pending, $stream, $query) {
+ $pending->then(
+ $resolve,
+ function ($e) use (&$pending, $stream, $query, $resolve, $reject) {
+ if ($e->getCode() === (\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90)) {
+ $pending = $stream->query($query)->then($resolve, $reject);
+ } else {
+ $reject($e);
+ }
+ }
+ );
+ }, function () use (&$pending) {
+ $pending->cancel();
+ $pending = null;
+ });
+ }
+}
diff --git a/vendor/react/dns/src/Query/TcpTransportExecutor.php b/vendor/react/dns/src/Query/TcpTransportExecutor.php
new file mode 100644
index 0000000..6644e16
--- /dev/null
+++ b/vendor/react/dns/src/Query/TcpTransportExecutor.php
@@ -0,0 +1,367 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Model\Message;
+use React\Dns\Protocol\BinaryDumper;
+use React\Dns\Protocol\Parser;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+
+/**
+ * Send DNS queries over a TCP/IP stream transport.
+ *
+ * This is one of the main classes that send a DNS query to your DNS server.
+ *
+ * For more advanced usages one can utilize this class directly.
+ * The following example looks up the `IPv6` address for `reactphp.org`.
+ *
+ * ```php
+ * $executor = new TcpTransportExecutor('8.8.8.8:53');
+ *
+ * $executor->query(
+ * 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');
+ * ```
+ *
+ * See also [example #92](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 TcpTransportExecutor($nameserver),
+ * 3.0
+ * );
+ * ```
+ *
+ * Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP
+ * transport, so you do not necessarily have to implement any retry logic.
+ *
+ * Note that this executor is entirely async and as such allows you to execute
+ * queries concurrently. The first query will establish a TCP/IP socket
+ * connection to the DNS server which will be kept open for a short period.
+ * Additional queries will automatically reuse this existing socket connection
+ * to the DNS server, will pipeline multiple requests over this single
+ * connection and will keep an idle connection open for a short period. The
+ * initial TCP/IP connection overhead may incur a slight delay if you only send
+ * occasional queries – when sending a larger number of concurrent queries over
+ * an existing connection, it becomes increasingly more efficient and avoids
+ * creating many concurrent sockets like the UDP-based executor. You may still
+ * want to limit the number of (concurrent) queries in your application or you
+ * may be facing 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 TimeoutExecutor(
+ * new TcpTransportExecutor($nameserver),
+ * 3.0
+ * )
+ * );
+ * ```
+ *
+ * > Internally, this class uses PHP's TCP/IP sockets and does not take advantage
+ * of [react/socket](https://github.com/reactphp/socket) purely for
+ * organizational reasons to avoid a cyclic dependency between the two
+ * packages. Higher-level components should take advantage of the Socket
+ * component instead of reimplementing this socket logic from scratch.
+ */
+class TcpTransportExecutor implements ExecutorInterface
+{
+ private $nameserver;
+ private $loop;
+ private $parser;
+ private $dumper;
+
+ /**
+ * @var ?resource
+ */
+ private $socket;
+
+ /**
+ * @var Deferred[]
+ */
+ private $pending = array();
+
+ /**
+ * @var string[]
+ */
+ private $names = array();
+
+ /**
+ * Maximum idle time when socket is current unused (i.e. no pending queries outstanding)
+ *
+ * If a new query is to be sent during the idle period, we can reuse the
+ * existing socket without having to wait for a new socket connection.
+ * This uses a rather small, hard-coded value to not keep any unneeded
+ * sockets open and to not keep the loop busy longer than needed.
+ *
+ * A future implementation may take advantage of `edns-tcp-keepalive` to keep
+ * the socket open for longer periods. This will likely require explicit
+ * configuration because this may consume additional resources and also keep
+ * the loop busy for longer than expected in some applications.
+ *
+ * @var float
+ * @link https://tools.ietf.org/html/rfc7766#section-6.2.1
+ * @link https://tools.ietf.org/html/rfc7828
+ */
+ private $idlePeriod = 0.001;
+
+ /**
+ * @var ?\React\EventLoop\TimerInterface
+ */
+ private $idleTimer;
+
+ private $writeBuffer = '';
+ private $writePending = false;
+
+ private $readBuffer = '';
+ private $readPending = false;
+
+ /** @var string */
+ private $readChunk = 0xffff;
+
+ /**
+ * @param string $nameserver
+ * @param ?LoopInterface $loop
+ */
+ public function __construct($nameserver, LoopInterface $loop = null)
+ {
+ if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
+ // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
+ $nameserver = '[' . $nameserver . ']';
+ }
+
+ $parts = \parse_url((\strpos($nameserver, '://') === false ? 'tcp://' : '') . $nameserver);
+ if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'tcp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
+ throw new \InvalidArgumentException('Invalid nameserver address given');
+ }
+
+ $this->nameserver = 'tcp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
+ $this->loop = $loop ?: Loop::get();
+ $this->parser = new Parser();
+ $this->dumper = new BinaryDumper();
+ }
+
+ public function query(Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ // keep shuffing message ID to avoid using the same message ID for two pending queries at the same time
+ while (isset($this->pending[$request->id])) {
+ $request->id = \mt_rand(0, 0xffff); // @codeCoverageIgnore
+ }
+
+ $queryData = $this->dumper->toBinary($request);
+ $length = \strlen($queryData);
+ if ($length > 0xffff) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: Query too large for TCP transport'
+ ));
+ }
+
+ $queryData = \pack('n', $length) . $queryData;
+
+ if ($this->socket === null) {
+ // create async TCP/IP connection (may take a while)
+ $socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT);
+ if ($socket === false) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
+ $errno
+ ));
+ }
+
+ // set socket to non-blocking and wait for it to become writable (connection success/rejected)
+ \stream_set_blocking($socket, false);
+ if (\function_exists('stream_set_chunk_size')) {
+ \stream_set_chunk_size($socket, $this->readChunk); // @codeCoverageIgnore
+ }
+ $this->socket = $socket;
+ }
+
+ if ($this->idleTimer !== null) {
+ $this->loop->cancelTimer($this->idleTimer);
+ $this->idleTimer = null;
+ }
+
+ // wait for socket to become writable to actually write out data
+ $this->writeBuffer .= $queryData;
+ if (!$this->writePending) {
+ $this->writePending = true;
+ $this->loop->addWriteStream($this->socket, array($this, 'handleWritable'));
+ }
+
+ $names =& $this->names;
+ $that = $this;
+ $deferred = new Deferred(function () use ($that, &$names, $request) {
+ // remove from list of pending names, but remember pending query
+ $name = $names[$request->id];
+ unset($names[$request->id]);
+ $that->checkIdle();
+
+ throw new CancellationException('DNS query for ' . $name . ' has been cancelled');
+ });
+
+ $this->pending[$request->id] = $deferred;
+ $this->names[$request->id] = $query->describe();
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @internal
+ */
+ public function handleWritable()
+ {
+ if ($this->readPending === false) {
+ $name = @\stream_socket_get_name($this->socket, true);
+ if ($name === false) {
+ // Connection failed? Check socket error if available for underlying errno/errstr.
+ // @codeCoverageIgnoreStart
+ if (\function_exists('socket_import_stream')) {
+ $socket = \socket_import_stream($this->socket);
+ $errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
+ $errstr = \socket_strerror($errno);
+ } else {
+ $errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;
+ $errstr = 'Connection refused';
+ }
+ // @codeCoverageIgnoreEnd
+
+ $this->closeError('Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')', $errno);
+ return;
+ }
+
+ $this->readPending = true;
+ $this->loop->addReadStream($this->socket, array($this, 'handleRead'));
+ }
+
+ $written = @\fwrite($this->socket, $this->writeBuffer);
+ if ($written === false || $written === 0) {
+ $error = \error_get_last();
+ \preg_match('/errno=(\d+) (.+)/', $error['message'], $m);
+ $this->closeError(
+ 'Unable to send query to DNS server ' . $this->nameserver . ' (' . (isset($m[2]) ? $m[2] : $error['message']) . ')',
+ isset($m[1]) ? (int) $m[1] : 0
+ );
+ return;
+ }
+
+ if (isset($this->writeBuffer[$written])) {
+ $this->writeBuffer = \substr($this->writeBuffer, $written);
+ } else {
+ $this->loop->removeWriteStream($this->socket);
+ $this->writePending = false;
+ $this->writeBuffer = '';
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public function handleRead()
+ {
+ // read one chunk of data from the DNS server
+ // any error is fatal, this is a stream of TCP/IP data
+ $chunk = @\fread($this->socket, $this->readChunk);
+ if ($chunk === false || $chunk === '') {
+ $this->closeError('Connection to DNS server ' . $this->nameserver . ' lost');
+ return;
+ }
+
+ // reassemble complete message by concatenating all chunks.
+ $this->readBuffer .= $chunk;
+
+ // response message header contains at least 12 bytes
+ while (isset($this->readBuffer[11])) {
+ // read response message length from first 2 bytes and ensure we have length + data in buffer
+ list(, $length) = \unpack('n', $this->readBuffer);
+ if (!isset($this->readBuffer[$length + 1])) {
+ return;
+ }
+
+ $data = \substr($this->readBuffer, 2, $length);
+ $this->readBuffer = (string)substr($this->readBuffer, $length + 2);
+
+ try {
+ $response = $this->parser->parseMessage($data);
+ } catch (\Exception $e) {
+ // reject all pending queries if we received an invalid message from remote server
+ $this->closeError('Invalid message received from DNS server ' . $this->nameserver);
+ return;
+ }
+
+ // reject all pending queries if we received an unexpected response ID or truncated response
+ if (!isset($this->pending[$response->id]) || $response->tc) {
+ $this->closeError('Invalid response message received from DNS server ' . $this->nameserver);
+ return;
+ }
+
+ $deferred = $this->pending[$response->id];
+ unset($this->pending[$response->id], $this->names[$response->id]);
+
+ $deferred->resolve($response);
+
+ $this->checkIdle();
+ }
+ }
+
+ /**
+ * @internal
+ * @param string $reason
+ * @param int $code
+ */
+ public function closeError($reason, $code = 0)
+ {
+ $this->readBuffer = '';
+ if ($this->readPending) {
+ $this->loop->removeReadStream($this->socket);
+ $this->readPending = false;
+ }
+
+ $this->writeBuffer = '';
+ if ($this->writePending) {
+ $this->loop->removeWriteStream($this->socket);
+ $this->writePending = false;
+ }
+
+ if ($this->idleTimer !== null) {
+ $this->loop->cancelTimer($this->idleTimer);
+ $this->idleTimer = null;
+ }
+
+ @\fclose($this->socket);
+ $this->socket = null;
+
+ foreach ($this->names as $id => $name) {
+ $this->pending[$id]->reject(new \RuntimeException(
+ 'DNS query for ' . $name . ' failed: ' . $reason,
+ $code
+ ));
+ }
+ $this->pending = $this->names = array();
+ }
+
+ /**
+ * @internal
+ */
+ public function checkIdle()
+ {
+ if ($this->idleTimer === null && !$this->names) {
+ $that = $this;
+ $this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () use ($that) {
+ $that->closeError('Idle timeout');
+ });
+ }
+ }
+}
diff --git a/vendor/react/dns/src/Query/TimeoutException.php b/vendor/react/dns/src/Query/TimeoutException.php
new file mode 100644
index 0000000..109b0a9
--- /dev/null
+++ b/vendor/react/dns/src/Query/TimeoutException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns\Query;
+
+final class TimeoutException extends \Exception
+{
+}
diff --git a/vendor/react/dns/src/Query/TimeoutExecutor.php b/vendor/react/dns/src/Query/TimeoutExecutor.php
new file mode 100644
index 0000000..15c8c22
--- /dev/null
+++ b/vendor/react/dns/src/Query/TimeoutExecutor.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise\Timer;
+
+final class TimeoutExecutor implements ExecutorInterface
+{
+ private $executor;
+ private $loop;
+ private $timeout;
+
+ public function __construct(ExecutorInterface $executor, $timeout, LoopInterface $loop = null)
+ {
+ $this->executor = $executor;
+ $this->loop = $loop ?: Loop::get();
+ $this->timeout = $timeout;
+ }
+
+ public function query(Query $query)
+ {
+ return Timer\timeout($this->executor->query($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->describe()), 0, $e);
+ }
+ throw $e;
+ });
+ }
+}
diff --git a/vendor/react/dns/src/Query/UdpTransportExecutor.php b/vendor/react/dns/src/Query/UdpTransportExecutor.php
new file mode 100644
index 0000000..4c995a8
--- /dev/null
+++ b/vendor/react/dns/src/Query/UdpTransportExecutor.php
@@ -0,0 +1,208 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Model\Message;
+use React\Dns\Protocol\BinaryDumper;
+use React\Dns\Protocol\Parser;
+use React\EventLoop\Loop;
+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
+ * $executor = new UdpTransportExecutor('8.8.8.8:53');
+ *
+ * $executor->query(
+ * 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');
+ * ```
+ *
+ * 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($nameserver),
+ * 3.0
+ * );
+ * ```
+ *
+ * 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($nameserver),
+ * 3.0
+ * )
+ * );
+ * ```
+ *
+ * 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($nameserver),
+ * 3.0
+ * )
+ * )
+ * );
+ * ```
+ *
+ * > 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.
+ */
+final class UdpTransportExecutor implements ExecutorInterface
+{
+ private $nameserver;
+ private $loop;
+ private $parser;
+ private $dumper;
+
+ /**
+ * maximum UDP packet size to send and receive
+ *
+ * @var int
+ */
+ private $maxPacketSize = 512;
+
+ /**
+ * @param string $nameserver
+ * @param ?LoopInterface $loop
+ */
+ public function __construct($nameserver, LoopInterface $loop = null)
+ {
+ if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
+ // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
+ $nameserver = '[' . $nameserver . ']';
+ }
+
+ $parts = \parse_url((\strpos($nameserver, '://') === false ? 'udp://' : '') . $nameserver);
+ if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'udp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
+ throw new \InvalidArgumentException('Invalid nameserver address given');
+ }
+
+ $this->nameserver = 'udp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
+ $this->loop = $loop ?: Loop::get();
+ $this->parser = new Parser();
+ $this->dumper = new BinaryDumper();
+ }
+
+ public function query(Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ $queryData = $this->dumper->toBinary($request);
+ if (isset($queryData[$this->maxPacketSize])) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: Query too large for UDP transport',
+ \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
+ ));
+ }
+
+ // UDP connections are instant, so try connection without a loop or timeout
+ $socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0);
+ if ($socket === false) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
+ $errno
+ ));
+ }
+
+ // set socket to non-blocking and immediately try to send (fill write buffer)
+ \stream_set_blocking($socket, false);
+ $written = @\fwrite($socket, $queryData);
+
+ if ($written !== \strlen($queryData)) {
+ // Write may potentially fail, but most common errors are already caught by connection check above.
+ // Among others, macOS is known to report here when trying to send to broadcast address.
+ // This can also be reproduced by writing data exceeding `stream_set_chunk_size()` to a server refusing UDP data.
+ // fwrite(): send of 8192 bytes failed with errno=111 Connection refused
+ $error = \error_get_last();
+ \preg_match('/errno=(\d+) (.+)/', $error['message'], $m);
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: Unable to send query to DNS server ' . $this->nameserver . ' (' . (isset($m[2]) ? $m[2] : $error['message']) . ')',
+ isset($m[1]) ? (int) $m[1] : 0
+ ));
+ }
+
+ $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->describe() . ' has been cancelled');
+ });
+
+ $max = $this->maxPacketSize;
+ $parser = $this->parser;
+ $nameserver = $this->nameserver;
+ $loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request, $max, $nameserver) {
+ // 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, $max);
+ if ($data === false) {
+ return;
+ }
+
+ 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->id !== $request->id) {
+ return;
+ }
+
+ // we only react to the first valid message, so remove socket from loop and close
+ $loop->removeReadStream($socket);
+ \fclose($socket);
+
+ if ($response->tc) {
+ $deferred->reject(new \RuntimeException(
+ 'DNS query for ' . $query->describe() . ' failed: The DNS server ' . $nameserver . ' returned a truncated result for a UDP query',
+ \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
+ ));
+ return;
+ }
+
+ $deferred->resolve($response);
+ });
+
+ return $deferred->promise();
+ }
+}
diff --git a/vendor/react/dns/src/RecordNotFoundException.php b/vendor/react/dns/src/RecordNotFoundException.php
new file mode 100644
index 0000000..3b70274
--- /dev/null
+++ b/vendor/react/dns/src/RecordNotFoundException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns;
+
+final class RecordNotFoundException extends \Exception
+{
+}
diff --git a/vendor/react/dns/src/Resolver/Factory.php b/vendor/react/dns/src/Resolver/Factory.php
new file mode 100644
index 0000000..5fe608c
--- /dev/null
+++ b/vendor/react/dns/src/Resolver/Factory.php
@@ -0,0 +1,214 @@
+<?php
+
+namespace React\Dns\Resolver;
+
+use React\Cache\ArrayCache;
+use React\Cache\CacheInterface;
+use React\Dns\Config\Config;
+use React\Dns\Config\HostsFile;
+use React\Dns\Query\CachingExecutor;
+use React\Dns\Query\CoopExecutor;
+use React\Dns\Query\ExecutorInterface;
+use React\Dns\Query\FallbackExecutor;
+use React\Dns\Query\HostsFileExecutor;
+use React\Dns\Query\RetryExecutor;
+use React\Dns\Query\SelectiveTransportExecutor;
+use React\Dns\Query\TcpTransportExecutor;
+use React\Dns\Query\TimeoutExecutor;
+use React\Dns\Query\UdpTransportExecutor;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+
+final class Factory
+{
+ /**
+ * Creates a DNS resolver instance for the given DNS config
+ *
+ * As of v1.7.0 it's recommended to pass a `Config` object instead of a
+ * single nameserver address. If the given config contains more than one DNS
+ * nameserver, all DNS nameservers will be used in order. The primary DNS
+ * server will always be used first before falling back to the secondary or
+ * tertiary DNS server.
+ *
+ * @param Config|string $config DNS Config object (recommended) or single nameserver address
+ * @param ?LoopInterface $loop
+ * @return \React\Dns\Resolver\ResolverInterface
+ * @throws \InvalidArgumentException for invalid DNS server address
+ * @throws \UnderflowException when given DNS Config object has an empty list of nameservers
+ */
+ public function create($config, LoopInterface $loop = null)
+ {
+ $executor = $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get()));
+
+ return new Resolver($executor);
+ }
+
+ /**
+ * Creates a cached DNS resolver instance for the given DNS config and cache
+ *
+ * As of v1.7.0 it's recommended to pass a `Config` object instead of a
+ * single nameserver address. If the given config contains more than one DNS
+ * nameserver, all DNS nameservers will be used in order. The primary DNS
+ * server will always be used first before falling back to the secondary or
+ * tertiary DNS server.
+ *
+ * @param Config|string $config DNS Config object (recommended) or single nameserver address
+ * @param ?LoopInterface $loop
+ * @param ?CacheInterface $cache
+ * @return \React\Dns\Resolver\ResolverInterface
+ * @throws \InvalidArgumentException for invalid DNS server address
+ * @throws \UnderflowException when given DNS Config object has an empty list of nameservers
+ */
+ public function createCached($config, LoopInterface $loop = null, CacheInterface $cache = null)
+ {
+ // default to keeping maximum of 256 responses in cache unless explicitly given
+ if (!($cache instanceof CacheInterface)) {
+ $cache = new ArrayCache(256);
+ }
+
+ $executor = $this->createExecutor($config, $loop ?: Loop::get());
+ $executor = new CachingExecutor($executor, $cache);
+ $executor = $this->decorateHostsFileExecutor($executor);
+
+ return new Resolver($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;
+ }
+
+ /**
+ * @param Config|string $nameserver
+ * @param LoopInterface $loop
+ * @return CoopExecutor
+ * @throws \InvalidArgumentException for invalid DNS server address
+ * @throws \UnderflowException when given DNS Config object has an empty list of nameservers
+ */
+ private function createExecutor($nameserver, LoopInterface $loop)
+ {
+ if ($nameserver instanceof Config) {
+ if (!$nameserver->nameservers) {
+ throw new \UnderflowException('Empty config with no DNS servers');
+ }
+
+ // Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config).
+ // Note to future self: Recursion isn't too hard, but how deep do we really want to go?
+ $primary = reset($nameserver->nameservers);
+ $secondary = next($nameserver->nameservers);
+ $tertiary = next($nameserver->nameservers);
+
+ if ($tertiary !== false) {
+ // 3 DNS servers given => nest first with fallback for second and third
+ return new CoopExecutor(
+ new RetryExecutor(
+ new FallbackExecutor(
+ $this->createSingleExecutor($primary, $loop),
+ new FallbackExecutor(
+ $this->createSingleExecutor($secondary, $loop),
+ $this->createSingleExecutor($tertiary, $loop)
+ )
+ )
+ )
+ );
+ } elseif ($secondary !== false) {
+ // 2 DNS servers given => fallback from first to second
+ return new CoopExecutor(
+ new RetryExecutor(
+ new FallbackExecutor(
+ $this->createSingleExecutor($primary, $loop),
+ $this->createSingleExecutor($secondary, $loop)
+ )
+ )
+ );
+ } else {
+ // 1 DNS server given => use single executor
+ $nameserver = $primary;
+ }
+ }
+
+ return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop)));
+ }
+
+ /**
+ * @param string $nameserver
+ * @param LoopInterface $loop
+ * @return ExecutorInterface
+ * @throws \InvalidArgumentException for invalid DNS server address
+ */
+ private function createSingleExecutor($nameserver, LoopInterface $loop)
+ {
+ $parts = \parse_url($nameserver);
+
+ if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') {
+ $executor = $this->createTcpExecutor($nameserver, $loop);
+ } elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') {
+ $executor = $this->createUdpExecutor($nameserver, $loop);
+ } else {
+ $executor = new SelectiveTransportExecutor(
+ $this->createUdpExecutor($nameserver, $loop),
+ $this->createTcpExecutor($nameserver, $loop)
+ );
+ }
+
+ return $executor;
+ }
+
+ /**
+ * @param string $nameserver
+ * @param LoopInterface $loop
+ * @return TimeoutExecutor
+ * @throws \InvalidArgumentException for invalid DNS server address
+ */
+ private function createTcpExecutor($nameserver, LoopInterface $loop)
+ {
+ return new TimeoutExecutor(
+ new TcpTransportExecutor($nameserver, $loop),
+ 5.0,
+ $loop
+ );
+ }
+
+ /**
+ * @param string $nameserver
+ * @param LoopInterface $loop
+ * @return TimeoutExecutor
+ * @throws \InvalidArgumentException for invalid DNS server address
+ */
+ private function createUdpExecutor($nameserver, LoopInterface $loop)
+ {
+ return new TimeoutExecutor(
+ new UdpTransportExecutor(
+ $nameserver,
+ $loop
+ ),
+ 5.0,
+ $loop
+ );
+ }
+}
diff --git a/vendor/react/dns/src/Resolver/Resolver.php b/vendor/react/dns/src/Resolver/Resolver.php
new file mode 100644
index 0000000..92926f3
--- /dev/null
+++ b/vendor/react/dns/src/Resolver/Resolver.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace React\Dns\Resolver;
+
+use React\Dns\Model\Message;
+use React\Dns\Query\ExecutorInterface;
+use React\Dns\Query\Query;
+use React\Dns\RecordNotFoundException;
+
+/**
+ * @see ResolverInterface for the base interface
+ */
+final class Resolver implements ResolverInterface
+{
+ private $executor;
+
+ public function __construct(ExecutorInterface $executor)
+ {
+ $this->executor = $executor;
+ }
+
+ public function resolve($domain)
+ {
+ return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
+ return $ips[array_rand($ips)];
+ });
+ }
+
+ public function resolveAll($domain, $type)
+ {
+ $query = new Query($domain, $type, Message::CLASS_IN);
+ $that = $this;
+
+ return $this->executor->query(
+ $query
+ )->then(function (Message $response) use ($query, $that) {
+ return $that->extractValues($query, $response);
+ });
+ }
+
+ /**
+ * [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->rcode;
+ 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->describe() . ' 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->describe() . ' did not return a valid answer (NOERROR / NODATA)'
+ );
+ }
+
+ return array_values($addresses);
+ }
+
+ /**
+ * @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);
+ }
+}
diff --git a/vendor/react/dns/src/Resolver/ResolverInterface.php b/vendor/react/dns/src/Resolver/ResolverInterface.php
new file mode 100644
index 0000000..fe937dc
--- /dev/null
+++ b/vendor/react/dns/src/Resolver/ResolverInterface.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace React\Dns\Resolver;
+
+interface ResolverInterface
+{
+ /**
+ * 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 \React\Promise\PromiseInterface<string,\Exception>
+ * resolves with a single IP address on success or rejects with an Exception on error.
+ */
+ public function resolve($domain);
+
+ /**
+ * 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 \React\Promise\PromiseInterface<array,\Exception>
+ * Resolves with all record values on success or rejects with an Exception on error.
+ */
+ public function resolveAll($domain, $type);
+}