Initial Commit

This commit is contained in:
David Stone
2024-11-30 18:24:12 -07:00
commit e8f7955c1c
5432 changed files with 1397750 additions and 0 deletions

38
dependencies/amphp/dns/appveyor.yml vendored Normal file
View File

@ -0,0 +1,38 @@
build: false
shallow_clone: false
platform:
- x86
- x64
clone_folder: c:\WP_Ultimo\Dependencies\projects\amphp
cache:
- c:\WP_Ultimo\Dependencies\tools\php74 -> appveyor.yml
init:
- SET PATH=C:\Program WP_Ultimo\Dependencies\Files\OpenSSL;c:\WP_Ultimo\Dependencies\tools\php74;%PATH%
- SET COMPOSER_NO_INTERACTION=1
- SET PHP=1
- SET ANSICON=121x90 (121x90)
install:
- IF EXIST c:\WP_Ultimo\Dependencies\tools\php74 (SET PHP=0)
- IF %PHP%==1 sc config wuauserv start= auto
- IF %PHP%==1 net start wuauserv
- IF %PHP%==1 cinst -y OpenSSL.Light
- IF %PHP%==1 cinst -y php
- cd c:\WP_Ultimo\Dependencies\tools\php74
- IF %PHP%==1 copy php.ini-production php.ini /Y
- IF %PHP%==1 echo date.timezone="UTC" >> php.ini
- IF %PHP%==1 echo extension_dir=ext >> php.ini
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
- IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini
- IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini
- cd c:\WP_Ultimo\Dependencies\projects\amphp
- appveyor DownloadFile https://getcomposer.org/composer.phar
- php composer.phar install --prefer-dist --no-progress
test_script:
- cd c:\WP_Ultimo\Dependencies\projects\amphp
- vendor/bin/phpunit --colors=always

View File

@ -0,0 +1,35 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
use WP_Ultimo\Dependencies\Amp\Failure;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\Amp\Success;
class BlockingFallbackResolver implements Resolver
{
public function resolve(string $name, int $typeRestriction = null) : Promise
{
if (!\in_array($typeRestriction, [Record::A, null], \true)) {
return new Failure(new DnsException("Query for '{$name}' failed, because loading the system's DNS configuration failed and querying records other than A records isn't supported in blocking fallback mode."));
}
return $this->query($name, Record::A);
}
public function query(string $name, int $type) : Promise
{
if ($type !== Record::A) {
return new Failure(new DnsException("Query for '{$name}' failed, because loading the system's DNS configuration failed and querying records other than A records isn't supported in blocking fallback mode."));
}
$result = \gethostbynamel($name);
if ($result === \false) {
return new Failure(new DnsException("Query for '{$name}' failed, because loading the system's DNS configuration failed and blocking fallback via gethostbynamel() failed, too."));
}
if ($result === []) {
return new Failure(new NoRecordException("No records returned for '{$name}' using blocking fallback mode."));
}
$records = [];
foreach ($result as $record) {
$records[] = new Record($record, Record::A, null);
}
return new Success($records);
}
}

137
dependencies/amphp/dns/lib/Config.php vendored Normal file
View File

@ -0,0 +1,137 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
final class Config
{
/** @var array */
private $nameservers;
/** @var array */
private $knownHosts;
/** @var int */
private $timeout;
/** @var int */
private $attempts;
/** @var array */
private $searchList = [];
/** @var int */
private $ndots = 1;
/** @var bool */
private $rotation = \false;
public function __construct(array $nameservers, array $knownHosts = [], int $timeout = 3000, int $attempts = 2)
{
if (\count($nameservers) < 1) {
throw new ConfigException("At least one nameserver is required for a valid config");
}
foreach ($nameservers as $nameserver) {
$this->validateNameserver($nameserver);
}
if ($timeout < 0) {
throw new ConfigException("Invalid timeout ({$timeout}), must be 0 or greater");
}
if ($attempts < 1) {
throw new ConfigException("Invalid attempt count ({$attempts}), must be 1 or greater");
}
// Windows does not include localhost in its host file. Fetch it from the system instead
if (!isset($knownHosts[Record::A]["localhost"]) && !isset($knownHosts[Record::AAAA]["localhost"])) {
// PHP currently provides no way to **resolve** IPv6 hostnames (not even with fallback)
$local = \gethostbyname("localhost");
if ($local !== "localhost") {
$knownHosts[Record::A]["localhost"] = $local;
} else {
$knownHosts[Record::AAAA]["localhost"] = '::1';
}
}
$this->nameservers = $nameservers;
$this->knownHosts = $knownHosts;
$this->timeout = $timeout;
$this->attempts = $attempts;
}
public function withSearchList(array $searchList) : self
{
$self = clone $this;
$self->searchList = $searchList;
return $self;
}
/**
* @throws ConfigException
*/
public function withNdots(int $ndots) : self
{
if ($ndots < 0) {
throw new ConfigException("Invalid ndots ({$ndots}), must be greater or equal to 0");
}
if ($ndots > 15) {
$ndots = 15;
}
$self = clone $this;
$self->ndots = $ndots;
return $self;
}
public function withRotationEnabled(bool $enabled = \true) : self
{
$self = clone $this;
$self->rotation = $enabled;
return $self;
}
private function validateNameserver($nameserver)
{
if (!$nameserver || !\is_string($nameserver)) {
throw new ConfigException("Invalid nameserver: {$nameserver}");
}
if ($nameserver[0] === "[") {
// IPv6
$addr = \strstr(\substr($nameserver, 1), "]", \true);
$port = \substr($nameserver, \strrpos($nameserver, "]") + 1);
if ($port !== "" && !\preg_match("(^:(\\d+)\$)", $port, $match)) {
throw new ConfigException("Invalid nameserver: {$nameserver}");
}
$port = $port === "" ? 53 : \substr($port, 1);
} else {
// IPv4
$arr = \explode(":", $nameserver, 2);
if (\count($arr) === 2) {
list($addr, $port) = $arr;
} else {
$addr = $arr[0];
$port = 53;
}
}
$addr = \trim($addr, "[]");
$port = (int) $port;
if (!($inAddr = @\inet_pton($addr))) {
throw new ConfigException("Invalid server IP: {$addr}");
}
if ($port < 1 || $port > 65535) {
throw new ConfigException("Invalid server port: {$port}");
}
}
public function getNameservers() : array
{
return $this->nameservers;
}
public function getKnownHosts() : array
{
return $this->knownHosts;
}
public function getTimeout() : int
{
return $this->timeout;
}
public function getAttempts() : int
{
return $this->attempts;
}
public function getSearchList() : array
{
return $this->searchList;
}
public function getNdots() : int
{
return $this->ndots;
}
public function isRotationEnabled() : bool
{
return $this->rotation;
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
use Throwable;
/**
* MUST be thrown in case the config can't be read and no fallback is available.
*/
class ConfigException extends DnsException
{
public function __construct(string $message, Throwable $previous = null)
{
parent::__construct($message, 0, $previous);
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
use WP_Ultimo\Dependencies\Amp\Promise;
interface ConfigLoader
{
public function loadConfig() : Promise;
}

View File

@ -0,0 +1,7 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
class DnsException extends \Exception
{
}

View File

@ -0,0 +1,70 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
use WP_Ultimo\Dependencies\Amp\Failure;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\Amp\Success;
use function WP_Ultimo\Dependencies\Amp\call;
class HostLoader
{
private $path;
public function __construct(string $path = null)
{
$this->path = $path ?? $this->getDefaultPath();
}
private function getDefaultPath() : string
{
return \stripos(\PHP_OS, "win") === 0 ? 'C:\\Windows\\system32\\drivers\\etc\\hosts' : '/etc/hosts';
}
protected function readFile(string $path) : Promise
{
\set_error_handler(function (int $errno, string $message) use($path) {
throw new ConfigException("Could not read configuration file '{$path}' ({$errno}) {$message}");
});
try {
// Blocking file access, but this file should be local and usually loaded only once.
$fileContent = \file_get_contents($path);
} catch (ConfigException $exception) {
return new Failure($exception);
} finally {
\restore_error_handler();
}
return new Success($fileContent);
}
public function loadHosts() : Promise
{
return call(function () {
try {
$contents = (yield $this->readFile($this->path));
} catch (ConfigException $exception) {
return [];
}
$data = [];
$lines = \array_filter(\array_map("trim", \explode("\n", $contents)));
foreach ($lines as $line) {
if ($line[0] === "#") {
// Skip comments
continue;
}
$parts = \preg_split('/\\s+/', $line);
if (!($ip = @\inet_pton($parts[0]))) {
continue;
} elseif (isset($ip[4])) {
$key = Record::AAAA;
} else {
$key = Record::A;
}
for ($i = 1, $l = \count($parts); $i < $l; $i++) {
try {
$normalizedName = normalizeName($parts[$i]);
$data[$key][$normalizedName] = $parts[0];
} catch (InvalidNameException $e) {
// ignore invalid entries
}
}
}
return $data;
});
}
}

View File

@ -0,0 +1,217 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns\Internal;
use WP_Ultimo\Dependencies\Amp;
use WP_Ultimo\Dependencies\Amp\ByteStream\ResourceInputStream;
use WP_Ultimo\Dependencies\Amp\ByteStream\ResourceOutputStream;
use WP_Ultimo\Dependencies\Amp\ByteStream\StreamException;
use WP_Ultimo\Dependencies\Amp\Deferred;
use WP_Ultimo\Dependencies\Amp\Dns\DnsException;
use WP_Ultimo\Dependencies\Amp\Dns\TimeoutException;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\LibDNS\Messages\Message;
use WP_Ultimo\Dependencies\LibDNS\Messages\MessageFactory;
use WP_Ultimo\Dependencies\LibDNS\Messages\MessageTypes;
use WP_Ultimo\Dependencies\LibDNS\Records\Question;
use function WP_Ultimo\Dependencies\Amp\call;
/** @internal */
abstract class Socket
{
const MAX_CONCURRENT_REQUESTS = 500;
/** @var ResourceInputStream */
private $input;
/** @var ResourceOutputStream */
private $output;
/** @var array Contains already sent queries with no response yet. For UDP this is exactly zero or one item. */
private $pending = [];
/** @var MessageFactory */
private $messageFactory;
/** @var callable */
private $onResolve;
/** @var int Used for determining whether the socket can be garbage collected, because it's inactive. */
private $lastActivity;
/** @var bool */
private $receiving = \false;
/** @var array Queued requests if the number of concurrent requests is too large. */
private $queue = [];
/**
* @param string $uri
*
* @return Promise<self>
*/
public static abstract function connect(string $uri) : Promise;
/**
* @param Message $message
*
* @return Promise<int>
*/
protected abstract function send(Message $message) : Promise;
/**
* @return Promise<Message>
*/
protected abstract function receive() : Promise;
/**
* @return bool
*/
public abstract function isAlive() : bool;
public function getLastActivity() : int
{
return $this->lastActivity;
}
protected function __construct($socket)
{
$this->input = new ResourceInputStream($socket);
$this->output = new ResourceOutputStream($socket);
$this->messageFactory = new MessageFactory();
$this->lastActivity = \time();
$this->onResolve = function (\Throwable $exception = null, Message $message = null) {
$this->lastActivity = \time();
$this->receiving = \false;
if ($exception) {
$this->error($exception);
return;
}
\assert($message instanceof Message);
$id = $message->getId();
// Ignore duplicate and invalid responses.
if (isset($this->pending[$id]) && $this->matchesQuestion($message, $this->pending[$id]->question)) {
/** @var Deferred $deferred */
$deferred = $this->pending[$id]->deferred;
unset($this->pending[$id]);
$deferred->resolve($message);
}
if (empty($this->pending)) {
$this->input->unreference();
} elseif (!$this->receiving) {
$this->input->reference();
$this->receiving = \true;
$this->receive()->onResolve($this->onResolve);
}
};
}
/**
* @param \LibDNS\Records\Question $question
* @param int $timeout
*
* @return \Amp\Promise<\LibDNS\Messages\Message>
*/
public final function ask(Question $question, int $timeout) : Promise
{
return call(function () use($question, $timeout) {
$this->lastActivity = \time();
if (\count($this->pending) > self::MAX_CONCURRENT_REQUESTS) {
$deferred = new Deferred();
$this->queue[] = $deferred;
(yield $deferred->promise());
}
do {
$id = \random_int(0, 0xffff);
} while (isset($this->pending[$id]));
$deferred = new Deferred();
$pending = new class
{
use Amp\Struct;
public $deferred;
public $question;
};
$pending->deferred = $deferred;
$pending->question = $question;
$this->pending[$id] = $pending;
$message = $this->createMessage($question, $id);
try {
(yield $this->send($message));
} catch (StreamException $exception) {
$exception = new DnsException("Sending the request failed", 0, $exception);
$this->error($exception);
throw $exception;
}
$this->input->reference();
if (!$this->receiving) {
$this->receiving = \true;
$this->receive()->onResolve($this->onResolve);
}
try {
// Work around an OPCache issue that returns an empty array with "return yield ...",
// so assign to a variable first and return after the try block.
//
// See https://github.com/amphp/dns/issues/58.
// See https://bugs.php.net/bug.php?id=74840.
$result = (yield Promise\timeout($deferred->promise(), $timeout));
} catch (Amp\TimeoutException $exception) {
unset($this->pending[$id]);
if (empty($this->pending)) {
$this->input->unreference();
}
throw new TimeoutException("Didn't receive a response within {$timeout} milliseconds.");
} finally {
if ($this->queue) {
$deferred = \array_shift($this->queue);
$deferred->resolve();
}
}
return $result;
});
}
public final function close()
{
$this->input->close();
$this->output->close();
}
private function error(\Throwable $exception)
{
$this->close();
if (empty($this->pending)) {
return;
}
if (!$exception instanceof DnsException) {
$message = "Unexpected error during resolution: " . $exception->getMessage();
$exception = new DnsException($message, 0, $exception);
}
$pending = $this->pending;
$this->pending = [];
foreach ($pending as $pendingQuestion) {
/** @var Deferred $deferred */
$deferred = $pendingQuestion->deferred;
$deferred->fail($exception);
}
}
protected final function read() : Promise
{
return $this->input->read();
}
protected final function write(string $data) : Promise
{
return $this->output->write($data);
}
protected final function createMessage(Question $question, int $id) : Message
{
$request = $this->messageFactory->create(MessageTypes::QUERY);
$request->getQuestionRecords()->add($question);
$request->isRecursionDesired(\true);
$request->setID($id);
return $request;
}
private function matchesQuestion(Message $message, Question $question) : bool
{
if ($message->getType() !== MessageTypes::RESPONSE) {
return \false;
}
$questionRecords = $message->getQuestionRecords();
// We only ever ask one question at a time
if (\count($questionRecords) !== 1) {
return \false;
}
$questionRecord = $questionRecords->getIterator()->current();
if ($questionRecord->getClass() !== $question->getClass()) {
return \false;
}
if ($questionRecord->getType() !== $question->getType()) {
return \false;
}
if ($questionRecord->getName()->getValue() !== $question->getName()->getValue()) {
return \false;
}
return \true;
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns\Internal;
use WP_Ultimo\Dependencies\Amp;
use WP_Ultimo\Dependencies\Amp\Deferred;
use WP_Ultimo\Dependencies\Amp\Dns\DnsException;
use WP_Ultimo\Dependencies\Amp\Dns\TimeoutException;
use WP_Ultimo\Dependencies\Amp\Loop;
use WP_Ultimo\Dependencies\Amp\Parser\Parser;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\Amp\Success;
use WP_Ultimo\Dependencies\LibDNS\Decoder\DecoderFactory;
use WP_Ultimo\Dependencies\LibDNS\Encoder\EncoderFactory;
use WP_Ultimo\Dependencies\LibDNS\Messages\Message;
use function WP_Ultimo\Dependencies\Amp\call;
/** @internal */
final class TcpSocket extends Socket
{
/** @var \LibDNS\Encoder\Encoder */
private $encoder;
/** @var \SplQueue */
private $queue;
/** @var Parser */
private $parser;
/** @var bool */
private $isAlive = \true;
public static function connect(string $uri, int $timeout = 5000) : Promise
{
if (!($socket = @\stream_socket_client($uri, $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT))) {
throw new DnsException(\sprintf("Connection to %s failed: [Error #%d] %s", $uri, $errno, $errstr));
}
\stream_set_blocking($socket, \false);
return call(function () use($uri, $socket, $timeout) {
$deferred = new Deferred();
$watcher = Loop::onWritable($socket, static function () use($socket, $deferred) {
$deferred->resolve(new self($socket));
});
try {
return (yield Promise\timeout($deferred->promise(), $timeout));
} catch (Amp\TimeoutException $e) {
throw new TimeoutException("Name resolution timed out, could not connect to server at {$uri}");
} finally {
Loop::cancel($watcher);
}
});
}
public static function parser(callable $callback) : \Generator
{
$decoder = (new DecoderFactory())->create();
while (\true) {
$length = (yield 2);
$length = \unpack("n", $length)[1];
$rawData = (yield $length);
$callback($decoder->decode($rawData));
}
}
protected function __construct($socket)
{
parent::__construct($socket);
$this->encoder = (new EncoderFactory())->create();
$this->queue = new \SplQueue();
$this->parser = new Parser(self::parser([$this->queue, 'push']));
}
protected function send(Message $message) : Promise
{
$data = $this->encoder->encode($message);
$promise = $this->write(\pack("n", \strlen($data)) . $data);
$promise->onResolve(function ($error) {
if ($error) {
$this->isAlive = \false;
}
});
return $promise;
}
protected function receive() : Promise
{
if ($this->queue->isEmpty()) {
return call(function () {
do {
$chunk = (yield $this->read());
if ($chunk === null) {
$this->isAlive = \false;
throw new DnsException("Reading from the server failed");
}
$this->parser->push($chunk);
} while ($this->queue->isEmpty());
return $this->queue->shift();
});
}
return new Success($this->queue->shift());
}
public function isAlive() : bool
{
return $this->isAlive;
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns\Internal;
use WP_Ultimo\Dependencies\Amp\Dns\DnsException;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\Amp\Success;
use WP_Ultimo\Dependencies\LibDNS\Decoder\DecoderFactory;
use WP_Ultimo\Dependencies\LibDNS\Encoder\EncoderFactory;
use WP_Ultimo\Dependencies\LibDNS\Messages\Message;
use function WP_Ultimo\Dependencies\Amp\call;
/** @internal */
final class UdpSocket extends Socket
{
/** @var \LibDNS\Encoder\Encoder */
private $encoder;
/** @var \LibDNS\Decoder\Decoder */
private $decoder;
public static function connect(string $uri) : Promise
{
if (!($socket = @\stream_socket_client($uri, $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT))) {
throw new DnsException(\sprintf("Connection to %s failed: [Error #%d] %s", $uri, $errno, $errstr));
}
return new Success(new self($socket));
}
protected function __construct($socket)
{
parent::__construct($socket);
$this->encoder = (new EncoderFactory())->create();
$this->decoder = (new DecoderFactory())->create();
}
protected function send(Message $message) : Promise
{
$data = $this->encoder->encode($message);
return $this->write($data);
}
protected function receive() : Promise
{
return call(function () {
$data = (yield $this->read());
if ($data === null) {
throw new DnsException("Reading from the server failed");
}
return $this->decoder->decode($data);
});
}
public function isAlive() : bool
{
return \true;
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
class InvalidNameException extends DnsException
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
class NoRecordException extends DnsException
{
}

98
dependencies/amphp/dns/lib/Record.php vendored Normal file
View File

@ -0,0 +1,98 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
use WP_Ultimo\Dependencies\LibDNS\Records\ResourceQTypes;
use WP_Ultimo\Dependencies\LibDNS\Records\ResourceTypes;
final class Record
{
const A = ResourceTypes::A;
const AAAA = ResourceTypes::AAAA;
const AFSDB = ResourceTypes::AFSDB;
// const APL = ResourceTypes::APL;
const CAA = ResourceTypes::CAA;
const CERT = ResourceTypes::CERT;
const CNAME = ResourceTypes::CNAME;
const DHCID = ResourceTypes::DHCID;
const DLV = ResourceTypes::DLV;
const DNAME = ResourceTypes::DNAME;
const DNSKEY = ResourceTypes::DNSKEY;
const DS = ResourceTypes::DS;
const HINFO = ResourceTypes::HINFO;
// const HIP = ResourceTypes::HIP;
// const IPSECKEY = ResourceTypes::IPSECKEY;
const KEY = ResourceTypes::KEY;
const KX = ResourceTypes::KX;
const ISDN = ResourceTypes::ISDN;
const LOC = ResourceTypes::LOC;
const MB = ResourceTypes::MB;
const MD = ResourceTypes::MD;
const MF = ResourceTypes::MF;
const MG = ResourceTypes::MG;
const MINFO = ResourceTypes::MINFO;
const MR = ResourceTypes::MR;
const MX = ResourceTypes::MX;
const NAPTR = ResourceTypes::NAPTR;
const NS = ResourceTypes::NS;
// const NSEC = ResourceTypes::NSEC;
// const NSEC3 = ResourceTypes::NSEC3;
// const NSEC3PARAM = ResourceTypes::NSEC3PARAM;
const NULL = ResourceTypes::NULL;
const PTR = ResourceTypes::PTR;
const RP = ResourceTypes::RP;
// const RRSIG = ResourceTypes::RRSIG;
const RT = ResourceTypes::RT;
const SIG = ResourceTypes::SIG;
const SOA = ResourceTypes::SOA;
const SPF = ResourceTypes::SPF;
const SRV = ResourceTypes::SRV;
const TXT = ResourceTypes::TXT;
const WKS = ResourceTypes::WKS;
const X25 = ResourceTypes::X25;
const AXFR = ResourceQTypes::AXFR;
const MAILB = ResourceQTypes::MAILB;
const MAILA = ResourceQTypes::MAILA;
const ALL = ResourceQTypes::ALL;
private $value;
private $type;
private $ttl;
public function __construct(string $value, int $type, int $ttl = null)
{
$this->value = $value;
$this->type = $type;
$this->ttl = $ttl;
}
public function getValue() : string
{
return $this->value;
}
public function getType() : int
{
return $this->type;
}
public function getTtl()
{
return $this->ttl;
}
/**
* Converts an record type integer back into its name as defined in this class.
*
* Returns "unknown (<type>)" in case a name for this record is not known.
*
* @param int $type Record type as integer.
*
* @return string Name of the constant for this record in this class.
*/
public static function getName(int $type) : string
{
static $types;
if (0 > $type || 0xffff < $type) {
$message = \sprintf('%d does not correspond to a valid record type (must be between 0 and 65535).', $type);
throw new \Error($message);
}
if ($types === null) {
$types = \array_flip((new \ReflectionClass(self::class))->getConstants());
}
return $types[$type] ?? "unknown ({$type})";
}
}

32
dependencies/amphp/dns/lib/Resolver.php vendored Normal file
View File

@ -0,0 +1,32 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
use WP_Ultimo\Dependencies\Amp\Promise;
interface Resolver
{
/**
* Resolves a hostname name to an IP address [hostname as defined by RFC 3986].
*
* Upon success the returned promise resolves to an array of Record objects.
*
* A null $ttl value indicates the DNS name was resolved from the cache or the local hosts file.
*
* @param string $name The hostname to resolve.
* @param int $typeRestriction Optional type restriction to `Record::A` or `Record::AAAA`, otherwise `null`.
*
* @return Promise
*/
public function resolve(string $name, int $typeRestriction = null) : Promise;
/**
* Query specific DNS records.
*
* Upon success the returned promise resolves to an array of Record objects.
*
* @param string $name Record to question, A, AAAA and PTR queries are automatically normalized.
* @param int $type Use constants of Amp\Dns\Record.
*
* @return Promise
*/
public function query(string $name, int $type) : Promise;
}

View File

@ -0,0 +1,414 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
use WP_Ultimo\Dependencies\Amp\Cache\ArrayCache;
use WP_Ultimo\Dependencies\Amp\Cache\Cache;
use WP_Ultimo\Dependencies\Amp\Dns\Internal\Socket;
use WP_Ultimo\Dependencies\Amp\Dns\Internal\TcpSocket;
use WP_Ultimo\Dependencies\Amp\Dns\Internal\UdpSocket;
use WP_Ultimo\Dependencies\Amp\Loop;
use WP_Ultimo\Dependencies\Amp\MultiReasonException;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\Amp\Success;
use WP_Ultimo\Dependencies\LibDNS\Messages\Message;
use WP_Ultimo\Dependencies\LibDNS\Records\Question;
use WP_Ultimo\Dependencies\LibDNS\Records\QuestionFactory;
use function WP_Ultimo\Dependencies\Amp\call;
final class Rfc1035StubResolver implements Resolver
{
const CACHE_PREFIX = "amphp.dns.";
const CONFIG_NOT_LOADED = 0;
const CONFIG_LOADED = 1;
const CONFIG_FAILED = 2;
/** @var ConfigLoader */
private $configLoader;
/** @var QuestionFactory */
private $questionFactory;
/** @var Config|null */
private $config;
/** @var int */
private $configStatus = self::CONFIG_NOT_LOADED;
/** @var Promise|null */
private $pendingConfig;
/** @var Cache */
private $cache;
/** @var Socket[] */
private $sockets = [];
/** @var Promise[] */
private $pendingSockets = [];
/** @var Promise[] */
private $pendingQueries = [];
/** @var string */
private $gcWatcher;
/** @var BlockingFallbackResolver */
private $blockingFallbackResolver;
/** @var int */
private $nextNameserver = 0;
public function __construct(Cache $cache = null, ConfigLoader $configLoader = null)
{
$this->cache = $cache ?? new ArrayCache(5000, 256);
$this->configLoader = $configLoader ?? (\stripos(\PHP_OS, "win") === 0 ? new WindowsConfigLoader() : new UnixConfigLoader());
$this->questionFactory = new QuestionFactory();
$this->blockingFallbackResolver = new BlockingFallbackResolver();
$sockets =& $this->sockets;
$this->gcWatcher = Loop::repeat(5000, static function () use(&$sockets) {
if (!$sockets) {
return;
}
$now = \time();
foreach ($sockets as $key => $server) {
if ($server->getLastActivity() < $now - 60) {
$server->close();
unset($sockets[$key]);
}
}
});
Loop::unreference($this->gcWatcher);
}
public function __destruct()
{
Loop::cancel($this->gcWatcher);
}
/** @inheritdoc */
public function resolve(string $name, int $typeRestriction = null) : Promise
{
if ($typeRestriction !== null && $typeRestriction !== Record::A && $typeRestriction !== Record::AAAA) {
throw new \Error("Invalid value for parameter 2: null|Record::A|Record::AAAA expected");
}
return call(function () use($name, $typeRestriction) {
if ($this->configStatus === self::CONFIG_NOT_LOADED) {
(yield $this->reloadConfig());
}
if ($this->configStatus === self::CONFIG_FAILED) {
return $this->blockingFallbackResolver->resolve($name, $typeRestriction);
}
switch ($typeRestriction) {
case Record::A:
if (\filter_var($name, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
return [new Record($name, Record::A, null)];
}
if (\filter_var($name, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
throw new DnsException("Got an IPv6 address, but type is restricted to IPv4");
}
break;
case Record::AAAA:
if (\filter_var($name, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return [new Record($name, Record::AAAA, null)];
}
if (\filter_var($name, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
throw new DnsException("Got an IPv4 address, but type is restricted to IPv6");
}
break;
default:
if (\filter_var($name, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
return [new Record($name, Record::A, null)];
}
if (\filter_var($name, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return [new Record($name, Record::AAAA, null)];
}
break;
}
$dots = \substr_count($name, ".");
// Should be replaced with $name[-1] from 7.1
$trailingDot = \substr($name, -1, 1) === ".";
$name = normalizeName($name);
if ($records = $this->queryHosts($name, $typeRestriction)) {
return $records;
}
// Follow RFC 6761 and never send queries for localhost to the caching DNS server
// Usually, these queries are already resolved via queryHosts()
if ($name === 'localhost') {
return $typeRestriction === Record::AAAA ? [new Record('::1', Record::AAAA, null)] : [new Record('127.0.0.1', Record::A, null)];
}
$searchList = [null];
if (!$trailingDot && $dots < $this->config->getNdots()) {
$searchList = \array_merge($this->config->getSearchList(), $searchList);
}
foreach ($searchList as $searchIndex => $search) {
for ($redirects = 0; $redirects < 5; $redirects++) {
$searchName = $name;
if ($search !== null) {
$searchName = $name . "." . $search;
}
try {
if ($typeRestriction) {
return (yield $this->query($searchName, $typeRestriction));
}
try {
list(, $records) = (yield Promise\some([$this->query($searchName, Record::A), $this->query($searchName, Record::AAAA)]));
return \array_merge(...$records);
} catch (MultiReasonException $e) {
$errors = [];
foreach ($e->getReasons() as $reason) {
if ($reason instanceof NoRecordException) {
throw $reason;
}
if ($searchIndex < \count($searchList) - 1 && \in_array($reason->getCode(), [2, 3], \true)) {
continue 2;
}
$errors[] = $reason->getMessage();
}
throw new DnsException("All query attempts failed for {$searchName}: " . \implode(", ", $errors), 0, $e);
}
} catch (NoRecordException $e) {
try {
/** @var Record[] $cnameRecords */
$cnameRecords = (yield $this->query($searchName, Record::CNAME));
$name = $cnameRecords[0]->getValue();
continue;
} catch (NoRecordException $e) {
/** @var Record[] $dnameRecords */
$dnameRecords = (yield $this->query($searchName, Record::DNAME));
$name = $dnameRecords[0]->getValue();
continue;
}
} catch (DnsException $e) {
if ($searchIndex < \count($searchList) - 1 && \in_array($e->getCode(), [2, 3], \true)) {
continue 2;
}
throw $e;
}
}
}
throw new DnsException("Giving up resolution of '{$searchName}', too many redirects");
});
}
/**
* Reloads the configuration in the background.
*
* Once it's finished, the configuration will be used for new requests.
*
* @return Promise
*/
public function reloadConfig() : Promise
{
if ($this->pendingConfig) {
return $this->pendingConfig;
}
$promise = call(function () {
try {
$this->config = (yield $this->configLoader->loadConfig());
$this->configStatus = self::CONFIG_LOADED;
} catch (ConfigException $e) {
$this->configStatus = self::CONFIG_FAILED;
try {
\trigger_error("Could not load the system's DNS configuration, using synchronous, blocking fallback", \E_USER_WARNING);
} catch (\Throwable $triggerException) {
\set_error_handler(null);
\trigger_error("Could not load the system's DNS configuration, using synchronous, blocking fallback", \E_USER_WARNING);
\restore_error_handler();
}
}
});
$this->pendingConfig = $promise;
$promise->onResolve(function () {
$this->pendingConfig = null;
});
return $promise;
}
/** @inheritdoc */
public function query(string $name, int $type) : Promise
{
$pendingQueryKey = $type . " " . $name;
if (isset($this->pendingQueries[$pendingQueryKey])) {
return $this->pendingQueries[$pendingQueryKey];
}
$promise = call(function () use($name, $type) {
if ($this->configStatus === self::CONFIG_NOT_LOADED) {
(yield $this->reloadConfig());
}
if ($this->configStatus === self::CONFIG_FAILED) {
return $this->blockingFallbackResolver->query($name, $type);
}
$name = $this->normalizeName($name, $type);
$question = $this->createQuestion($name, $type);
if (null !== ($cachedValue = (yield $this->cache->get($this->getCacheKey($name, $type))))) {
return $this->decodeCachedResult($name, $type, $cachedValue);
}
$nameservers = $this->selectNameservers();
$nameserversCount = \count($nameservers);
$attempts = $this->config->getAttempts();
$protocol = "udp";
$attempt = 0;
/** @var Socket $socket */
$uri = $protocol . "://" . $nameservers[0];
$socket = (yield $this->getSocket($uri));
$attemptDescription = [];
while ($attempt < $attempts) {
try {
if (!$socket->isAlive()) {
unset($this->sockets[$uri]);
$socket->close();
$uri = $protocol . "://" . $nameservers[$attempt % $nameserversCount];
$socket = (yield $this->getSocket($uri));
}
$attemptDescription[] = $uri;
/** @var Message $response */
$response = (yield $socket->ask($question, $this->config->getTimeout()));
$this->assertAcceptableResponse($response, $name);
// UDP sockets are never reused, they're not in the $this->sockets map
if ($protocol === "udp") {
// Defer call, because it interferes with the unreference() call in Internal\Socket otherwise
Loop::defer(static function () use($socket) {
$socket->close();
});
}
if ($response->isTruncated()) {
if ($protocol !== "tcp") {
// Retry with TCP, don't count attempt
$protocol = "tcp";
$uri = $protocol . "://" . $nameservers[$attempt % $nameserversCount];
$socket = (yield $this->getSocket($uri));
continue;
}
throw new DnsException("Server returned a truncated response for '{$name}' (" . Record::getName($type) . ")");
}
$answers = $response->getAnswerRecords();
$result = [];
$ttls = [];
/** @var \LibDNS\Records\Resource $record */
foreach ($answers as $record) {
$recordType = $record->getType();
$result[$recordType][] = (string) $record->getData();
// Cache for max one day
$ttls[$recordType] = \min($ttls[$recordType] ?? 86400, $record->getTTL());
}
foreach ($result as $recordType => $records) {
// We don't care here whether storing in the cache fails
$this->cache->set($this->getCacheKey($name, $recordType), \json_encode($records), $ttls[$recordType]);
}
if (!isset($result[$type])) {
// "it MUST NOT cache it for longer than five (5) minutes" per RFC 2308 section 7.1
$this->cache->set($this->getCacheKey($name, $type), \json_encode([]), 300);
throw new NoRecordException("No records returned for '{$name}' (" . Record::getName($type) . ")");
}
return \array_map(static function ($data) use($type, $ttls) {
return new Record($data, $type, $ttls[$type]);
}, $result[$type]);
} catch (TimeoutException $e) {
// Defer call, because it might interfere with the unreference() call in Internal\Socket otherwise
Loop::defer(function () use($socket, $uri) {
unset($this->sockets[$uri]);
$socket->close();
});
$uri = $protocol . "://" . $nameservers[++$attempt % $nameserversCount];
$socket = (yield $this->getSocket($uri));
continue;
}
}
throw new TimeoutException(\sprintf("No response for '%s' (%s) from any nameserver within %d ms after %d attempts, tried %s", $name, Record::getName($type), $this->config->getTimeout(), $attempts, \implode(", ", $attemptDescription)));
});
$this->pendingQueries[$type . " " . $name] = $promise;
$promise->onResolve(function () use($name, $type) {
unset($this->pendingQueries[$type . " " . $name]);
});
return $promise;
}
private function queryHosts(string $name, int $typeRestriction = null) : array
{
$hosts = $this->config->getKnownHosts();
$records = [];
$returnIPv4 = $typeRestriction === null || $typeRestriction === Record::A;
$returnIPv6 = $typeRestriction === null || $typeRestriction === Record::AAAA;
if ($returnIPv4 && isset($hosts[Record::A][$name])) {
$records[] = new Record($hosts[Record::A][$name], Record::A, null);
}
if ($returnIPv6 && isset($hosts[Record::AAAA][$name])) {
$records[] = new Record($hosts[Record::AAAA][$name], Record::AAAA, null);
}
return $records;
}
private function normalizeName(string $name, int $type)
{
if ($type === Record::PTR) {
if (($packedIp = @\inet_pton($name)) !== \false) {
if (isset($packedIp[4])) {
// IPv6
$name = \wordwrap(\strrev(\bin2hex($packedIp)), 1, ".", \true) . ".ip6.arpa";
} else {
// IPv4
$name = \inet_ntop(\strrev($packedIp)) . ".in-addr.arpa";
}
}
} elseif (\in_array($type, [Record::A, Record::AAAA], \true)) {
$name = normalizeName($name);
}
return $name;
}
/**
* @param string $name
* @param int $type
*
* @return Question
*/
private function createQuestion(string $name, int $type) : Question
{
if (0 > $type || 0xffff < $type) {
$message = \sprintf('%d does not correspond to a valid record type (must be between 0 and 65535).', $type);
throw new \Error($message);
}
$question = $this->questionFactory->create($type);
$question->setName($name);
return $question;
}
private function getCacheKey(string $name, int $type) : string
{
return self::CACHE_PREFIX . $name . "#" . $type;
}
private function decodeCachedResult(string $name, int $type, string $encoded) : array
{
$decoded = \json_decode($encoded, \true);
if (!$decoded) {
throw new NoRecordException("No records returned for {$name} (cached result)");
}
$result = [];
foreach ($decoded as $data) {
$result[] = new Record($data, $type);
}
return $result;
}
private function getSocket($uri) : Promise
{
// We use a new socket for each UDP request, as that increases the entropy and mitigates response forgery.
if (\substr($uri, 0, 3) === "udp") {
return UdpSocket::connect($uri);
}
// Over TCP we might reuse sockets if the server allows to keep them open. Sequence IDs in TCP are already
// better than a random port. Additionally, a TCP connection is more expensive.
if (isset($this->sockets[$uri])) {
return new Success($this->sockets[$uri]);
}
if (isset($this->pendingSockets[$uri])) {
return $this->pendingSockets[$uri];
}
$server = TcpSocket::connect($uri);
$server->onResolve(function ($error, $server) use($uri) {
unset($this->pendingSockets[$uri]);
if (!$error) {
$this->sockets[$uri] = $server;
}
});
return $server;
}
/**
* @throws DnsException
*/
private function assertAcceptableResponse(Message $response, string $name)
{
if ($response->getResponseCode() !== 0) {
// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
$errors = [1 => 'FormErr', 2 => 'ServFail', 3 => 'NXDomain', 4 => 'NotImp', 5 => 'Refused', 6 => 'YXDomain', 7 => 'YXRRSet', 8 => 'NXRRSet', 9 => 'NotAuth', 10 => 'NotZone', 11 => 'DSOTYPENI', 16 => 'BADVERS', 17 => 'BADKEY', 18 => 'BADTIME', 19 => 'BADMODE', 20 => 'BADNAME', 21 => 'BADALG', 22 => 'BADTRUNC', 23 => 'BADCOOKIE'];
throw new DnsException(\sprintf("Name resolution failed for '%s'; server returned error code: %d (%s)", $name, $response->getResponseCode(), $errors[$response->getResponseCode()] ?? 'UNKNOWN'), $response->getResponseCode());
}
}
private function selectNameservers() : array
{
$nameservers = $this->config->getNameservers();
if ($this->config->isRotationEnabled() && ($nameserversCount = \count($nameservers)) > 1) {
$nameservers = \array_merge(\array_slice($nameservers, $this->nextNameserver), \array_slice($nameservers, 0, $this->nextNameserver));
$this->nextNameserver = ++$this->nextNameserver % $nameserversCount;
}
return $nameservers;
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
class TimeoutException extends DnsException
{
}

View File

@ -0,0 +1,158 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
use WP_Ultimo\Dependencies\Amp\Failure;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\Amp\Success;
use function WP_Ultimo\Dependencies\Amp\call;
class UnixConfigLoader implements ConfigLoader
{
const MAX_NAMESERVERS = 3;
const MAX_DNS_SEARCH = 6;
const MAX_TIMEOUT = 30 * 1000;
const MAX_ATTEMPTS = 5;
const MAX_NDOTS = 15;
const DEFAULT_TIMEOUT = 5 * 1000;
const DEFAULT_ATTEMPTS = 2;
const DEFAULT_NDOTS = 1;
const DEFAULT_OPTIONS = ["timeout" => self::DEFAULT_TIMEOUT, "attempts" => self::DEFAULT_ATTEMPTS, "ndots" => self::DEFAULT_NDOTS, "rotate" => \false];
private $path;
private $hostLoader;
public function __construct(string $path = "/etc/resolv.conf", HostLoader $hostLoader = null)
{
$this->path = $path;
$this->hostLoader = $hostLoader ?? new HostLoader();
}
protected function readFile(string $path) : Promise
{
\set_error_handler(function (int $errno, string $message) use($path) {
throw new ConfigException("Could not read configuration file '{$path}' ({$errno}) {$message}");
});
try {
// Blocking file access, but this file should be local and usually loaded only once.
$fileContent = \file_get_contents($path);
} catch (ConfigException $exception) {
return new Failure($exception);
} finally {
\restore_error_handler();
}
return new Success($fileContent);
}
public final function loadConfig() : Promise
{
return call(function () {
$nameservers = [];
$searchList = [];
$options = self::DEFAULT_OPTIONS;
$haveLocaldomainEnv = \false;
/* Allow user to override the local domain definition. */
if ($localdomain = \getenv("LOCALDOMAIN")) {
/* Set search list to be blank-separated strings from rest of
env value. Permits users of LOCALDOMAIN to still have a
search list, and anyone to set the one that they want to use
as an individual (even more important now that the rfc1535
stuff restricts searches). */
$searchList = $this->splitOnWhitespace($localdomain);
$haveLocaldomainEnv = \true;
}
$fileContent = (yield $this->readFile($this->path));
$lines = \explode("\n", $fileContent);
foreach ($lines as $line) {
$line = \preg_split('#\\s+#', $line, 2);
if (\count($line) !== 2) {
continue;
}
list($type, $value) = $line;
if ($type === "nameserver") {
if (\count($nameservers) === self::MAX_NAMESERVERS) {
continue;
}
$value = \trim($value);
$ip = @\inet_pton($value);
if ($ip === \false) {
continue;
}
if (isset($ip[15])) {
// IPv6
$nameservers[] = "[" . $value . "]:53";
} else {
// IPv4
$nameservers[] = $value . ":53";
}
} elseif ($type === "domain" && !$haveLocaldomainEnv) {
// LOCALDOMAIN env overrides config
$searchList = $this->splitOnWhitespace($value);
} elseif ($type === "search" && !$haveLocaldomainEnv) {
// LOCALDOMAIN env overrides config
$searchList = $this->splitOnWhitespace($value);
} elseif ($type === "options") {
$option = $this->parseOption($value);
if (\count($option) === 2) {
$options[$option[0]] = $option[1];
}
}
}
$hosts = (yield $this->hostLoader->loadHosts());
if (\count($searchList) === 0) {
$hostname = \gethostname();
$dot = \strpos(".", $hostname);
if ($dot !== \false && $dot < \strlen($hostname)) {
$searchList = [\substr($hostname, $dot)];
}
}
if (\count($searchList) > self::MAX_DNS_SEARCH) {
$searchList = \array_slice($searchList, 0, self::MAX_DNS_SEARCH);
}
$resOptions = \getenv("RES_OPTIONS");
if ($resOptions) {
foreach ($this->splitOnWhitespace($resOptions) as $option) {
$option = $this->parseOption($option);
if (\count($option) === 2) {
$options[$option[0]] = $option[1];
}
}
}
$config = new Config($nameservers, $hosts, $options["timeout"], $options["attempts"]);
return $config->withSearchList($searchList)->withNdots($options["ndots"])->withRotationEnabled($options["rotate"]);
});
}
private function splitOnWhitespace(string $names) : array
{
return \preg_split("#\\s+#", \trim($names));
}
private function parseOption(string $option) : array
{
$optline = \explode(':', $option, 2);
list($name, $value) = $optline + [1 => null];
switch ($name) {
case "timeout":
$value = (int) $value;
if ($value < 0) {
return [];
// don't overwrite option value
}
// The value for this option is silently capped to 30s
return ["timeout", (int) \min($value * 1000, self::MAX_TIMEOUT)];
case "attempts":
$value = (int) $value;
if ($value < 0) {
return [];
// don't overwrite option value
}
// The value for this option is silently capped to 5
return ["attempts", (int) \min($value, self::MAX_ATTEMPTS)];
case "ndots":
$value = (int) $value;
if ($value < 0) {
return [];
// don't overwrite option value
}
// The value for this option is silently capped to 15
return ["ndots", (int) \min($value, self::MAX_NDOTS)];
case "rotate":
return ["rotate", \true];
}
return [];
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\Amp\WindowsRegistry\KeyNotFoundException;
use WP_Ultimo\Dependencies\Amp\WindowsRegistry\WindowsRegistry;
use function WP_Ultimo\Dependencies\Amp\call;
final class WindowsConfigLoader implements ConfigLoader
{
private $hostLoader;
public function __construct(HostLoader $hostLoader = null)
{
$this->hostLoader = $hostLoader ?? new HostLoader();
}
public function loadConfig() : Promise
{
return call(function () {
$keys = ["WP_Ultimo\\Dependencies\\HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\NameServer", "WP_Ultimo\\Dependencies\\HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\DhcpNameServer"];
$reader = new WindowsRegistry();
$nameserver = "";
while ($nameserver === "" && ($key = \array_shift($keys))) {
try {
$nameserver = (yield $reader->read($key));
} catch (KeyNotFoundException $e) {
// retry other possible locations
}
}
if ($nameserver === "") {
$interfaces = "WP_Ultimo\\Dependencies\\HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces";
$subKeys = (yield $reader->listKeys($interfaces));
foreach ($subKeys as $key) {
foreach (["NameServer", "DhcpNameServer"] as $property) {
try {
$nameserver = (yield $reader->read("{$key}\\{$property}"));
if ($nameserver !== "") {
break 2;
}
} catch (KeyNotFoundException $e) {
// retry other possible locations
}
}
}
}
if ($nameserver === "") {
throw new ConfigException("Could not find a nameserver in the Windows Registry");
}
$nameservers = [];
// Microsoft documents space as delimiter, AppVeyor uses comma, we just accept both
foreach (\explode(" ", \strtr($nameserver, ",", " ")) as $nameserver) {
$nameserver = \trim($nameserver);
$ip = @\inet_pton($nameserver);
if ($ip === \false) {
continue;
}
if (isset($ip[15])) {
// IPv6
$nameservers[] = "[" . $nameserver . "]:53";
} else {
// IPv4
$nameservers[] = $nameserver . ":53";
}
}
$hosts = (yield $this->hostLoader->loadHosts());
return new Config($nameservers, $hosts);
});
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
use WP_Ultimo\Dependencies\Amp\Loop;
use WP_Ultimo\Dependencies\Amp\Promise;
const LOOP_STATE_IDENTIFIER = Resolver::class;
/**
* Retrieve the application-wide dns resolver instance.
*
* @param \Amp\Dns\Resolver $resolver Optionally specify a new default dns resolver instance
*
* @return \Amp\Dns\Resolver Returns the application-wide dns resolver instance
*/
function resolver(Resolver $resolver = null) : Resolver
{
if ($resolver === null) {
$resolver = Loop::getState(LOOP_STATE_IDENTIFIER);
if ($resolver) {
return $resolver;
}
$resolver = createDefaultResolver();
}
Loop::setState(LOOP_STATE_IDENTIFIER, $resolver);
return $resolver;
}
/**
* Create a new dns resolver best-suited for the current environment.
*
* @return \Amp\Dns\Resolver
*/
function createDefaultResolver() : Resolver
{
return new Rfc1035StubResolver();
}
/**
* @see Resolver::resolve()
*/
function resolve(string $name, int $typeRestriction = null) : Promise
{
return resolver()->resolve($name, $typeRestriction);
}
/**
* @see Resolver::query()
*/
function query(string $name, int $type) : Promise
{
return resolver()->query($name, $type);
}
/**
* Checks whether a string is a valid DNS name.
*
* @param string $name String to check.
*
* @return bool
*/
function isValidName(string $name)
{
try {
normalizeName($name);
return \true;
} catch (InvalidNameException $e) {
return \false;
}
}
/**
* Normalizes a DNS name and automatically checks it for validity.
*
* @param string $name DNS name.
*
* @return string Normalized DNS name.
* @throws InvalidNameException If an invalid name or an IDN name without ext/intl being installed has been passed.
*/
function normalizeName(string $name) : string
{
static $pattern = '/^(?<name>[a-z0-9]([a-z0-9-_]{0,61}[a-z0-9])?)(\\.(?&name))*\\.?$/i';
if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) {
if (\false === ($result = \idn_to_ascii($name, 0, \INTL_IDNA_VARIANT_UTS46))) {
throw new InvalidNameException("Name '{$name}' could not be processed for IDN.");
}
$name = $result;
} elseif (\preg_match('/[\\x80-\\xff]/', $name)) {
throw new InvalidNameException("Name '{$name}' contains non-ASCII characters and IDN support is not available. " . "Verify that ext/intl is installed for IDN support and that ICU is at least version 4.6.");
}
if (isset($name[253]) || !\preg_match($pattern, $name)) {
throw new InvalidNameException("Name '{$name}' is not a valid hostname.");
}
if ($name[\strlen($name) - 1] === '.') {
$name = \substr($name, 0, -1);
}
return $name;
}