Initial Commit
This commit is contained in:
38
dependencies/amphp/dns/appveyor.yml
vendored
Normal file
38
dependencies/amphp/dns/appveyor.yml
vendored
Normal 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
|
35
dependencies/amphp/dns/lib/BlockingFallbackResolver.php
vendored
Normal file
35
dependencies/amphp/dns/lib/BlockingFallbackResolver.php
vendored
Normal 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
137
dependencies/amphp/dns/lib/Config.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
15
dependencies/amphp/dns/lib/ConfigException.php
vendored
Normal file
15
dependencies/amphp/dns/lib/ConfigException.php
vendored
Normal 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);
|
||||
}
|
||||
}
|
9
dependencies/amphp/dns/lib/ConfigLoader.php
vendored
Normal file
9
dependencies/amphp/dns/lib/ConfigLoader.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Dns;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
interface ConfigLoader
|
||||
{
|
||||
public function loadConfig() : Promise;
|
||||
}
|
7
dependencies/amphp/dns/lib/DnsException.php
vendored
Normal file
7
dependencies/amphp/dns/lib/DnsException.php
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Dns;
|
||||
|
||||
class DnsException extends \Exception
|
||||
{
|
||||
}
|
70
dependencies/amphp/dns/lib/HostLoader.php
vendored
Normal file
70
dependencies/amphp/dns/lib/HostLoader.php
vendored
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
217
dependencies/amphp/dns/lib/Internal/Socket.php
vendored
Normal file
217
dependencies/amphp/dns/lib/Internal/Socket.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
97
dependencies/amphp/dns/lib/Internal/TcpSocket.php
vendored
Normal file
97
dependencies/amphp/dns/lib/Internal/TcpSocket.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
51
dependencies/amphp/dns/lib/Internal/UdpSocket.php
vendored
Normal file
51
dependencies/amphp/dns/lib/Internal/UdpSocket.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
7
dependencies/amphp/dns/lib/InvalidNameException.php
vendored
Normal file
7
dependencies/amphp/dns/lib/InvalidNameException.php
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Dns;
|
||||
|
||||
class InvalidNameException extends DnsException
|
||||
{
|
||||
}
|
7
dependencies/amphp/dns/lib/NoRecordException.php
vendored
Normal file
7
dependencies/amphp/dns/lib/NoRecordException.php
vendored
Normal 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
98
dependencies/amphp/dns/lib/Record.php
vendored
Normal 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
32
dependencies/amphp/dns/lib/Resolver.php
vendored
Normal 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;
|
||||
}
|
414
dependencies/amphp/dns/lib/Rfc1035StubResolver.php
vendored
Normal file
414
dependencies/amphp/dns/lib/Rfc1035StubResolver.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
7
dependencies/amphp/dns/lib/TimeoutException.php
vendored
Normal file
7
dependencies/amphp/dns/lib/TimeoutException.php
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Dns;
|
||||
|
||||
class TimeoutException extends DnsException
|
||||
{
|
||||
}
|
158
dependencies/amphp/dns/lib/UnixConfigLoader.php
vendored
Normal file
158
dependencies/amphp/dns/lib/UnixConfigLoader.php
vendored
Normal 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 [];
|
||||
}
|
||||
}
|
68
dependencies/amphp/dns/lib/WindowsConfigLoader.php
vendored
Normal file
68
dependencies/amphp/dns/lib/WindowsConfigLoader.php
vendored
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
92
dependencies/amphp/dns/lib/functions.php
vendored
Normal file
92
dependencies/amphp/dns/lib/functions.php
vendored
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user