aboutsummaryrefslogtreecommitdiffhomepage
path: root/assets/php/vendor/react/dns/src
diff options
context:
space:
mode:
Diffstat (limited to 'assets/php/vendor/react/dns/src')
-rw-r--r--assets/php/vendor/react/dns/src/BadServerException.php7
-rw-r--r--assets/php/vendor/react/dns/src/Config/Config.php127
-rw-r--r--assets/php/vendor/react/dns/src/Config/FilesystemFactory.php73
-rw-r--r--assets/php/vendor/react/dns/src/Config/HostsFile.php151
-rw-r--r--assets/php/vendor/react/dns/src/Model/HeaderBag.php56
-rw-r--r--assets/php/vendor/react/dns/src/Model/Message.php100
-rw-r--r--assets/php/vendor/react/dns/src/Model/Record.php21
-rw-r--r--assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php62
-rw-r--r--assets/php/vendor/react/dns/src/Protocol/Parser.php254
-rw-r--r--assets/php/vendor/react/dns/src/Query/CachedExecutor.php55
-rw-r--r--assets/php/vendor/react/dns/src/Query/CancellationException.php7
-rw-r--r--assets/php/vendor/react/dns/src/Query/Executor.php156
-rw-r--r--assets/php/vendor/react/dns/src/Query/ExecutorInterface.php8
-rw-r--r--assets/php/vendor/react/dns/src/Query/HostsFileExecutor.php89
-rw-r--r--assets/php/vendor/react/dns/src/Query/Query.php19
-rw-r--r--assets/php/vendor/react/dns/src/Query/RecordBag.php27
-rw-r--r--assets/php/vendor/react/dns/src/Query/RecordCache.php82
-rw-r--r--assets/php/vendor/react/dns/src/Query/RetryExecutor.php44
-rw-r--r--assets/php/vendor/react/dns/src/Query/TimeoutException.php7
-rw-r--r--assets/php/vendor/react/dns/src/Query/TimeoutExecutor.php32
-rw-r--r--assets/php/vendor/react/dns/src/RecordNotFoundException.php7
-rw-r--r--assets/php/vendor/react/dns/src/Resolver/Factory.php103
-rw-r--r--assets/php/vendor/react/dns/src/Resolver/Resolver.php100
23 files changed, 1587 insertions, 0 deletions
diff --git a/assets/php/vendor/react/dns/src/BadServerException.php b/assets/php/vendor/react/dns/src/BadServerException.php
new file mode 100644
index 0000000..3bf50f1
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/BadServerException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns;
+
+class BadServerException extends \Exception
+{
+}
diff --git a/assets/php/vendor/react/dns/src/Config/Config.php b/assets/php/vendor/react/dns/src/Config/Config.php
new file mode 100644
index 0000000..c82635d
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Config/Config.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace React\Dns\Config;
+
+use RuntimeException;
+
+class Config
+{
+ /**
+ * Loads the system DNS configuration
+ *
+ * Note that this method may block while loading its internal files and/or
+ * commands and should thus be used with care! While this should be
+ * relatively fast for most systems, it remains unknown if this may block
+ * under certain circumstances. In particular, this method should only be
+ * executed before the loop starts, not while it is running.
+ *
+ * Note that this method will try to access its files and/or commands and
+ * try to parse its output. Currently, this will only parse valid nameserver
+ * entries from its output and will ignore all other output without
+ * complaining.
+ *
+ * Note that the previous section implies that this may return an empty
+ * `Config` object if no valid nameserver entries can be found.
+ *
+ * @return self
+ * @codeCoverageIgnore
+ */
+ public static function loadSystemConfigBlocking()
+ {
+ // Use WMIC output on Windows
+ if (DIRECTORY_SEPARATOR === '\\') {
+ return self::loadWmicBlocking();
+ }
+
+ // otherwise (try to) load from resolv.conf
+ try {
+ return self::loadResolvConfBlocking();
+ } catch (RuntimeException $ignored) {
+ // return empty config if parsing fails (file not found)
+ return new self();
+ }
+ }
+
+ /**
+ * Loads a resolv.conf file (from the given path or default location)
+ *
+ * Note that this method blocks while loading the given path and should
+ * thus be used with care! While this should be relatively fast for normal
+ * resolv.conf files, this may be an issue if this file is located on a slow
+ * device or contains an excessive number of entries. In particular, this
+ * method should only be executed before the loop starts, not while it is
+ * running.
+ *
+ * Note that this method will throw if the given file can not be loaded,
+ * such as if it is not readable or does not exist. In particular, this file
+ * is not available on Windows.
+ *
+ * Currently, this will only parse valid "nameserver X" lines from the
+ * given file contents. Lines can be commented out with "#" and ";" and
+ * invalid lines will be ignored without complaining. See also
+ * `man resolv.conf` for more details.
+ *
+ * Note that the previous section implies that this may return an empty
+ * `Config` object if no valid "nameserver X" lines can be found. See also
+ * `man resolv.conf` which suggests that the DNS server on the localhost
+ * should be used in this case. This is left up to higher level consumers
+ * of this API.
+ *
+ * @param ?string $path (optional) path to resolv.conf file or null=load default location
+ * @return self
+ * @throws RuntimeException if the path can not be loaded (does not exist)
+ */
+ public static function loadResolvConfBlocking($path = null)
+ {
+ if ($path === null) {
+ $path = '/etc/resolv.conf';
+ }
+
+ $contents = @file_get_contents($path);
+ if ($contents === false) {
+ throw new RuntimeException('Unable to load resolv.conf file "' . $path . '"');
+ }
+
+ preg_match_all('/^nameserver\s+(\S+)\s*$/m', $contents, $matches);
+
+ $config = new self();
+ $config->nameservers = $matches[1];
+
+ return $config;
+ }
+
+ /**
+ * Loads the DNS configurations from Windows's WMIC (from the given command or default command)
+ *
+ * Note that this method blocks while loading the given command and should
+ * thus be used with care! While this should be relatively fast for normal
+ * WMIC commands, it remains unknown if this may block under certain
+ * circumstances. In particular, this method should only be executed before
+ * the loop starts, not while it is running.
+ *
+ * Note that this method will only try to execute the given command try to
+ * parse its output, irrespective of whether this command exists. In
+ * particular, this command is only available on Windows. Currently, this
+ * will only parse valid nameserver entries from the command output and will
+ * ignore all other output without complaining.
+ *
+ * Note that the previous section implies that this may return an empty
+ * `Config` object if no valid nameserver entries can be found.
+ *
+ * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
+ * @return self
+ * @link https://ss64.com/nt/wmic.html
+ */
+ public static function loadWmicBlocking($command = null)
+ {
+ $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
+ preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
+
+ $config = new self();
+ $config->nameservers = $matches[1];
+
+ return $config;
+ }
+
+ public $nameservers = array();
+}
diff --git a/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php b/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php
new file mode 100644
index 0000000..68cec3e
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace React\Dns\Config;
+
+use React\EventLoop\LoopInterface;
+use React\Promise;
+use React\Promise\Deferred;
+use React\Stream\ReadableResourceStream;
+use React\Stream\Stream;
+
+/**
+ * @deprecated
+ * @see Config see Config class instead.
+ */
+class FilesystemFactory
+{
+ private $loop;
+
+ public function __construct(LoopInterface $loop)
+ {
+ $this->loop = $loop;
+ }
+
+ public function create($filename)
+ {
+ return $this
+ ->loadEtcResolvConf($filename)
+ ->then(array($this, 'parseEtcResolvConf'));
+ }
+
+ /**
+ * @param string $contents
+ * @return Promise
+ * @deprecated see Config instead
+ */
+ public function parseEtcResolvConf($contents)
+ {
+ return Promise\resolve(Config::loadResolvConfBlocking(
+ 'data://text/plain;base64,' . base64_encode($contents)
+ ));
+ }
+
+ public function loadEtcResolvConf($filename)
+ {
+ if (!file_exists($filename)) {
+ return Promise\reject(new \InvalidArgumentException("The filename for /etc/resolv.conf given does not exist: $filename"));
+ }
+
+ try {
+ $deferred = new Deferred();
+
+ $fd = fopen($filename, 'r');
+ stream_set_blocking($fd, 0);
+
+ $contents = '';
+
+ $stream = class_exists('React\Stream\ReadableResourceStream') ? new ReadableResourceStream($fd, $this->loop) : new Stream($fd, $this->loop);
+ $stream->on('data', function ($data) use (&$contents) {
+ $contents .= $data;
+ });
+ $stream->on('end', function () use (&$contents, $deferred) {
+ $deferred->resolve($contents);
+ });
+ $stream->on('error', function ($error) use ($deferred) {
+ $deferred->reject($error);
+ });
+
+ return $deferred->promise();
+ } catch (\Exception $e) {
+ return Promise\reject($e);
+ }
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Config/HostsFile.php b/assets/php/vendor/react/dns/src/Config/HostsFile.php
new file mode 100644
index 0000000..5b6277e
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Config/HostsFile.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace React\Dns\Config;
+
+use RuntimeException;
+
+/**
+ * Represents a static hosts file which maps hostnames to IPs
+ *
+ * Hosts files are used on most systems to avoid actually hitting the DNS for
+ * certain common hostnames.
+ *
+ * Most notably, this file usually contains an entry to map "localhost" to the
+ * local IP. Windows is a notable exception here, as Windows does not actually
+ * include "localhost" in this file by default. To compensate for this, this
+ * class may explicitly be wrapped in another HostsFile instance which
+ * hard-codes these entries for Windows (see also Factory).
+ *
+ * This class mostly exists to abstract the parsing/extraction process so this
+ * can be replaced with a faster alternative in the future.
+ */
+class HostsFile
+{
+ /**
+ * Returns the default path for the hosts file on this system
+ *
+ * @return string
+ * @codeCoverageIgnore
+ */
+ public static function getDefaultPath()
+ {
+ // use static path for all Unix-based systems
+ if (DIRECTORY_SEPARATOR !== '\\') {
+ return '/etc/hosts';
+ }
+
+ // Windows actually stores the path in the registry under
+ // \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DataBasePath
+ $path = '%SystemRoot%\\system32\drivers\etc\hosts';
+
+ $base = getenv('SystemRoot');
+ if ($base === false) {
+ $base = 'C:\\Windows';
+ }
+
+ return str_replace('%SystemRoot%', $base, $path);
+ }
+
+ /**
+ * Loads a hosts file (from the given path or default location)
+ *
+ * Note that this method blocks while loading the given path and should
+ * thus be used with care! While this should be relatively fast for normal
+ * hosts file, this may be an issue if this file is located on a slow device
+ * or contains an excessive number of entries. In particular, this method
+ * should only be executed before the loop starts, not while it is running.
+ *
+ * @param ?string $path (optional) path to hosts file or null=load default location
+ * @return self
+ * @throws RuntimeException if the path can not be loaded (does not exist)
+ */
+ public static function loadFromPathBlocking($path = null)
+ {
+ if ($path === null) {
+ $path = self::getDefaultPath();
+ }
+
+ $contents = @file_get_contents($path);
+ if ($contents === false) {
+ throw new RuntimeException('Unable to load hosts file "' . $path . '"');
+ }
+
+ return new self($contents);
+ }
+
+ /**
+ * Instantiate new hosts file with the given hosts file contents
+ *
+ * @param string $contents
+ */
+ public function __construct($contents)
+ {
+ // remove all comments from the contents
+ $contents = preg_replace('/[ \t]*#.*/', '', strtolower($contents));
+
+ $this->contents = $contents;
+ }
+
+ /**
+ * Returns all IPs for the given hostname
+ *
+ * @param string $name
+ * @return string[]
+ */
+ public function getIpsForHost($name)
+ {
+ $name = strtolower($name);
+
+ $ips = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line);
+ $ip = array_shift($parts);
+ if ($parts && array_search($name, $parts) !== false) {
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
+ $ip = substr($ip, 0, $pos);
+ }
+
+ if (@inet_pton($ip) !== false) {
+ $ips[] = $ip;
+ }
+ }
+ }
+
+ return $ips;
+ }
+
+ /**
+ * Returns all hostnames for the given IPv4 or IPv6 address
+ *
+ * @param string $ip
+ * @return string[]
+ */
+ public function getHostsForIp($ip)
+ {
+ // check binary representation of IP to avoid string case and short notation
+ $ip = @inet_pton($ip);
+ if ($ip === false) {
+ return array();
+ }
+
+ $names = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line, null, PREG_SPLIT_NO_EMPTY);
+ $addr = array_shift($parts);
+
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
+ $addr = substr($addr, 0, $pos);
+ }
+
+ if (@inet_pton($addr) === $ip) {
+ foreach ($parts as $part) {
+ $names[] = $part;
+ }
+ }
+ }
+
+ return $names;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Model/HeaderBag.php b/assets/php/vendor/react/dns/src/Model/HeaderBag.php
new file mode 100644
index 0000000..193e65c
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Model/HeaderBag.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace React\Dns\Model;
+
+class HeaderBag
+{
+ public $data = '';
+
+ public $attributes = array(
+ 'qdCount' => 0,
+ 'anCount' => 0,
+ 'nsCount' => 0,
+ 'arCount' => 0,
+ 'qr' => 0,
+ 'opcode' => Message::OPCODE_QUERY,
+ 'aa' => 0,
+ 'tc' => 0,
+ 'rd' => 0,
+ 'ra' => 0,
+ 'z' => 0,
+ 'rcode' => Message::RCODE_OK,
+ );
+
+ public function get($name)
+ {
+ return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
+ }
+
+ public function set($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ public function isQuery()
+ {
+ return 0 === $this->attributes['qr'];
+ }
+
+ public function isResponse()
+ {
+ return 1 === $this->attributes['qr'];
+ }
+
+ public function isTruncated()
+ {
+ return 1 === $this->attributes['tc'];
+ }
+
+ public function populateCounts(Message $message)
+ {
+ $this->attributes['qdCount'] = count($message->questions);
+ $this->attributes['anCount'] = count($message->answers);
+ $this->attributes['nsCount'] = count($message->authority);
+ $this->attributes['arCount'] = count($message->additional);
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Model/Message.php b/assets/php/vendor/react/dns/src/Model/Message.php
new file mode 100644
index 0000000..715cb1f
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Model/Message.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace React\Dns\Model;
+
+use React\Dns\Query\Query;
+use React\Dns\Model\Record;
+
+class Message
+{
+ const TYPE_A = 1;
+ const TYPE_NS = 2;
+ const TYPE_CNAME = 5;
+ const TYPE_SOA = 6;
+ const TYPE_PTR = 12;
+ const TYPE_MX = 15;
+ const TYPE_TXT = 16;
+ const TYPE_AAAA = 28;
+
+ const CLASS_IN = 1;
+
+ const OPCODE_QUERY = 0;
+ const OPCODE_IQUERY = 1; // inverse query
+ const OPCODE_STATUS = 2;
+
+ const RCODE_OK = 0;
+ const RCODE_FORMAT_ERROR = 1;
+ const RCODE_SERVER_FAILURE = 2;
+ const RCODE_NAME_ERROR = 3;
+ const RCODE_NOT_IMPLEMENTED = 4;
+ const RCODE_REFUSED = 5;
+
+ /**
+ * Creates a new request message for the given query
+ *
+ * @param Query $query
+ * @return self
+ */
+ public static function createRequestForQuery(Query $query)
+ {
+ $request = new Message();
+ $request->header->set('id', self::generateId());
+ $request->header->set('rd', 1);
+ $request->questions[] = (array) $query;
+ $request->prepare();
+
+ return $request;
+ }
+
+ /**
+ * Creates a new response message for the given query with the given answer records
+ *
+ * @param Query $query
+ * @param Record[] $answers
+ * @return self
+ */
+ public static function createResponseWithAnswersForQuery(Query $query, array $answers)
+ {
+ $response = new Message();
+ $response->header->set('id', self::generateId());
+ $response->header->set('qr', 1);
+ $response->header->set('opcode', Message::OPCODE_QUERY);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = (array) $query;
+
+ foreach ($answers as $record) {
+ $response->answers[] = $record;
+ }
+
+ $response->prepare();
+
+ return $response;
+ }
+
+ private static function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+
+ public $data = '';
+
+ public $header;
+ public $questions = array();
+ public $answers = array();
+ public $authority = array();
+ public $additional = array();
+
+ public $consumed = 0;
+
+ public function __construct()
+ {
+ $this->header = new HeaderBag();
+ }
+
+ public function prepare()
+ {
+ $this->header->populateCounts($this);
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Model/Record.php b/assets/php/vendor/react/dns/src/Model/Record.php
new file mode 100644
index 0000000..029d232
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Model/Record.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace React\Dns\Model;
+
+class Record
+{
+ public $name;
+ public $type;
+ public $class;
+ public $ttl;
+ public $data;
+
+ public function __construct($name, $type, $class, $ttl = 0, $data = null)
+ {
+ $this->name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->ttl = $ttl;
+ $this->data = $data;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php b/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php
new file mode 100644
index 0000000..35d6ae6
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace React\Dns\Protocol;
+
+use React\Dns\Model\Message;
+use React\Dns\Model\HeaderBag;
+
+class BinaryDumper
+{
+ public function toBinary(Message $message)
+ {
+ $data = '';
+
+ $data .= $this->headerToBinary($message->header);
+ $data .= $this->questionToBinary($message->questions);
+
+ return $data;
+ }
+
+ private function headerToBinary(HeaderBag $header)
+ {
+ $data = '';
+
+ $data .= pack('n', $header->get('id'));
+
+ $flags = 0x00;
+ $flags = ($flags << 1) | $header->get('qr');
+ $flags = ($flags << 4) | $header->get('opcode');
+ $flags = ($flags << 1) | $header->get('aa');
+ $flags = ($flags << 1) | $header->get('tc');
+ $flags = ($flags << 1) | $header->get('rd');
+ $flags = ($flags << 1) | $header->get('ra');
+ $flags = ($flags << 3) | $header->get('z');
+ $flags = ($flags << 4) | $header->get('rcode');
+
+ $data .= pack('n', $flags);
+
+ $data .= pack('n', $header->get('qdCount'));
+ $data .= pack('n', $header->get('anCount'));
+ $data .= pack('n', $header->get('nsCount'));
+ $data .= pack('n', $header->get('arCount'));
+
+ return $data;
+ }
+
+ private function questionToBinary(array $questions)
+ {
+ $data = '';
+
+ foreach ($questions as $question) {
+ $labels = explode('.', $question['name']);
+ foreach ($labels as $label) {
+ $data .= chr(strlen($label)).$label;
+ }
+ $data .= "\x00";
+
+ $data .= pack('n*', $question['type'], $question['class']);
+ }
+
+ return $data;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Protocol/Parser.php b/assets/php/vendor/react/dns/src/Protocol/Parser.php
new file mode 100644
index 0000000..1191cd3
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Protocol/Parser.php
@@ -0,0 +1,254 @@
+<?php
+
+namespace React\Dns\Protocol;
+
+use React\Dns\Model\Message;
+use React\Dns\Model\Record;
+use InvalidArgumentException;
+
+/**
+ * DNS protocol parser
+ *
+ * Obsolete and uncommon types and classes are not implemented.
+ */
+class Parser
+{
+ /**
+ * Parses the given raw binary message into a Message object
+ *
+ * @param string $data
+ * @throws InvalidArgumentException
+ * @return Message
+ */
+ public function parseMessage($data)
+ {
+ $message = new Message();
+ if ($this->parse($data, $message) !== $message) {
+ throw new InvalidArgumentException('Unable to parse binary message');
+ }
+
+ return $message;
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function parseChunk($data, Message $message)
+ {
+ return $this->parse($data, $message);
+ }
+
+ private function parse($data, Message $message)
+ {
+ $message->data .= $data;
+
+ if (!$message->header->get('id')) {
+ if (!$this->parseHeader($message)) {
+ return;
+ }
+ }
+
+ if ($message->header->get('qdCount') != count($message->questions)) {
+ if (!$this->parseQuestion($message)) {
+ return;
+ }
+ }
+
+ if ($message->header->get('anCount') != count($message->answers)) {
+ if (!$this->parseAnswer($message)) {
+ return;
+ }
+ }
+
+ return $message;
+ }
+
+ public function parseHeader(Message $message)
+ {
+ if (strlen($message->data) < 12) {
+ return;
+ }
+
+ $header = substr($message->data, 0, 12);
+ $message->consumed += 12;
+
+ list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', $header));
+
+ $rcode = $fields & bindec('1111');
+ $z = ($fields >> 4) & bindec('111');
+ $ra = ($fields >> 7) & 1;
+ $rd = ($fields >> 8) & 1;
+ $tc = ($fields >> 9) & 1;
+ $aa = ($fields >> 10) & 1;
+ $opcode = ($fields >> 11) & bindec('1111');
+ $qr = ($fields >> 15) & 1;
+
+ $vars = compact('id', 'qdCount', 'anCount', 'nsCount', 'arCount',
+ 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode');
+
+
+ foreach ($vars as $name => $value) {
+ $message->header->set($name, $value);
+ }
+
+ return $message;
+ }
+
+ public function parseQuestion(Message $message)
+ {
+ if (strlen($message->data) < 2) {
+ return;
+ }
+
+ $consumed = $message->consumed;
+
+ list($labels, $consumed) = $this->readLabels($message->data, $consumed);
+
+ if (null === $labels) {
+ return;
+ }
+
+ if (strlen($message->data) - $consumed < 4) {
+ return;
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ $message->consumed = $consumed;
+
+ $message->questions[] = array(
+ 'name' => implode('.', $labels),
+ 'type' => $type,
+ 'class' => $class,
+ );
+
+ if ($message->header->get('qdCount') != count($message->questions)) {
+ return $this->parseQuestion($message);
+ }
+
+ return $message;
+ }
+
+ public function parseAnswer(Message $message)
+ {
+ if (strlen($message->data) < 2) {
+ return;
+ }
+
+ $consumed = $message->consumed;
+
+ list($labels, $consumed) = $this->readLabels($message->data, $consumed);
+
+ if (null === $labels) {
+ return;
+ }
+
+ if (strlen($message->data) - $consumed < 10) {
+ return;
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2)));
+ $consumed += 2;
+
+ $rdata = null;
+
+ if (Message::TYPE_A === $type || Message::TYPE_AAAA === $type) {
+ $ip = substr($message->data, $consumed, $rdLength);
+ $consumed += $rdLength;
+
+ $rdata = inet_ntop($ip);
+ }
+
+ if (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type) {
+ list($bodyLabels, $consumed) = $this->readLabels($message->data, $consumed);
+
+ $rdata = implode('.', $bodyLabels);
+ }
+
+ $message->consumed = $consumed;
+
+ $name = implode('.', $labels);
+ $ttl = $this->signedLongToUnsignedLong($ttl);
+ $record = new Record($name, $type, $class, $ttl, $rdata);
+
+ $message->answers[] = $record;
+
+ if ($message->header->get('anCount') != count($message->answers)) {
+ return $this->parseAnswer($message);
+ }
+
+ return $message;
+ }
+
+ private function readLabels($data, $consumed)
+ {
+ $labels = array();
+
+ while (true) {
+ if ($this->isEndOfLabels($data, $consumed)) {
+ $consumed += 1;
+ break;
+ }
+
+ if ($this->isCompressedLabel($data, $consumed)) {
+ list($newLabels, $consumed) = $this->getCompressedLabel($data, $consumed);
+ $labels = array_merge($labels, $newLabels);
+ break;
+ }
+
+ $length = ord(substr($data, $consumed, 1));
+ $consumed += 1;
+
+ if (strlen($data) - $consumed < $length) {
+ return array(null, null);
+ }
+
+ $labels[] = substr($data, $consumed, $length);
+ $consumed += $length;
+ }
+
+ return array($labels, $consumed);
+ }
+
+ public function isEndOfLabels($data, $consumed)
+ {
+ $length = ord(substr($data, $consumed, 1));
+ return 0 === $length;
+ }
+
+ public function getCompressedLabel($data, $consumed)
+ {
+ list($nameOffset, $consumed) = $this->getCompressedLabelOffset($data, $consumed);
+ list($labels) = $this->readLabels($data, $nameOffset);
+
+ return array($labels, $consumed);
+ }
+
+ public function isCompressedLabel($data, $consumed)
+ {
+ $mask = 0xc000; // 1100000000000000
+ list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
+
+ return (bool) ($peek & $mask);
+ }
+
+ public function getCompressedLabelOffset($data, $consumed)
+ {
+ $mask = 0x3fff; // 0011111111111111
+ list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
+
+ return array($peek & $mask, $consumed + 2);
+ }
+
+ public function signedLongToUnsignedLong($i)
+ {
+ return $i & 0x80000000 ? $i - 0xffffffff : $i;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/CachedExecutor.php b/assets/php/vendor/react/dns/src/Query/CachedExecutor.php
new file mode 100644
index 0000000..285936d
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/CachedExecutor.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Model\Message;
+
+class CachedExecutor implements ExecutorInterface
+{
+ private $executor;
+ private $cache;
+
+ public function __construct(ExecutorInterface $executor, RecordCache $cache)
+ {
+ $this->executor = $executor;
+ $this->cache = $cache;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $executor = $this->executor;
+ $cache = $this->cache;
+
+ return $this->cache
+ ->lookup($query)
+ ->then(
+ function ($cachedRecords) use ($query) {
+ return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
+ },
+ function () use ($executor, $cache, $nameserver, $query) {
+ return $executor
+ ->query($nameserver, $query)
+ ->then(function ($response) use ($cache, $query) {
+ $cache->storeResponseMessage($query->currentTime, $response);
+ return $response;
+ });
+ }
+ );
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function buildResponse(Query $query, array $cachedRecords)
+ {
+ return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ protected function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/CancellationException.php b/assets/php/vendor/react/dns/src/Query/CancellationException.php
new file mode 100644
index 0000000..ac30f4c
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/CancellationException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns\Query;
+
+class CancellationException extends \RuntimeException
+{
+}
diff --git a/assets/php/vendor/react/dns/src/Query/Executor.php b/assets/php/vendor/react/dns/src/Query/Executor.php
new file mode 100644
index 0000000..4c51f2b
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/Executor.php
@@ -0,0 +1,156 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Model\Message;
+use React\Dns\Protocol\Parser;
+use React\Dns\Protocol\BinaryDumper;
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+use React\Promise;
+use React\Stream\DuplexResourceStream;
+use React\Stream\Stream;
+
+class Executor implements ExecutorInterface
+{
+ private $loop;
+ private $parser;
+ private $dumper;
+ private $timeout;
+
+ /**
+ *
+ * Note that albeit supported, the $timeout parameter is deprecated!
+ * You should pass a `null` value here instead. If you need timeout handling,
+ * use the `TimeoutConnector` instead.
+ *
+ * @param LoopInterface $loop
+ * @param Parser $parser
+ * @param BinaryDumper $dumper
+ * @param null|float $timeout DEPRECATED: timeout for DNS query or NULL=no timeout
+ */
+ public function __construct(LoopInterface $loop, Parser $parser, BinaryDumper $dumper, $timeout = 5)
+ {
+ $this->loop = $loop;
+ $this->parser = $parser;
+ $this->dumper = $dumper;
+ $this->timeout = $timeout;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ $queryData = $this->dumper->toBinary($request);
+ $transport = strlen($queryData) > 512 ? 'tcp' : 'udp';
+
+ return $this->doQuery($nameserver, $transport, $queryData, $query->name);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function prepareRequest(Query $query)
+ {
+ return Message::createRequestForQuery($query);
+ }
+
+ public function doQuery($nameserver, $transport, $queryData, $name)
+ {
+ // we only support UDP right now
+ if ($transport !== 'udp') {
+ return Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $name . ' failed: Requested transport "' . $transport . '" not available, only UDP is supported in this version'
+ ));
+ }
+
+ $that = $this;
+ $parser = $this->parser;
+ $loop = $this->loop;
+
+ // UDP connections are instant, so try this without a timer
+ try {
+ $conn = $this->createConnection($nameserver, $transport);
+ } catch (\Exception $e) {
+ return Promise\reject(new \RuntimeException('DNS query for ' . $name . ' failed: ' . $e->getMessage(), 0, $e));
+ }
+
+ $deferred = new Deferred(function ($resolve, $reject) use (&$timer, $loop, &$conn, $name) {
+ $reject(new CancellationException(sprintf('DNS query for %s has been cancelled', $name)));
+
+ if ($timer !== null) {
+ $loop->cancelTimer($timer);
+ }
+ $conn->close();
+ });
+
+ $timer = null;
+ if ($this->timeout !== null) {
+ $timer = $this->loop->addTimer($this->timeout, function () use (&$conn, $name, $deferred) {
+ $conn->close();
+ $deferred->reject(new TimeoutException(sprintf("DNS query for %s timed out", $name)));
+ });
+ }
+
+ $conn->on('data', function ($data) use ($conn, $parser, $deferred, $timer, $loop, $name) {
+ $conn->end();
+ if ($timer !== null) {
+ $loop->cancelTimer($timer);
+ }
+
+ try {
+ $response = $parser->parseMessage($data);
+ } catch (\Exception $e) {
+ $deferred->reject($e);
+ return;
+ }
+
+ if ($response->header->isTruncated()) {
+ $deferred->reject(new \RuntimeException('DNS query for ' . $name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
+ return;
+ }
+
+ $deferred->resolve($response);
+ });
+ $conn->write($queryData);
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ protected function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+
+ /**
+ * @param string $nameserver
+ * @param string $transport
+ * @return \React\Stream\DuplexStreamInterface
+ */
+ protected function createConnection($nameserver, $transport)
+ {
+ $fd = @stream_socket_client("$transport://$nameserver", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
+ if ($fd === false) {
+ throw new \RuntimeException('Unable to connect to DNS server: ' . $errstr, $errno);
+ }
+
+ // Instantiate stream instance around this stream resource.
+ // This ought to be replaced with a datagram socket in the future.
+ // Temporary work around for Windows 10: buffer whole UDP response
+ // @coverageIgnoreStart
+ if (!class_exists('React\Stream\Stream')) {
+ // prefer DuplexResourceStream as of react/stream v0.7.0
+ $conn = new DuplexResourceStream($fd, $this->loop, -1);
+ } else {
+ // use legacy Stream class for react/stream < v0.7.0
+ $conn = new Stream($fd, $this->loop);
+ $conn->bufferSize = null;
+ }
+ // @coverageIgnoreEnd
+
+ return $conn;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php b/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php
new file mode 100644
index 0000000..2f7a635
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace React\Dns\Query;
+
+interface ExecutorInterface
+{
+ public function query($nameserver, Query $query);
+}
diff --git a/assets/php/vendor/react/dns/src/Query/HostsFileExecutor.php b/assets/php/vendor/react/dns/src/Query/HostsFileExecutor.php
new file mode 100644
index 0000000..0ca58be
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/HostsFileExecutor.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Config\HostsFile;
+use React\Dns\Model\Message;
+use React\Dns\Model\Record;
+use React\Promise;
+
+/**
+ * Resolves hosts from the givne HostsFile or falls back to another executor
+ *
+ * If the host is found in the hosts file, it will not be passed to the actual
+ * DNS executor. If the host is not found in the hosts file, it will be passed
+ * to the DNS executor as a fallback.
+ */
+class HostsFileExecutor implements ExecutorInterface
+{
+ private $hosts;
+ private $fallback;
+
+ public function __construct(HostsFile $hosts, ExecutorInterface $fallback)
+ {
+ $this->hosts = $hosts;
+ $this->fallback = $fallback;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
+ // forward lookup for type A or AAAA
+ $records = array();
+ $expectsColon = $query->type === Message::TYPE_AAAA;
+ foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
+ // ensure this is an IPv4/IPV6 address according to query type
+ if ((strpos($ip, ':') !== false) === $expectsColon) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $ip);
+ }
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ } elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
+ // reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
+ $ip = $this->getIpFromHost($query->name);
+
+ if ($ip !== null) {
+ $records = array();
+ foreach ($this->hosts->getHostsForIp($ip) as $host) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $host);
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ }
+ }
+
+ return $this->fallback->query($nameserver, $query);
+ }
+
+ private function getIpFromHost($host)
+ {
+ if (substr($host, -13) === '.in-addr.arpa') {
+ // IPv4: read as IP and reverse bytes
+ $ip = @inet_pton(substr($host, 0, -13));
+ if ($ip === false || isset($ip[4])) {
+ return null;
+ }
+
+ return inet_ntop(strrev($ip));
+ } elseif (substr($host, -9) === '.ip6.arpa') {
+ // IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
+ $ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
+ if ($ip === false) {
+ return null;
+ }
+
+ return $ip;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/Query.php b/assets/php/vendor/react/dns/src/Query/Query.php
new file mode 100644
index 0000000..aef6e05
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/Query.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace React\Dns\Query;
+
+class Query
+{
+ public $name;
+ public $type;
+ public $class;
+ public $currentTime;
+
+ public function __construct($name, $type, $class, $currentTime)
+ {
+ $this->name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->currentTime = $currentTime;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/RecordBag.php b/assets/php/vendor/react/dns/src/Query/RecordBag.php
new file mode 100644
index 0000000..358cf5d
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/RecordBag.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Dns\Model\Message;
+use React\Dns\Model\Record;
+
+class RecordBag
+{
+ private $records = array();
+
+ public function set($currentTime, Record $record)
+ {
+ $this->records[$record->data] = array($currentTime + $record->ttl, $record);
+ }
+
+ public function all()
+ {
+ return array_values(array_map(
+ function ($value) {
+ list($expiresAt, $record) = $value;
+ return $record;
+ },
+ $this->records
+ ));
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/RecordCache.php b/assets/php/vendor/react/dns/src/Query/RecordCache.php
new file mode 100644
index 0000000..b8142d3
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/RecordCache.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Cache\CacheInterface;
+use React\Dns\Model\Message;
+use React\Dns\Model\Record;
+use React\Promise;
+
+class RecordCache
+{
+ private $cache;
+ private $expiredAt;
+
+ public function __construct(CacheInterface $cache)
+ {
+ $this->cache = $cache;
+ }
+
+ public function lookup(Query $query)
+ {
+ $id = $this->serializeQueryToIdentity($query);
+
+ $expiredAt = $this->expiredAt;
+
+ return $this->cache
+ ->get($id)
+ ->then(function ($value) use ($query, $expiredAt) {
+ $recordBag = unserialize($value);
+
+ if (null !== $expiredAt && $expiredAt <= $query->currentTime) {
+ return Promise\reject();
+ }
+
+ return $recordBag->all();
+ });
+ }
+
+ public function storeResponseMessage($currentTime, Message $message)
+ {
+ foreach ($message->answers as $record) {
+ $this->storeRecord($currentTime, $record);
+ }
+ }
+
+ public function storeRecord($currentTime, Record $record)
+ {
+ $id = $this->serializeRecordToIdentity($record);
+
+ $cache = $this->cache;
+
+ $this->cache
+ ->get($id)
+ ->then(
+ function ($value) {
+ return unserialize($value);
+ },
+ function ($e) {
+ return new RecordBag();
+ }
+ )
+ ->then(function ($recordBag) use ($id, $currentTime, $record, $cache) {
+ $recordBag->set($currentTime, $record);
+ $cache->set($id, serialize($recordBag));
+ });
+ }
+
+ public function expire($currentTime)
+ {
+ $this->expiredAt = $currentTime;
+ }
+
+ public function serializeQueryToIdentity(Query $query)
+ {
+ return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
+ }
+
+ public function serializeRecordToIdentity(Record $record)
+ {
+ return sprintf('%s:%s:%s', $record->name, $record->type, $record->class);
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/RetryExecutor.php b/assets/php/vendor/react/dns/src/Query/RetryExecutor.php
new file mode 100644
index 0000000..90353e5
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/RetryExecutor.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\Promise\Deferred;
+
+class RetryExecutor implements ExecutorInterface
+{
+ private $executor;
+ private $retries;
+
+ public function __construct(ExecutorInterface $executor, $retries = 2)
+ {
+ $this->executor = $executor;
+ $this->retries = $retries;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ return $this->tryQuery($nameserver, $query, $this->retries);
+ }
+
+ public function tryQuery($nameserver, Query $query, $retries)
+ {
+ $that = $this;
+ $errorback = function ($error) use ($nameserver, $query, $retries, $that) {
+ if (!$error instanceof TimeoutException) {
+ throw $error;
+ }
+ if (0 >= $retries) {
+ throw new \RuntimeException(
+ sprintf("DNS query for %s failed: too many retries", $query->name),
+ 0,
+ $error
+ );
+ }
+ return $that->tryQuery($nameserver, $query, $retries-1);
+ };
+
+ return $this->executor
+ ->query($nameserver, $query)
+ ->then(null, $errorback);
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/TimeoutException.php b/assets/php/vendor/react/dns/src/Query/TimeoutException.php
new file mode 100644
index 0000000..90bf806
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/TimeoutException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns\Query;
+
+class TimeoutException extends \Exception
+{
+}
diff --git a/assets/php/vendor/react/dns/src/Query/TimeoutExecutor.php b/assets/php/vendor/react/dns/src/Query/TimeoutExecutor.php
new file mode 100644
index 0000000..6a44888
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/TimeoutExecutor.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace React\Dns\Query;
+
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+use React\Promise\CancellablePromiseInterface;
+use React\Promise\Timer;
+
+class TimeoutExecutor implements ExecutorInterface
+{
+ private $executor;
+ private $loop;
+ private $timeout;
+
+ public function __construct(ExecutorInterface $executor, $timeout, LoopInterface $loop)
+ {
+ $this->executor = $executor;
+ $this->loop = $loop;
+ $this->timeout = $timeout;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ return Timer\timeout($this->executor->query($nameserver, $query), $this->timeout, $this->loop)->then(null, function ($e) use ($query) {
+ if ($e instanceof Timer\TimeoutException) {
+ $e = new TimeoutException(sprintf("DNS query for %s timed out", $query->name), 0, $e);
+ }
+ throw $e;
+ });
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/RecordNotFoundException.php b/assets/php/vendor/react/dns/src/RecordNotFoundException.php
new file mode 100644
index 0000000..0028413
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/RecordNotFoundException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace React\Dns;
+
+class RecordNotFoundException extends \Exception
+{
+}
diff --git a/assets/php/vendor/react/dns/src/Resolver/Factory.php b/assets/php/vendor/react/dns/src/Resolver/Factory.php
new file mode 100644
index 0000000..12a912f
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Resolver/Factory.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace React\Dns\Resolver;
+
+use React\Cache\ArrayCache;
+use React\Cache\CacheInterface;
+use React\Dns\Config\HostsFile;
+use React\Dns\Protocol\Parser;
+use React\Dns\Protocol\BinaryDumper;
+use React\Dns\Query\CachedExecutor;
+use React\Dns\Query\Executor;
+use React\Dns\Query\ExecutorInterface;
+use React\Dns\Query\HostsFileExecutor;
+use React\Dns\Query\RecordCache;
+use React\Dns\Query\RetryExecutor;
+use React\Dns\Query\TimeoutExecutor;
+use React\EventLoop\LoopInterface;
+
+class Factory
+{
+ public function create($nameserver, LoopInterface $loop)
+ {
+ $nameserver = $this->addPortToServerIfMissing($nameserver);
+ $executor = $this->decorateHostsFileExecutor($this->createRetryExecutor($loop));
+
+ return new Resolver($nameserver, $executor);
+ }
+
+ public function createCached($nameserver, LoopInterface $loop, CacheInterface $cache = null)
+ {
+ if (!($cache instanceof CacheInterface)) {
+ $cache = new ArrayCache();
+ }
+
+ $nameserver = $this->addPortToServerIfMissing($nameserver);
+ $executor = $this->decorateHostsFileExecutor($this->createCachedExecutor($loop, $cache));
+
+ return new Resolver($nameserver, $executor);
+ }
+
+ /**
+ * Tries to load the hosts file and decorates the given executor on success
+ *
+ * @param ExecutorInterface $executor
+ * @return ExecutorInterface
+ * @codeCoverageIgnore
+ */
+ private function decorateHostsFileExecutor(ExecutorInterface $executor)
+ {
+ try {
+ $executor = new HostsFileExecutor(
+ HostsFile::loadFromPathBlocking(),
+ $executor
+ );
+ } catch (\RuntimeException $e) {
+ // ignore this file if it can not be loaded
+ }
+
+ // Windows does not store localhost in hosts file by default but handles this internally
+ // To compensate for this, we explicitly use hard-coded defaults for localhost
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $executor = new HostsFileExecutor(
+ new HostsFile("127.0.0.1 localhost\n::1 localhost"),
+ $executor
+ );
+ }
+
+ return $executor;
+ }
+
+ protected function createExecutor(LoopInterface $loop)
+ {
+ return new TimeoutExecutor(
+ new Executor($loop, new Parser(), new BinaryDumper(), null),
+ 5.0,
+ $loop
+ );
+ }
+
+ protected function createRetryExecutor(LoopInterface $loop)
+ {
+ return new RetryExecutor($this->createExecutor($loop));
+ }
+
+ protected function createCachedExecutor(LoopInterface $loop, CacheInterface $cache)
+ {
+ return new CachedExecutor($this->createRetryExecutor($loop), new RecordCache($cache));
+ }
+
+ protected function addPortToServerIfMissing($nameserver)
+ {
+ if (strpos($nameserver, '[') === false && substr_count($nameserver, ':') >= 2) {
+ // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
+ $nameserver = '[' . $nameserver . ']';
+ }
+ // assume a dummy scheme when checking for the port, otherwise parse_url() fails
+ if (parse_url('dummy://' . $nameserver, PHP_URL_PORT) === null) {
+ $nameserver .= ':53';
+ }
+
+ return $nameserver;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Resolver/Resolver.php b/assets/php/vendor/react/dns/src/Resolver/Resolver.php
new file mode 100644
index 0000000..4a4983a
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Resolver/Resolver.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace React\Dns\Resolver;
+
+use React\Dns\Query\ExecutorInterface;
+use React\Dns\Query\Query;
+use React\Dns\RecordNotFoundException;
+use React\Dns\Model\Message;
+
+class Resolver
+{
+ private $nameserver;
+ private $executor;
+
+ public function __construct($nameserver, ExecutorInterface $executor)
+ {
+ $this->nameserver = $nameserver;
+ $this->executor = $executor;
+ }
+
+ public function resolve($domain)
+ {
+ $query = new Query($domain, Message::TYPE_A, Message::CLASS_IN, time());
+ $that = $this;
+
+ return $this->executor
+ ->query($this->nameserver, $query)
+ ->then(function (Message $response) use ($query, $that) {
+ return $that->extractAddress($query, $response);
+ });
+ }
+
+ public function extractAddress(Query $query, Message $response)
+ {
+ $answers = $response->answers;
+
+ $addresses = $this->resolveAliases($answers, $query->name);
+
+ if (0 === count($addresses)) {
+ $message = 'DNS Request did not return valid answer.';
+ throw new RecordNotFoundException($message);
+ }
+
+ $address = $addresses[array_rand($addresses)];
+ return $address;
+ }
+
+ public function resolveAliases(array $answers, $name)
+ {
+ $named = $this->filterByName($answers, $name);
+ $aRecords = $this->filterByType($named, Message::TYPE_A);
+ $cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
+
+ if ($aRecords) {
+ return $this->mapRecordData($aRecords);
+ }
+
+ if ($cnameRecords) {
+ $aRecords = array();
+
+ $cnames = $this->mapRecordData($cnameRecords);
+ foreach ($cnames as $cname) {
+ $targets = $this->filterByName($answers, $cname);
+ $aRecords = array_merge(
+ $aRecords,
+ $this->resolveAliases($answers, $cname)
+ );
+ }
+
+ return $aRecords;
+ }
+
+ return array();
+ }
+
+ private function filterByName(array $answers, $name)
+ {
+ return $this->filterByField($answers, 'name', $name);
+ }
+
+ private function filterByType(array $answers, $type)
+ {
+ return $this->filterByField($answers, 'type', $type);
+ }
+
+ private function filterByField(array $answers, $field, $value)
+ {
+ $value = strtolower($value);
+ return array_filter($answers, function ($answer) use ($field, $value) {
+ return $value === strtolower($answer->$field);
+ });
+ }
+
+ private function mapRecordData(array $records)
+ {
+ return array_map(function ($record) {
+ return $record->data;
+ }, $records);
+ }
+}