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

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

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

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

@@ -0,0 +1,127 @@
<?php
namespace React\Dns\Config;
use RuntimeException;
class Config
{
/**
* Loads the system DNS configuration
*
* Note that this method may block while loading its internal files and/or
* commands and should thus be used with care! While this should be
* relatively fast for most systems, it remains unknown if this may block
* under certain circumstances. In particular, this method should only be
* executed before the loop starts, not while it is running.
*
* Note that this method will try to access its files and/or commands and
* try to parse its output. Currently, this will only parse valid nameserver
* entries from its output and will ignore all other output without
* complaining.
*
* Note that the previous section implies that this may return an empty
* `Config` object if no valid nameserver entries can be found.
*
* @return self
* @codeCoverageIgnore
*/
public static function loadSystemConfigBlocking()
{
// Use WMIC output on Windows
if (DIRECTORY_SEPARATOR === '\\') {
return self::loadWmicBlocking();
}
// otherwise (try to) load from resolv.conf
try {
return self::loadResolvConfBlocking();
} catch (RuntimeException $ignored) {
// return empty config if parsing fails (file not found)
return new self();
}
}
/**
* Loads a resolv.conf file (from the given path or default location)
*
* Note that this method blocks while loading the given path and should
* thus be used with care! While this should be relatively fast for normal
* resolv.conf files, this may be an issue if this file is located on a slow
* device or contains an excessive number of entries. In particular, this
* method should only be executed before the loop starts, not while it is
* running.
*
* Note that this method will throw if the given file can not be loaded,
* such as if it is not readable or does not exist. In particular, this file
* is not available on Windows.
*
* Currently, this will only parse valid "nameserver X" lines from the
* given file contents. Lines can be commented out with "#" and ";" and
* invalid lines will be ignored without complaining. See also
* `man resolv.conf` for more details.
*
* Note that the previous section implies that this may return an empty
* `Config` object if no valid "nameserver X" lines can be found. See also
* `man resolv.conf` which suggests that the DNS server on the localhost
* should be used in this case. This is left up to higher level consumers
* of this API.
*
* @param ?string $path (optional) path to resolv.conf file or null=load default location
* @return self
* @throws RuntimeException if the path can not be loaded (does not exist)
*/
public static function loadResolvConfBlocking($path = null)
{
if ($path === null) {
$path = '/etc/resolv.conf';
}
$contents = @file_get_contents($path);
if ($contents === false) {
throw new RuntimeException('Unable to load resolv.conf file "' . $path . '"');
}
preg_match_all('/^nameserver\s+(\S+)\s*$/m', $contents, $matches);
$config = new self();
$config->nameservers = $matches[1];
return $config;
}
/**
* Loads the DNS configurations from Windows's WMIC (from the given command or default command)
*
* Note that this method blocks while loading the given command and should
* thus be used with care! While this should be relatively fast for normal
* WMIC commands, it remains unknown if this may block under certain
* circumstances. In particular, this method should only be executed before
* the loop starts, not while it is running.
*
* Note that this method will only try to execute the given command try to
* parse its output, irrespective of whether this command exists. In
* particular, this command is only available on Windows. Currently, this
* will only parse valid nameserver entries from the command output and will
* ignore all other output without complaining.
*
* Note that the previous section implies that this may return an empty
* `Config` object if no valid nameserver entries can be found.
*
* @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
* @return self
* @link https://ss64.com/nt/wmic.html
*/
public static function loadWmicBlocking($command = null)
{
$contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
$config = new self();
$config->nameservers = $matches[1];
return $config;
}
public $nameservers = array();
}

View File

@@ -0,0 +1,73 @@
<?php
namespace React\Dns\Config;
use React\EventLoop\LoopInterface;
use React\Promise;
use React\Promise\Deferred;
use React\Stream\ReadableResourceStream;
use React\Stream\Stream;
/**
* @deprecated
* @see Config see Config class instead.
*/
class FilesystemFactory
{
private $loop;
public function __construct(LoopInterface $loop)
{
$this->loop = $loop;
}
public function create($filename)
{
return $this
->loadEtcResolvConf($filename)
->then(array($this, 'parseEtcResolvConf'));
}
/**
* @param string $contents
* @return Promise
* @deprecated see Config instead
*/
public function parseEtcResolvConf($contents)
{
return Promise\resolve(Config::loadResolvConfBlocking(
'data://text/plain;base64,' . base64_encode($contents)
));
}
public function loadEtcResolvConf($filename)
{
if (!file_exists($filename)) {
return Promise\reject(new \InvalidArgumentException("The filename for /etc/resolv.conf given does not exist: $filename"));
}
try {
$deferred = new Deferred();
$fd = fopen($filename, 'r');
stream_set_blocking($fd, 0);
$contents = '';
$stream = class_exists('React\Stream\ReadableResourceStream') ? new ReadableResourceStream($fd, $this->loop) : new Stream($fd, $this->loop);
$stream->on('data', function ($data) use (&$contents) {
$contents .= $data;
});
$stream->on('end', function () use (&$contents, $deferred) {
$deferred->resolve($contents);
});
$stream->on('error', function ($error) use ($deferred) {
$deferred->reject($error);
});
return $deferred->promise();
} catch (\Exception $e) {
return Promise\reject($e);
}
}
}

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

@@ -0,0 +1,151 @@
<?php
namespace React\Dns\Config;
use RuntimeException;
/**
* Represents a static hosts file which maps hostnames to IPs
*
* Hosts files are used on most systems to avoid actually hitting the DNS for
* certain common hostnames.
*
* Most notably, this file usually contains an entry to map "localhost" to the
* local IP. Windows is a notable exception here, as Windows does not actually
* include "localhost" in this file by default. To compensate for this, this
* class may explicitly be wrapped in another HostsFile instance which
* hard-codes these entries for Windows (see also Factory).
*
* This class mostly exists to abstract the parsing/extraction process so this
* can be replaced with a faster alternative in the future.
*/
class HostsFile
{
/**
* Returns the default path for the hosts file on this system
*
* @return string
* @codeCoverageIgnore
*/
public static function getDefaultPath()
{
// use static path for all Unix-based systems
if (DIRECTORY_SEPARATOR !== '\\') {
return '/etc/hosts';
}
// Windows actually stores the path in the registry under
// \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DataBasePath
$path = '%SystemRoot%\\system32\drivers\etc\hosts';
$base = getenv('SystemRoot');
if ($base === false) {
$base = 'C:\\Windows';
}
return str_replace('%SystemRoot%', $base, $path);
}
/**
* Loads a hosts file (from the given path or default location)
*
* Note that this method blocks while loading the given path and should
* thus be used with care! While this should be relatively fast for normal
* hosts file, this may be an issue if this file is located on a slow device
* or contains an excessive number of entries. In particular, this method
* should only be executed before the loop starts, not while it is running.
*
* @param ?string $path (optional) path to hosts file or null=load default location
* @return self
* @throws RuntimeException if the path can not be loaded (does not exist)
*/
public static function loadFromPathBlocking($path = null)
{
if ($path === null) {
$path = self::getDefaultPath();
}
$contents = @file_get_contents($path);
if ($contents === false) {
throw new RuntimeException('Unable to load hosts file "' . $path . '"');
}
return new self($contents);
}
/**
* Instantiate new hosts file with the given hosts file contents
*
* @param string $contents
*/
public function __construct($contents)
{
// remove all comments from the contents
$contents = preg_replace('/[ \t]*#.*/', '', strtolower($contents));
$this->contents = $contents;
}
/**
* Returns all IPs for the given hostname
*
* @param string $name
* @return string[]
*/
public function getIpsForHost($name)
{
$name = strtolower($name);
$ips = array();
foreach (preg_split('/\r?\n/', $this->contents) as $line) {
$parts = preg_split('/\s+/', $line);
$ip = array_shift($parts);
if ($parts && array_search($name, $parts) !== false) {
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
$ip = substr($ip, 0, $pos);
}
if (@inet_pton($ip) !== false) {
$ips[] = $ip;
}
}
}
return $ips;
}
/**
* Returns all hostnames for the given IPv4 or IPv6 address
*
* @param string $ip
* @return string[]
*/
public function getHostsForIp($ip)
{
// check binary representation of IP to avoid string case and short notation
$ip = @inet_pton($ip);
if ($ip === false) {
return array();
}
$names = array();
foreach (preg_split('/\r?\n/', $this->contents) as $line) {
$parts = preg_split('/\s+/', $line, null, PREG_SPLIT_NO_EMPTY);
$addr = array_shift($parts);
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
$addr = substr($addr, 0, $pos);
}
if (@inet_pton($addr) === $ip) {
foreach ($parts as $part) {
$names[] = $part;
}
}
}
return $names;
}
}

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

@@ -0,0 +1,59 @@
<?php
namespace React\Dns\Model;
class HeaderBag
{
public $attributes = array(
'qdCount' => 0,
'anCount' => 0,
'nsCount' => 0,
'arCount' => 0,
'qr' => 0,
'opcode' => Message::OPCODE_QUERY,
'aa' => 0,
'tc' => 0,
'rd' => 0,
'ra' => 0,
'z' => 0,
'rcode' => Message::RCODE_OK,
);
/**
* @deprecated unused, exists for BC only
*/
public $data = '';
public function get($name)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
}
public function set($name, $value)
{
$this->attributes[$name] = $value;
}
public function isQuery()
{
return 0 === $this->attributes['qr'];
}
public function isResponse()
{
return 1 === $this->attributes['qr'];
}
public function isTruncated()
{
return 1 === $this->attributes['tc'];
}
public function populateCounts(Message $message)
{
$this->attributes['qdCount'] = count($message->questions);
$this->attributes['anCount'] = count($message->answers);
$this->attributes['nsCount'] = count($message->authority);
$this->attributes['arCount'] = count($message->additional);
}
}

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

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

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

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

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

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

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

@@ -0,0 +1,395 @@
<?php
namespace React\Dns\Protocol;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use InvalidArgumentException;
/**
* DNS protocol parser
*
* Obsolete and uncommon types and classes are not implemented.
*/
class Parser
{
/**
* Parses the given raw binary message into a Message object
*
* @param string $data
* @throws InvalidArgumentException
* @return Message
*/
public function parseMessage($data)
{
$message = new Message();
if ($this->parse($data, $message) !== $message) {
throw new InvalidArgumentException('Unable to parse binary message');
}
return $message;
}
/**
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function parseChunk($data, Message $message)
{
return $this->parse($data, $message);
}
private function parse($data, Message $message)
{
$message->data .= $data;
if (!$message->header->get('id')) {
if (!$this->parseHeader($message)) {
return;
}
}
if ($message->header->get('qdCount') != count($message->questions)) {
if (!$this->parseQuestion($message)) {
return;
}
}
// parse all answer records
for ($i = $message->header->get('anCount'); $i > 0; --$i) {
$record = $this->parseRecord($message);
if ($record === null) {
return;
} else {
$message->answers[] = $record;
}
}
// parse all authority records
for ($i = $message->header->get('nsCount'); $i > 0; --$i) {
$record = $this->parseRecord($message);
if ($record === null) {
return;
} else {
$message->authority[] = $record;
}
}
// parse all additional records
for ($i = $message->header->get('arCount'); $i > 0; --$i) {
$record = $this->parseRecord($message);
if ($record === null) {
return;
} else {
$message->additional[] = $record;
}
}
return $message;
}
public function parseHeader(Message $message)
{
if (!isset($message->data[12 - 1])) {
return;
}
$header = substr($message->data, 0, 12);
$message->consumed += 12;
list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', $header));
$rcode = $fields & bindec('1111');
$z = ($fields >> 4) & bindec('111');
$ra = ($fields >> 7) & 1;
$rd = ($fields >> 8) & 1;
$tc = ($fields >> 9) & 1;
$aa = ($fields >> 10) & 1;
$opcode = ($fields >> 11) & bindec('1111');
$qr = ($fields >> 15) & 1;
$vars = compact('id', 'qdCount', 'anCount', 'nsCount', 'arCount',
'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode');
foreach ($vars as $name => $value) {
$message->header->set($name, $value);
}
return $message;
}
public function parseQuestion(Message $message)
{
$consumed = $message->consumed;
list($labels, $consumed) = $this->readLabels($message->data, $consumed);
if ($labels === null || !isset($message->data[$consumed + 4 - 1])) {
return;
}
list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
$consumed += 4;
$message->consumed = $consumed;
$message->questions[] = array(
'name' => implode('.', $labels),
'type' => $type,
'class' => $class,
);
if ($message->header->get('qdCount') != count($message->questions)) {
return $this->parseQuestion($message);
}
return $message;
}
/**
* recursively parse all answers from the message data into message answer records
*
* @param Message $message
* @return ?Message returns the updated message on success or null if the data is invalid/incomplete
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function parseAnswer(Message $message)
{
$record = $this->parseRecord($message);
if ($record === null) {
return null;
}
$message->answers[] = $record;
if ($message->header->get('anCount') != count($message->answers)) {
return $this->parseAnswer($message);
}
return $message;
}
/**
* @param Message $message
* @return ?Record returns parsed Record on success or null if data is invalid/incomplete
*/
private function parseRecord(Message $message)
{
$consumed = $message->consumed;
list($name, $consumed) = $this->readDomain($message->data, $consumed);
if ($name === null || !isset($message->data[$consumed + 10 - 1])) {
return null;
}
list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
$consumed += 4;
list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4)));
$consumed += 4;
// TTL is a UINT32 that must not have most significant bit set for BC reasons
if ($ttl < 0 || $ttl >= 1 << 31) {
$ttl = 0;
}
list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2)));
$consumed += 2;
if (!isset($message->data[$consumed + $rdLength - 1])) {
return null;
}
$rdata = null;
$expected = $consumed + $rdLength;
if (Message::TYPE_A === $type) {
if ($rdLength === 4) {
$rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
$consumed += $rdLength;
}
} elseif (Message::TYPE_AAAA === $type) {
if ($rdLength === 16) {
$rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
$consumed += $rdLength;
}
} elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
list($rdata, $consumed) = $this->readDomain($message->data, $consumed);
} elseif (Message::TYPE_TXT === $type) {
$rdata = array();
while ($consumed < $expected) {
$len = ord($message->data[$consumed]);
$rdata[] = (string)substr($message->data, $consumed + 1, $len);
$consumed += $len + 1;
}
} elseif (Message::TYPE_MX === $type) {
if ($rdLength > 2) {
list($priority) = array_values(unpack('n', substr($message->data, $consumed, 2)));
list($target, $consumed) = $this->readDomain($message->data, $consumed + 2);
$rdata = array(
'priority' => $priority,
'target' => $target
);
}
} elseif (Message::TYPE_SRV === $type) {
if ($rdLength > 6) {
list($priority, $weight, $port) = array_values(unpack('n*', substr($message->data, $consumed, 6)));
list($target, $consumed) = $this->readDomain($message->data, $consumed + 6);
$rdata = array(
'priority' => $priority,
'weight' => $weight,
'port' => $port,
'target' => $target
);
}
} elseif (Message::TYPE_SOA === $type) {
list($mname, $consumed) = $this->readDomain($message->data, $consumed);
list($rname, $consumed) = $this->readDomain($message->data, $consumed);
if ($mname !== null && $rname !== null && isset($message->data[$consumed + 20 - 1])) {
list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($message->data, $consumed, 20)));
$consumed += 20;
$rdata = array(
'mname' => $mname,
'rname' => $rname,
'serial' => $serial,
'refresh' => $refresh,
'retry' => $retry,
'expire' => $expire,
'minimum' => $minimum
);
}
} else {
// unknown types simply parse rdata as an opaque binary string
$rdata = substr($message->data, $consumed, $rdLength);
$consumed += $rdLength;
}
// ensure parsing record data consumes expact number of bytes indicated in record length
if ($consumed !== $expected || $rdata === null) {
return null;
}
$message->consumed = $consumed;
return new Record($name, $type, $class, $ttl, $rdata);
}
private function readDomain($data, $consumed)
{
list ($labels, $consumed) = $this->readLabels($data, $consumed);
if ($labels === null) {
return array(null, null);
}
return array(implode('.', $labels), $consumed);
}
private function readLabels($data, $consumed)
{
$labels = array();
while (true) {
if (!isset($data[$consumed])) {
return array(null, null);
}
$length = \ord($data[$consumed]);
// end of labels reached
if ($length === 0) {
$consumed += 1;
break;
}
// first two bits set? this is a compressed label (14 bit pointer offset)
if (($length & 0xc0) === 0xc0 && isset($data[$consumed + 1])) {
$offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
if ($offset >= $consumed) {
return array(null, null);
}
$consumed += 2;
list($newLabels) = $this->readLabels($data, $offset);
if ($newLabels === null) {
return array(null, null);
}
$labels = array_merge($labels, $newLabels);
break;
}
// length MUST be 0-63 (6 bits only) and data has to be large enough
if ($length & 0xc0 || !isset($data[$consumed + $length - 1])) {
return array(null, null);
}
$labels[] = substr($data, $consumed + 1, $length);
$consumed += $length + 1;
}
return array($labels, $consumed);
}
/**
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function isEndOfLabels($data, $consumed)
{
$length = ord(substr($data, $consumed, 1));
return 0 === $length;
}
/**
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function getCompressedLabel($data, $consumed)
{
list($nameOffset, $consumed) = $this->getCompressedLabelOffset($data, $consumed);
list($labels) = $this->readLabels($data, $nameOffset);
return array($labels, $consumed);
}
/**
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function isCompressedLabel($data, $consumed)
{
$mask = 0xc000; // 1100000000000000
list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
return (bool) ($peek & $mask);
}
/**
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function getCompressedLabelOffset($data, $consumed)
{
$mask = 0x3fff; // 0011111111111111
list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
return array($peek & $mask, $consumed + 2);
}
/**
* @deprecated unused, exists for BC only
* @codeCoverageIgnore
*/
public function signedLongToUnsignedLong($i)
{
return $i & 0x80000000 ? $i - 0xffffffff : $i;
}
}

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

@@ -0,0 +1,59 @@
<?php
namespace React\Dns\Query;
use React\Dns\Model\Message;
/**
* @deprecated unused, exists for BC only
* @see CachingExecutor
*/
class CachedExecutor implements ExecutorInterface
{
private $executor;
private $cache;
public function __construct(ExecutorInterface $executor, RecordCache $cache)
{
$this->executor = $executor;
$this->cache = $cache;
}
public function query($nameserver, Query $query)
{
$executor = $this->executor;
$cache = $this->cache;
return $this->cache
->lookup($query)
->then(
function ($cachedRecords) use ($query) {
return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
},
function () use ($executor, $cache, $nameserver, $query) {
return $executor
->query($nameserver, $query)
->then(function ($response) use ($cache, $query) {
$cache->storeResponseMessage($query->currentTime, $response);
return $response;
});
}
);
}
/**
* @deprecated unused, exists for BC only
*/
public function buildResponse(Query $query, array $cachedRecords)
{
return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
}
/**
* @deprecated unused, exists for BC only
*/
protected function generateId()
{
return mt_rand(0, 0xffff);
}
}

View File

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

View File

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

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

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

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

@@ -0,0 +1,160 @@
<?php
namespace React\Dns\Query;
use React\Dns\Model\Message;
use React\Dns\Protocol\Parser;
use React\Dns\Protocol\BinaryDumper;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
use React\Promise;
use React\Stream\DuplexResourceStream;
use React\Stream\Stream;
/**
* @deprecated unused, exists for BC only
* @see UdpTransportExecutor
*/
class Executor implements ExecutorInterface
{
private $loop;
private $parser;
private $dumper;
private $timeout;
/**
*
* Note that albeit supported, the $timeout parameter is deprecated!
* You should pass a `null` value here instead. If you need timeout handling,
* use the `TimeoutConnector` instead.
*
* @param LoopInterface $loop
* @param Parser $parser
* @param BinaryDumper $dumper
* @param null|float $timeout DEPRECATED: timeout for DNS query or NULL=no timeout
*/
public function __construct(LoopInterface $loop, Parser $parser, BinaryDumper $dumper, $timeout = 5)
{
$this->loop = $loop;
$this->parser = $parser;
$this->dumper = $dumper;
$this->timeout = $timeout;
}
public function query($nameserver, Query $query)
{
$request = Message::createRequestForQuery($query);
$queryData = $this->dumper->toBinary($request);
$transport = strlen($queryData) > 512 ? 'tcp' : 'udp';
return $this->doQuery($nameserver, $transport, $queryData, $query->name);
}
/**
* @deprecated unused, exists for BC only
*/
public function prepareRequest(Query $query)
{
return Message::createRequestForQuery($query);
}
public function doQuery($nameserver, $transport, $queryData, $name)
{
// we only support UDP right now
if ($transport !== 'udp') {
return Promise\reject(new \RuntimeException(
'DNS query for ' . $name . ' failed: Requested transport "' . $transport . '" not available, only UDP is supported in this version'
));
}
$that = $this;
$parser = $this->parser;
$loop = $this->loop;
// UDP connections are instant, so try this without a timer
try {
$conn = $this->createConnection($nameserver, $transport);
} catch (\Exception $e) {
return Promise\reject(new \RuntimeException('DNS query for ' . $name . ' failed: ' . $e->getMessage(), 0, $e));
}
$deferred = new Deferred(function ($resolve, $reject) use (&$timer, $loop, &$conn, $name) {
$reject(new CancellationException(sprintf('DNS query for %s has been cancelled', $name)));
if ($timer !== null) {
$loop->cancelTimer($timer);
}
$conn->close();
});
$timer = null;
if ($this->timeout !== null) {
$timer = $this->loop->addTimer($this->timeout, function () use (&$conn, $name, $deferred) {
$conn->close();
$deferred->reject(new TimeoutException(sprintf("DNS query for %s timed out", $name)));
});
}
$conn->on('data', function ($data) use ($conn, $parser, $deferred, $timer, $loop, $name) {
$conn->end();
if ($timer !== null) {
$loop->cancelTimer($timer);
}
try {
$response = $parser->parseMessage($data);
} catch (\Exception $e) {
$deferred->reject($e);
return;
}
if ($response->header->isTruncated()) {
$deferred->reject(new \RuntimeException('DNS query for ' . $name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
return;
}
$deferred->resolve($response);
});
$conn->write($queryData);
return $deferred->promise();
}
/**
* @deprecated unused, exists for BC only
*/
protected function generateId()
{
return mt_rand(0, 0xffff);
}
/**
* @param string $nameserver
* @param string $transport
* @return \React\Stream\DuplexStreamInterface
*/
protected function createConnection($nameserver, $transport)
{
$fd = @stream_socket_client("$transport://$nameserver", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
if ($fd === false) {
throw new \RuntimeException('Unable to connect to DNS server: ' . $errstr, $errno);
}
// Instantiate stream instance around this stream resource.
// This ought to be replaced with a datagram socket in the future.
// Temporary work around for Windows 10: buffer whole UDP response
// @coverageIgnoreStart
if (!class_exists('React\Stream\Stream')) {
// prefer DuplexResourceStream as of react/stream v0.7.0
$conn = new DuplexResourceStream($fd, $this->loop, -1);
} else {
// use legacy Stream class for react/stream < v0.7.0
$conn = new Stream($fd, $this->loop);
$conn->bufferSize = null;
}
// @coverageIgnoreEnd
return $conn;
}
}

View File

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

View File

@@ -0,0 +1,89 @@
<?php
namespace React\Dns\Query;
use React\Dns\Config\HostsFile;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use React\Promise;
/**
* Resolves hosts from the given HostsFile or falls back to another executor
*
* If the host is found in the hosts file, it will not be passed to the actual
* DNS executor. If the host is not found in the hosts file, it will be passed
* to the DNS executor as a fallback.
*/
class HostsFileExecutor implements ExecutorInterface
{
private $hosts;
private $fallback;
public function __construct(HostsFile $hosts, ExecutorInterface $fallback)
{
$this->hosts = $hosts;
$this->fallback = $fallback;
}
public function query($nameserver, Query $query)
{
if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
// forward lookup for type A or AAAA
$records = array();
$expectsColon = $query->type === Message::TYPE_AAAA;
foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
// ensure this is an IPv4/IPV6 address according to query type
if ((strpos($ip, ':') !== false) === $expectsColon) {
$records[] = new Record($query->name, $query->type, $query->class, 0, $ip);
}
}
if ($records) {
return Promise\resolve(
Message::createResponseWithAnswersForQuery($query, $records)
);
}
} elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
// reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
$ip = $this->getIpFromHost($query->name);
if ($ip !== null) {
$records = array();
foreach ($this->hosts->getHostsForIp($ip) as $host) {
$records[] = new Record($query->name, $query->type, $query->class, 0, $host);
}
if ($records) {
return Promise\resolve(
Message::createResponseWithAnswersForQuery($query, $records)
);
}
}
}
return $this->fallback->query($nameserver, $query);
}
private function getIpFromHost($host)
{
if (substr($host, -13) === '.in-addr.arpa') {
// IPv4: read as IP and reverse bytes
$ip = @inet_pton(substr($host, 0, -13));
if ($ip === false || isset($ip[4])) {
return null;
}
return inet_ntop(strrev($ip));
} elseif (substr($host, -9) === '.ip6.arpa') {
// IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
$ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
if ($ip === false) {
return null;
}
return $ip;
} else {
return null;
}
}
}

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

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

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

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

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

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

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

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

View File

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

View File

@@ -0,0 +1,32 @@
<?php
namespace React\Dns\Query;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
use React\Promise\CancellablePromiseInterface;
use React\Promise\Timer;
class TimeoutExecutor implements ExecutorInterface
{
private $executor;
private $loop;
private $timeout;
public function __construct(ExecutorInterface $executor, $timeout, LoopInterface $loop)
{
$this->executor = $executor;
$this->loop = $loop;
$this->timeout = $timeout;
}
public function query($nameserver, Query $query)
{
return Timer\timeout($this->executor->query($nameserver, $query), $this->timeout, $this->loop)->then(null, function ($e) use ($query) {
if ($e instanceof Timer\TimeoutException) {
$e = new TimeoutException(sprintf("DNS query for %s timed out", $query->name), 0, $e);
}
throw $e;
});
}
}

View File

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

View File

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

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

@@ -0,0 +1,102 @@
<?php
namespace React\Dns\Resolver;
use React\Cache\ArrayCache;
use React\Cache\CacheInterface;
use React\Dns\Config\HostsFile;
use React\Dns\Query\CachingExecutor;
use React\Dns\Query\CoopExecutor;
use React\Dns\Query\ExecutorInterface;
use React\Dns\Query\HostsFileExecutor;
use React\Dns\Query\RetryExecutor;
use React\Dns\Query\TimeoutExecutor;
use React\Dns\Query\UdpTransportExecutor;
use React\EventLoop\LoopInterface;
class Factory
{
public function create($nameserver, LoopInterface $loop)
{
$nameserver = $this->addPortToServerIfMissing($nameserver);
$executor = $this->decorateHostsFileExecutor($this->createRetryExecutor($loop));
return new Resolver($nameserver, $executor);
}
public function createCached($nameserver, LoopInterface $loop, CacheInterface $cache = null)
{
// default to keeping maximum of 256 responses in cache unless explicitly given
if (!($cache instanceof CacheInterface)) {
$cache = new ArrayCache(256);
}
$nameserver = $this->addPortToServerIfMissing($nameserver);
$executor = $this->decorateHostsFileExecutor($this->createCachedExecutor($loop, $cache));
return new Resolver($nameserver, $executor);
}
/**
* Tries to load the hosts file and decorates the given executor on success
*
* @param ExecutorInterface $executor
* @return ExecutorInterface
* @codeCoverageIgnore
*/
private function decorateHostsFileExecutor(ExecutorInterface $executor)
{
try {
$executor = new HostsFileExecutor(
HostsFile::loadFromPathBlocking(),
$executor
);
} catch (\RuntimeException $e) {
// ignore this file if it can not be loaded
}
// Windows does not store localhost in hosts file by default but handles this internally
// To compensate for this, we explicitly use hard-coded defaults for localhost
if (DIRECTORY_SEPARATOR === '\\') {
$executor = new HostsFileExecutor(
new HostsFile("127.0.0.1 localhost\n::1 localhost"),
$executor
);
}
return $executor;
}
protected function createExecutor(LoopInterface $loop)
{
return new TimeoutExecutor(
new UdpTransportExecutor($loop),
5.0,
$loop
);
}
protected function createRetryExecutor(LoopInterface $loop)
{
return new CoopExecutor(new RetryExecutor($this->createExecutor($loop)));
}
protected function createCachedExecutor(LoopInterface $loop, CacheInterface $cache)
{
return new CachingExecutor($this->createRetryExecutor($loop), $cache);
}
protected function addPortToServerIfMissing($nameserver)
{
if (strpos($nameserver, '[') === false && substr_count($nameserver, ':') >= 2) {
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
$nameserver = '[' . $nameserver . ']';
}
// assume a dummy scheme when checking for the port, otherwise parse_url() fails
if (parse_url('dummy://' . $nameserver, PHP_URL_PORT) === null) {
$nameserver .= ':53';
}
return $nameserver;
}
}

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

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