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

View File

@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@ -0,0 +1,37 @@
<?php
declare (strict_types=1);
namespace WP_Ultimo\Dependencies;
require_once './vendor/autoload.php';
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecord;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Factories\SpatieDNS;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers\Dig;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Subscribers\STDIOSubscriber;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Cached;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Chain;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\CloudFlare;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\GoogleDNS;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\LocalSystem;
use WP_Ultimo\Dependencies\Symfony\Component\Cache\Adapter\FilesystemAdapter;
\class_alias(Hostname::class, 'WP_Ultimo\\Dependencies\\Hostname');
\class_alias(DNSRecord::class, 'WP_Ultimo\\Dependencies\\DNSRecord');
\class_alias(DNSRecordType::class, 'WP_Ultimo\\Dependencies\\DNSRecordType');
\class_alias(DNSRecordCollection::class, 'WP_Ultimo\\Dependencies\\DNSRecordCollection');
$stdOut = new \SplFileObject('php://stdout');
$stdErr = new \SplFileObject('php://stderr');
$IOSubscriber = new STDIOSubscriber($stdOut, $stdErr);
$localSystemResolver = new LocalSystem();
$localSystemResolver->addSubscriber($IOSubscriber);
$googleDNSResolver = new GoogleDNS();
$googleDNSResolver->addSubscriber($IOSubscriber);
$cloudFlareResolver = new CloudFlare();
$cloudFlareResolver->addSubscriber($IOSubscriber);
$digResolver = new \WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Dig(new SpatieDNS(), new Dig());
$digResolver->addSubscriber($IOSubscriber);
$chainResolver = new Chain($cloudFlareResolver, $googleDNSResolver, $localSystemResolver);
$cachedResolver = new Cached(new FilesystemAdapter(), $chainResolver);
$cachedResolver->addSubscriber($IOSubscriber);

View File

@ -0,0 +1,56 @@
# The maximum number of files to display in the results table.
# Default: 10
filesToShow: 20
# The minimum score a file need to display in the results table.
# Disabled if null.
# Default: 0.1
minScoreToShow: 0
# The command returns an 1 exit code if the highest score is greater than the threshold.
# Disabled if null.
# Default: null
maxScoreThreshold: 0.9
# The number of parallel jobs to use when processing files.
# Default: 10
parallelJobs: 10
# How far back in the VCS history to count the number of commits to a file
# Can be a human readable date like 'One week ago' or a date like '2017-07-12'
# Default: '10 Years ago'
commitsSince: One year ago
# Files to ignore when processing. The full path to the file relative to the root of your project is required.
# Also supports regular expressions.
# Default: All PHP files in the path provided to churn-php are processed.
#filesToIgnore:
# File extensions to use when processing.
# Default: php
fileExtensions:
- php
- inc
# This list is used only if there is no argument when running churn.
# Default: <empty>
directoriesToScan:
- src
- tests/
# List of user-defined hooks.
# They can be referenced by their full qualified class name if churn has access to the autoloader.
# Otherwise the file path can be used as well.
# See below the section about hooks for more details.
# Default: <empty>
#hooks:
# The version control system used for your project.
# Accepted values: fossil, git, mercurial, subversion, none
# Default: git
vcs: git
# The path of the cache file. It doesn't need to exist before running churn.
# Disabled if null.
# Default: null
cachePath: .churn.cache

View File

@ -0,0 +1,4 @@
parameters:
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
ignoreErrors:

View File

@ -0,0 +1,61 @@
<?xml version="1.0"?>
<psalm
totallyTyped="false"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->
<DeprecatedMethod errorLevel="info" />
<DeprecatedProperty errorLevel="info" />
<DeprecatedClass errorLevel="info" />
<DeprecatedConstant errorLevel="info" />
<DeprecatedFunction errorLevel="info" />
<DeprecatedInterface errorLevel="info" />
<DeprecatedTrait errorLevel="info" />
<InternalMethod errorLevel="info" />
<InternalProperty errorLevel="info" />
<InternalClass errorLevel="info" />
<MissingClosureReturnType errorLevel="info" />
<MissingReturnType errorLevel="info" />
<MissingPropertyType errorLevel="info" />
<InvalidDocblock errorLevel="info" />
<PropertyNotSetInConstructor errorLevel="info" />
<MissingConstructor errorLevel="info" />
<MissingClosureParamType errorLevel="info" />
<MissingParamType errorLevel="info" />
<RedundantPropertyInitializationCheck errorLevel="suppress"/>
<RedundantCondition errorLevel="info" />
<DocblockTypeContradiction errorLevel="info" />
<RedundantConditionGivenDocblockType errorLevel="info" />
<UnresolvableInclude errorLevel="info" />
<RawObjectIteration errorLevel="info" />
<InvalidStringClass errorLevel="info" />
<TypeDoesNotContainType errorLevel="info" />
<TooManyArguments>
<errorLevel type="suppress">
<referencedFunction name="WP_Ultimo\Dependencies\Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch" />
</errorLevel>
</TooManyArguments>
</issueHandlers>
</psalm>

View File

@ -0,0 +1,20 @@
<?php
declare (strict_types=1);
namespace WP_Ultimo\Dependencies;
use Rector\Core\Configuration\Option;
use Rector\Php74\Rector\Property\TypedPropertyRector;
use Rector\Set\ValueObject\LevelSetList;
use WP_Ultimo\Dependencies\Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator) : void {
// get parameters
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PATHS, [__DIR__ . '/src']);
// Define what rule sets will be applied
$containerConfigurator->import(LevelSetList::UP_TO_PHP_81);
// get services (needed for register a single rule)
$services = $containerConfigurator->services();
// register a single rule
$services->set(TypedPropertyRector::class);
};

View File

@ -0,0 +1,64 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions;
use function preg_match;
use function serialize;
use function str_ireplace;
use function trim;
use function unserialize;
final class CAAData extends DataAbstract
{
private int $flags;
private string $tag;
private ?string $value;
public function __construct(int $flags, string $tag, string $value = null)
{
$this->flags = $flags;
$this->tag = $tag;
$this->value = $value ? $this->normalizeValue($value) : null;
}
public function __toString() : string
{
return "{$this->flags} {$this->tag} \"{$this->value}\"";
}
public function getFlags() : int
{
return $this->flags;
}
public function getTag() : string
{
return $this->tag;
}
public function getValue() : ?string
{
return $this->value;
}
public function toArray() : array
{
return ['flags' => $this->flags, 'tag' => $this->tag, 'value' => $this->value];
}
public function serialize() : string
{
return serialize($this->toArray());
}
/**
* @param string $serialized
*/
public function unserialize($serialized) : void
{
$unserialized = unserialize($serialized);
$this->flags = $unserialized['flags'];
$this->tag = $unserialized['tag'];
$this->value = $unserialized['value'];
}
private function normalizeValue(string $value) : string
{
$normalized = trim(str_ireplace('"', '', $value));
if (preg_match('/\\s/m', $normalized)) {
throw new Exceptions\InvalidArgumentException("{$value} is not a valid CAA value");
}
return $normalized;
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use function serialize;
use function unserialize;
final class CNAMEData extends DataAbstract
{
private Hostname $hostname;
public function __construct(Hostname $hostname)
{
$this->hostname = $hostname;
}
public function __toString() : string
{
return (string) $this->hostname;
}
public function getHostname() : Hostname
{
return $this->hostname;
}
public function toArray() : array
{
return ['hostname' => (string) $this->hostname];
}
public function serialize() : string
{
return serialize($this->toArray());
}
/**
* @param string $serialized
*/
public function unserialize($serialized) : void
{
/** @var array{'hostname': string} $unserialized */
$unserialized = unserialize($serialized);
$this->hostname = new Hostname($unserialized['hostname']);
}
}

View File

@ -0,0 +1,103 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
use function serialize;
use function unserialize;
final class DNSRecord extends EntityAbstract implements DNSRecordInterface
{
private DNSRecordType $recordType;
private Hostname $hostname;
private int $TTL;
private ?IPAddress $IPAddress;
private string $class;
private ?DataAbstract $data;
/**
* @var string
*/
private const DATA = 'data';
public function __construct(DNSRecordType $recordType, Hostname $hostname, int $ttl, IPAddress $IPAddress = null, string $class = 'IN', DataAbstract $data = null)
{
$this->recordType = $recordType;
$this->hostname = $hostname;
$this->TTL = $ttl;
$this->IPAddress = $IPAddress;
$this->class = $class;
$this->data = $data;
}
public static function createFromPrimitives(string $recordType, string $hostname, int $ttl, string $IPAddress = null, string $class = 'IN', string $data = null) : DNSRecord
{
$type = DNSRecordType::createFromString($recordType);
$hostname = Hostname::createFromString($hostname);
$data = $data !== null ? DataAbstract::createFromTypeAndString($type, $data) : null;
return new self($type, $hostname, $ttl, $IPAddress ? IPAddress::createFromString($IPAddress) : null, $class, $data);
}
public function getType() : DNSRecordType
{
return $this->recordType;
}
public function getHostname() : Hostname
{
return $this->hostname;
}
public function getTTL() : int
{
return $this->TTL;
}
public function getIPAddress() : ?IPAddress
{
return $this->IPAddress;
}
public function getClass() : string
{
return $this->class;
}
public function getData() : ?DataAbstract
{
return $this->data;
}
public function setTTL(int $ttl) : DNSRecordInterface
{
$this->TTL = $ttl;
return $this;
}
public function toArray() : array
{
$formatted = ['hostname' => (string) $this->hostname, 'type' => (string) $this->recordType, 'TTL' => $this->TTL, 'class' => $this->class];
if ($this->IPAddress) {
$formatted['IPAddress'] = (string) $this->IPAddress;
}
if ($this->data) {
$formatted[self::DATA] = (string) $this->data;
}
return $formatted;
}
public function equals(DNSRecordInterface $record) : bool
{
return $this->hostname->equals($record->getHostname()) && $this->recordType->equals($record->getType()) && (string) $this->data === (string) $record->getData() && (string) $this->IPAddress === (string) $record->getIPAddress();
// could be null
}
public function serialize() : string
{
return serialize($this->toArray());
}
/**
* @param string $serialized
*/
public function unserialize($serialized) : void
{
$unserialized = unserialize($serialized);
$rawIPAddres = $unserialized['IPAddress'] ?? null;
$this->recordType = DNSRecordType::createFromString($unserialized['type']);
$this->hostname = Hostname::createFromString($unserialized['hostname']);
$this->TTL = (int) $unserialized['TTL'];
$this->IPAddress = $rawIPAddres ? IPAddress::createFromString($rawIPAddres) : null;
$this->class = $unserialized['class'];
$this->data = isset($unserialized[self::DATA]) ? DataAbstract::createFromTypeAndString($this->recordType, $unserialized[self::DATA]) : null;
}
public function jsonSerialize() : array
{
return $this->toArray();
}
}

View File

@ -0,0 +1,151 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use ArrayAccess;
use ArrayIterator;
use Countable;
use Iterator;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\Arrayable;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\Serializable;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException;
use function array_filter;
use function array_shift;
use function serialize;
use function unserialize;
final class DNSRecordCollection extends EntityAbstract implements ArrayAccess, Iterator, Countable, Arrayable, Serializable
{
private ArrayIterator $records;
public function __construct(DNSRecordInterface ...$records)
{
$this->records = new ArrayIterator($records);
}
public function toArray() : array
{
return $this->records->getArrayCopy();
}
public function pickFirst() : ?DNSRecordInterface
{
$copy = $this->records->getArrayCopy();
return array_shift($copy);
}
public function filteredByType(DNSRecordType $type) : self
{
$fn = fn(DNSRecordInterface $record) => $record->getType()->equals($type);
return new self(...array_filter($this->records->getArrayCopy(), $fn));
}
public function has(DNSRecordInterface $lookupRecord) : bool
{
foreach ($this->records->getArrayCopy() as $record) {
if ($lookupRecord->equals($record)) {
return \true;
}
}
return \false;
}
public function current() : ?DNSRecordInterface
{
return $this->records->current();
}
public function next() : void
{
$this->records->next();
}
/**
* @return int|string|bool
*/
public function key()
{
return $this->records->key();
}
public function valid() : bool
{
return $this->records->valid();
}
public function rewind() : void
{
$this->records->rewind();
}
/**
* @param mixed $offset
* @return bool
*/
public function offsetExists($offset) : bool
{
return $this->records->offsetExists($offset);
}
/**
* @param mixed $offset
* @return \RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface
*/
public function offsetGet($offset) : DNSRecordInterface
{
return $this->records->offsetGet($offset);
}
/**
* @param mixed $offset
* @param mixed $value
* @throws \RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException
*/
public function offsetSet($offset, $value) : void
{
if (!$value instanceof DNSRecordInterface) {
throw new InvalidArgumentException('Invalid value');
}
$this->records->offsetSet(
$offset,
/** @scrutinizer ignore-type */
$value
);
}
/**
* @param mixed $offset
*/
public function offsetUnset($offset) : void
{
$this->records->offsetUnset($offset);
}
public function count() : int
{
return $this->records->count();
}
public function isEmpty() : bool
{
return $this->count() === 0;
}
public function serialize() : string
{
return serialize($this->records->getArrayCopy());
}
/**
* @param string $serialized
*/
public function unserialize($serialized) : void
{
$this->records = new ArrayIterator(unserialize($serialized));
}
public function jsonSerialize() : array
{
return $this->toArray();
}
public function withUniqueValuesExcluded() : self
{
return $this->filterValues(fn(DNSRecordInterface $candidateRecord, DNSRecordCollection $remaining): bool => $remaining->has($candidateRecord))->withUniqueValues();
}
public function withUniqueValues() : self
{
return $this->filterValues(fn(DNSRecordInterface $candidateRecord, DNSRecordCollection $remaining): bool => !$remaining->has($candidateRecord));
}
private function filterValues(callable $eval) : self
{
$filtered = new self();
$records = $this->records->getArrayCopy();
while ($record = array_shift($records)) {
if ($eval($record, new self(...$records))) {
$filtered[] = $record;
}
}
return $filtered;
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException;
use function array_flip;
use function in_array;
use function strtoupper;
final class DNSRecordType extends EntityAbstract
{
public const TYPE_A = 'A';
public const TYPE_CNAME = 'CNAME';
public const TYPE_HINFO = 'HINFO';
public const TYPE_CAA = 'CAA';
public const TYPE_MX = 'MX';
public const TYPE_NS = 'NS';
public const TYPE_PTR = 'PTR';
public const TYPE_SOA = 'SOA';
public const TYPE_TXT = 'TXT';
public const TYPE_AAAA = 'AAAA';
public const TYPE_SRV = 'SRV';
public const TYPE_NAPTR = 'NAPTR';
public const TYPE_A6 = 'A6';
public const TYPE_ANY = 'ANY';
public const VALID_TYPES = [self::TYPE_A, self::TYPE_CNAME, self::TYPE_HINFO, self::TYPE_CAA, self::TYPE_MX, self::TYPE_NS, self::TYPE_PTR, self::TYPE_SOA, self::TYPE_TXT, self::TYPE_AAAA, self::TYPE_SRV, self::TYPE_NAPTR, self::TYPE_A6, self::TYPE_ANY];
protected const CODE_TYPE_MAP = [1 => DNSRecordType::TYPE_A, 5 => DNSRecordType::TYPE_CNAME, 13 => DNSRecordType::TYPE_HINFO, 257 => DNSRecordType::TYPE_CAA, 15 => DNSRecordType::TYPE_MX, 2 => DNSRecordType::TYPE_NS, 12 => DNSRecordType::TYPE_PTR, 6 => DNSRecordType::TYPE_SOA, 16 => DNSRecordType::TYPE_TXT, 28 => DNSRecordType::TYPE_AAAA, 33 => DNSRecordType::TYPE_SRV, 35 => DNSRecordType::TYPE_NAPTR, 38 => DNSRecordType::TYPE_A6, 255 => DNSRecordType::TYPE_ANY];
private string $type;
/**
* @throws \RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException
*/
public function __construct(string $type)
{
if (!in_array($type, self::VALID_TYPES, \true)) {
throw new InvalidArgumentException("{$type} is not an existing DNS record type");
}
$this->type = $type;
}
public function __toString() : string
{
return $this->type;
}
/**
* @throws \RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException
*/
public static function createFromInt(int $code) : DNSRecordType
{
if (!isset(self::CODE_TYPE_MAP[$code])) {
throw new InvalidArgumentException("{$code} is not able to be mapped to an existing DNS record type");
}
return new self(self::CODE_TYPE_MAP[$code]);
}
public static function createFromString(string $type) : DNSRecordType
{
return new self($type);
}
public function toInt() : int
{
return array_flip(self::CODE_TYPE_MAP)[$this->type];
}
public function isA(string $type) : bool
{
return $this->type === strtoupper($type);
}
public function equals(DNSRecordType $recordType) : bool
{
return $this->type === (string) $recordType;
}
public static function createA() : self
{
return self::createFromString(self::TYPE_A);
}
public static function createCNAME() : self
{
return self::createFromString(self::TYPE_CNAME);
}
public static function createHINFO() : self
{
return self::createFromString(self::TYPE_HINFO);
}
public static function createCAA() : self
{
return self::createFromString(self::TYPE_CAA);
}
public static function createMX() : self
{
return self::createFromString(self::TYPE_MX);
}
public static function createNS() : self
{
return self::createFromString(self::TYPE_NS);
}
public static function createPTR() : self
{
return self::createFromString(self::TYPE_PTR);
}
public static function createSOA() : self
{
return self::createFromString(self::TYPE_SOA);
}
public static function createTXT() : self
{
return self::createFromString(self::TYPE_TXT);
}
public static function createAAAA() : self
{
return self::createFromString(self::TYPE_AAAA);
}
public static function createSRV() : self
{
return self::createFromString(self::TYPE_SRV);
}
public static function createNAPTR() : self
{
return self::createFromString(self::TYPE_NAPTR);
}
public static function createA6() : self
{
return self::createFromString(self::TYPE_A6);
}
public static function createANY() : self
{
return self::createFromString(self::TYPE_ANY);
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\Arrayable;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\Serializable;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException;
use function count;
use function explode;
use function trim;
abstract class DataAbstract implements Arrayable, Serializable
{
public abstract function __toString() : string;
public abstract function toArray() : array;
public function equals(DataAbstract $dataAbstract) : bool
{
return (string) $this === (string) $dataAbstract;
}
/**
* @throws \RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException
*/
public static function createFromTypeAndString(DNSRecordType $recordType, string $data) : self
{
if ($recordType->isA(DNSRecordType::TYPE_TXT)) {
return new TXTData(trim($data, '"'));
}
if ($recordType->isA(DNSRecordType::TYPE_NS)) {
return new NSData(new Hostname($data));
}
if ($recordType->isA(DNSRecordType::TYPE_CNAME)) {
return new CNAMEData(new Hostname($data));
}
$parsed = self::parseDataToArray($data);
if ($recordType->isA(DNSRecordType::TYPE_MX)) {
return new MXData(new Hostname($parsed[1]), (int) $parsed[0]);
}
if ($recordType->isA(DNSRecordType::TYPE_SOA)) {
return new SOAData(new Hostname($parsed[0]), new Hostname($parsed[1]), (int) ($parsed[2] ?? 0), (int) ($parsed[3] ?? 0), (int) ($parsed[4] ?? 0), (int) ($parsed[5] ?? 0), (int) ($parsed[6] ?? 0));
}
if ($recordType->isA(DNSRecordType::TYPE_CAA) && count($parsed) === 3) {
return new CAAData((int) $parsed[0], (string) $parsed[1], $parsed[2]);
}
if ($recordType->isA(DNSRecordType::TYPE_SRV)) {
return new SRVData((int) $parsed[0] ?: 0, (int) $parsed[1] ?: 0, (int) $parsed[2] ?: 0, new Hostname($parsed[3]));
}
if ($recordType->isA(DNSRecordType::TYPE_PTR)) {
return new PTRData(new Hostname($data));
}
throw new InvalidArgumentException("{$data} could not be created with type {$recordType}");
}
public function jsonSerialize() : array
{
return $this->toArray();
}
private static function parseDataToArray(string $data) : array
{
return explode(' ', $data);
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
abstract class EntityAbstract
{
}

View File

@ -0,0 +1,66 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException;
use function filter_var;
use function idn_to_ascii;
use function idn_to_utf8;
use function mb_strtolower;
use function substr;
use function trim;
final class Hostname extends EntityAbstract
{
private string $hostname;
/**
* @throws \RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException
*/
public function __construct(string $hostname)
{
$hostname = $this->normalizeHostName($hostname);
if (filter_var($hostname, \FILTER_VALIDATE_DOMAIN) !== $hostname) {
throw new InvalidArgumentException("{$hostname} is not a valid hostname");
}
$this->hostname = $hostname;
}
public function __toString() : string
{
return $this->hostname;
}
public function equals(Hostname $hostname) : bool
{
return $this->hostname === (string) $hostname;
}
public static function createFromString(string $hostname) : Hostname
{
return new self($hostname);
}
public function getHostName() : string
{
return $this->hostname;
}
public function getHostnameWithoutTrailingDot() : string
{
return substr($this->hostname, 0, -1);
}
public function isPunycoded() : bool
{
return $this->toUTF8() !== $this->hostname;
}
public function toUTF8() : string
{
return (string) idn_to_utf8($this->hostname, \IDNA_ERROR_PUNYCODE, \INTL_IDNA_VARIANT_UTS46);
}
private static function punyCode(string $hostname) : string
{
return (string) idn_to_ascii($hostname, \IDNA_ERROR_PUNYCODE, \INTL_IDNA_VARIANT_UTS46);
}
private function normalizeHostName(string $hostname) : string
{
$hostname = self::punyCode(mb_strtolower(trim($hostname)));
if (substr($hostname, -1) !== '.') {
return "{$hostname}.";
}
return $hostname;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException;
use function filter_var;
use function trim;
final class IPAddress extends EntityAbstract
{
private string $IPAddress;
/**
* @throws \RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException
*/
public function __construct(string $IPAddress)
{
$IPAddress = trim($IPAddress);
if (self::isValid($IPAddress) === \false) {
throw new InvalidArgumentException("{$IPAddress} is not a valid IP address");
}
$this->IPAddress = $IPAddress;
}
public function __toString() : string
{
return $this->IPAddress;
}
public static function isValid(string $IPAddress) : bool
{
return (bool) filter_var($IPAddress, \FILTER_VALIDATE_IP);
}
public static function createFromString(string $IPAddress) : IPAddress
{
return new self($IPAddress);
}
public function equals(IPAddress $IPAddress) : bool
{
return $this->IPAddress === (string) $IPAddress;
}
public function getIPAddress() : string
{
return $this->IPAddress;
}
public function isIPv6() : bool
{
return (bool) filter_var($this->IPAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6);
}
public function isIPv4() : bool
{
return (bool) filter_var($this->IPAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces;
interface Arrayable
{
public function toArray() : array;
}

View File

@ -0,0 +1,20 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DataAbstract;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\IPAddress;
interface DNSRecordInterface extends Arrayable, Serializable
{
public function getType() : DNSRecordType;
public function getHostname() : Hostname;
public function getTTL() : int;
public function getIPAddress() : ?IPAddress;
public function getClass() : string;
public function getData() : ?DataAbstract;
public function setTTL(int $ttl) : DNSRecordInterface;
public function toArray() : array;
public function equals(DNSRecordInterface $record) : bool;
}

View File

@ -0,0 +1,11 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces;
use JsonSerializable;
interface Serializable extends \Serializable, JsonSerializable
{
public function serialize() : string;
public function unserialize($serialized) : void;
public function jsonSerialize() : array;
}

View File

@ -0,0 +1,45 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use function serialize;
use function unserialize;
final class MXData extends DataAbstract
{
private Hostname $target;
private int $priority;
public function __construct(Hostname $target, int $priority = 0)
{
$this->target = $target;
$this->priority = $priority;
}
public function __toString() : string
{
return "{$this->priority} {$this->target}";
}
public function getTarget() : Hostname
{
return $this->target;
}
public function getPriority() : int
{
return $this->priority;
}
public function toArray() : array
{
return ['target' => (string) $this->target, 'priority' => $this->priority];
}
public function serialize() : string
{
return serialize($this->toArray());
}
/**
* @param string $serialized
*/
public function unserialize($serialized) : void
{
$unserialized = unserialize($serialized);
$this->target = new Hostname($unserialized['target']);
$this->priority = $unserialized['priority'];
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use function serialize;
use function unserialize;
final class NSData extends DataAbstract
{
private Hostname $target;
public function __construct(Hostname $target)
{
$this->target = $target;
}
public function __toString() : string
{
return (string) $this->target;
}
public function getTarget() : Hostname
{
return $this->target;
}
public function toArray() : array
{
return ['target' => (string) $this->target];
}
public function serialize() : string
{
return serialize($this->toArray());
}
/**
* @param string $serialized
*/
public function unserialize($serialized) : void
{
$unserialized = unserialize($serialized);
$this->target = new Hostname($unserialized['target']);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use function serialize;
use function unserialize;
final class PTRData extends DataAbstract
{
private Hostname $hostname;
public function __construct(Hostname $hostname)
{
$this->hostname = $hostname;
}
public function __toString() : string
{
return (string) $this->hostname;
}
public function getHostname() : Hostname
{
return $this->hostname;
}
public function toArray() : array
{
return ['hostname' => (string) $this->hostname];
}
public function serialize() : string
{
return serialize($this->toArray());
}
/**
* @param string $serialized
*/
public function unserialize($serialized) : void
{
$unserialized = unserialize($serialized);
$this->hostname = new Hostname($unserialized['hostname']);
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use function serialize;
use function unserialize;
use function vsprintf;
final class SOAData extends DataAbstract
{
private Hostname $mname;
private Hostname $rname;
private int $serial;
private int $refresh;
private int $retry;
private int $expire;
private int $minTTL;
/**
* @var string
*/
private const TEMPLATE = '%s %s %s %s %s %s %s';
public function __construct(Hostname $mname, Hostname $rname, int $serial, int $refresh, int $retry, int $expire, int $minTTL)
{
$this->mname = $mname;
$this->rname = $rname;
$this->serial = $serial;
$this->refresh = $refresh;
$this->retry = $retry;
$this->expire = $expire;
$this->minTTL = $minTTL;
}
public function __toString() : string
{
return vsprintf(self::TEMPLATE, $this->toArray());
}
/**
* @return \RemotelyLiving\PHPDNS\Entities\Hostname
*/
public function getMname() : Hostname
{
return $this->mname;
}
/**
* @return \RemotelyLiving\PHPDNS\Entities\Hostname
*/
public function getRname() : Hostname
{
return $this->rname;
}
/**
* @return int
*/
public function getSerial() : int
{
return $this->serial;
}
/**
* @return int
*/
public function getRefresh() : int
{
return $this->refresh;
}
/**
* @return int
*/
public function getRetry() : int
{
return $this->retry;
}
/**
* @return int
*/
public function getExpire() : int
{
return $this->expire;
}
/**
* @return int
*/
public function getMinTTL() : int
{
return $this->minTTL;
}
public function toArray() : array
{
return ['mname' => (string) $this->mname, 'rname' => (string) $this->rname, 'serial' => $this->serial, 'refresh' => $this->refresh, 'retry' => $this->retry, 'expire' => $this->expire, 'minimumTTL' => $this->minTTL];
}
public function serialize() : string
{
return serialize($this->toArray());
}
/**
* @param string $serialized
*/
public function unserialize($serialized) : void
{
$unserialized = unserialize($serialized);
$this->mname = new Hostname($unserialized['mname']);
$this->rname = new Hostname($unserialized['rname']);
$this->serial = $unserialized['serial'];
$this->refresh = $unserialized['refresh'];
$this->retry = $unserialized['retry'];
$this->expire = $unserialized['expire'];
$this->minTTL = $unserialized['minimumTTL'];
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use function serialize;
use function unserialize;
final class SRVData extends DataAbstract
{
private int $priority;
private int $weight;
private int $port;
private Hostname $target;
public function __construct(int $priority, int $weight, int $port, Hostname $target)
{
$this->priority = $priority;
$this->weight = $weight;
$this->port = $port;
$this->target = $target;
}
public function __toString() : string
{
return "{$this->priority} {$this->weight} {$this->port} {$this->target}";
}
public function getPriority() : int
{
return $this->priority;
}
public function getWeight() : int
{
return $this->weight;
}
public function getPort() : int
{
return $this->port;
}
public function getTarget() : Hostname
{
return $this->target;
}
public function toArray() : array
{
return ['priority' => $this->priority, 'weight' => $this->weight, 'port' => $this->port, 'target' => (string) $this->target];
}
public function serialize() : string
{
return serialize($this->toArray());
}
/**
* @param string $serialized
*/
public function unserialize($serialized) : void
{
$unserialized = unserialize($serialized);
$this->priority = $unserialized['priority'];
$this->weight = $unserialized['weight'];
$this->port = $unserialized['port'];
$this->target = new Hostname($unserialized['target']);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities;
use function serialize;
use function unserialize;
final class TXTData extends DataAbstract
{
private string $value;
public function __construct(string $value)
{
$this->value = $value;
}
public function __toString() : string
{
return $this->value;
}
public function getValue() : string
{
return $this->value;
}
public function toArray() : array
{
return ['value' => $this->value];
}
public function serialize() : string
{
return serialize($this->toArray());
}
/**
* @param string $serialized
*/
public function unserialize($serialized) : void
{
$unserialized = unserialize($serialized);
$this->value = $unserialized['value'];
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions;
use JsonSerializable;
class Exception extends \Exception implements JsonSerializable
{
public function jsonSerialize() : array
{
return ['message' => $this->getMessage(), 'code' => $this->getCode(), 'file' => $this->getFile(), 'line' => $this->getLine(), 'trace' => $this->getTraceAsString()];
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions;
final class InvalidArgumentException extends Exception
{
}

View File

@ -0,0 +1,13 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Factories;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\Spatie\Dns\Dns;
class SpatieDNS
{
public function createResolver(Hostname $domain, Hostname $nameserver = null) : Dns
{
return new Dns((string) $domain, (string) $nameserver);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecord;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\IPAddress;
use function str_ireplace;
final class CloudFlare extends MapperAbstract
{
/**
* @var string
*/
private const DATA = 'data';
public function toDNSRecord() : DNSRecordInterface
{
$type = DNSRecordType::createFromInt((int) $this->fields['type']);
$IPAddress = isset($this->fields[self::DATA]) && IPAddress::isValid($this->fields[self::DATA]) ? $this->fields[self::DATA] : null;
$value = isset($this->fields[self::DATA]) && !$IPAddress ? str_ireplace('"', '', (string) $this->fields[self::DATA]) : null;
return DNSRecord::createFromPrimitives((string) $type, $this->fields['name'], $this->fields['TTL'], $IPAddress, 'IN', $value);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecord;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
final class Dig extends MapperAbstract
{
public function toDNSRecord() : DNSRecordInterface
{
$type = new DNSRecordType($this->fields[3]);
if ($type->isA(DNSRecordType::TYPE_A) || $type->isA(DNSRecordType::TYPE_AAAA)) {
return DNSRecord::createFromPrimitives($this->fields[3], (string) $this->fields[0], (int) $this->fields[1], $this->fields[4]);
}
return DNSRecord::createFromPrimitives($this->fields[3], (string) $this->fields[0], (int) $this->fields[1], null, 'IN', $this->fields[4]);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecord;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\IPAddress;
use function str_ireplace;
final class GoogleDNS extends MapperAbstract
{
/**
* @var string
*/
private const DATA = 'data';
public function toDNSRecord() : DNSRecordInterface
{
$type = DNSRecordType::createFromInt((int) $this->fields['type']);
$IPAddress = isset($this->fields[self::DATA]) && IPAddress::isValid($this->fields[self::DATA]) ? $this->fields[self::DATA] : null;
$value = isset($this->fields[self::DATA]) && !$IPAddress ? str_ireplace('"', '', (string) $this->fields[self::DATA]) : null;
return DNSRecord::createFromPrimitives((string) $type, $this->fields['name'], $this->fields['TTL'], $IPAddress, 'IN', $value);
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecord;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
use function array_flip;
use function sprintf;
use const DNS_A;
use const DNS_A6;
use const DNS_AAAA;
use const DNS_ANY;
use const DNS_CAA;
use const DNS_CNAME;
use const DNS_HINFO;
use const DNS_MX;
use const DNS_NAPTR;
use const DNS_NS;
use const DNS_PTR;
use const DNS_SOA;
use const DNS_SRV;
use const DNS_TXT;
final class LocalSystem extends MapperAbstract
{
private const PHP_CODE_TYPE_MAP = [DNS_A => DNSRecordType::TYPE_A, DNS_CNAME => DNSRecordType::TYPE_CNAME, DNS_HINFO => DNSRecordType::TYPE_HINFO, DNS_CAA => DNSRecordType::TYPE_CAA, DNS_MX => DNSRecordType::TYPE_MX, DNS_NS => DNSRecordType::TYPE_NS, DNS_PTR => DNSRecordType::TYPE_PTR, DNS_SOA => DNSRecordType::TYPE_SOA, DNS_TXT => DNSRecordType::TYPE_TXT, DNS_AAAA => DNSRecordType::TYPE_AAAA, DNS_SRV => DNSRecordType::TYPE_SRV, DNS_NAPTR => DNSRecordType::TYPE_NAPTR, DNS_A6 => DNSRecordType::TYPE_A6, DNS_ANY => DNSRecordType::TYPE_ANY];
/**
* @var string
*/
private const TARGET = 'target';
/**
* @var string
*/
private const PRI = 'pri';
/**
* @var string
*/
private const TEMPLATE = '%s %s %s %s %s %s %s';
public function toDNSRecord() : DNSRecordInterface
{
$IPAddress = null;
if (isset($this->fields['ipv6'])) {
$IPAddress = $this->fields['ipv6'];
}
if (isset($this->fields['ip'])) {
$IPAddress = $this->fields['ip'];
}
return DNSRecord::createFromPrimitives($this->fields['type'], $this->fields['host'], $this->fields['ttl'], $IPAddress, $this->fields['class'], $this->formatData($this->fields));
}
public function getTypeCodeFromType(DNSRecordType $type) : int
{
return array_flip(self::PHP_CODE_TYPE_MAP)[(string) $type] ?? DNS_ANY;
}
private function formatData(array $fields) : ?string
{
if (isset($this->fields['flags'], $fields['tag'], $fields['value'])) {
return "{$fields['flags']} {$fields['tag']} \"{$fields['value']}\"";
}
if (isset($fields['mname'])) {
return sprintf(self::TEMPLATE, $fields['mname'], $fields['rname'], $fields['serial'], $fields['refresh'], $fields['retry'], $fields['expire'], $fields['minimum-ttl']);
}
if (isset($fields[self::TARGET], $fields[self::PRI], $fields['weight'], $fields['port'])) {
return "{$fields[self::PRI]} {$fields['weight']} {$fields['port']} {$fields[self::TARGET]}";
}
if (isset($fields[self::TARGET], $fields[self::PRI])) {
return "{$fields[self::PRI]} {$fields[self::TARGET]}";
}
if (isset($fields[self::TARGET])) {
return $fields[self::TARGET];
}
if (isset($fields['txt'])) {
return $fields['txt'];
}
return null;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
abstract class MapperAbstract implements MapperInterface
{
protected array $fields = [];
public final function __construct(array $fields = [])
{
$this->fields = $fields;
}
public function mapFields(array $fields) : MapperInterface
{
return new static($fields);
}
public abstract function toDNSRecord() : DNSRecordInterface;
}

View File

@ -0,0 +1,10 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
interface MapperInterface
{
public function mapFields(array $fields) : MapperInterface;
public function toDNSRecord() : DNSRecordInterface;
}

View File

@ -0,0 +1,58 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces\Resolver;
final class DNSQueried extends ObservableEventAbstract
{
public const NAME = 'dns.queried';
private Resolver $resolver;
private Hostname $hostname;
private DNSRecordType $recordType;
private DNSRecordCollection $recordCollection;
public function __construct(Resolver $resolver, Hostname $hostname, DNSRecordType $recordType, DNSRecordCollection $recordCollection = null)
{
parent::__construct();
$this->resolver = $resolver;
$this->hostname = $hostname;
$this->recordType = $recordType;
$this->recordCollection = $recordCollection ?? new DNSRecordCollection();
}
public function getResolver() : Resolver
{
return $this->resolver;
}
public function getHostname() : Hostname
{
return $this->hostname;
}
public function getRecordType() : DNSRecordType
{
return $this->recordType;
}
public function getRecordCollection() : DNSRecordCollection
{
return $this->recordCollection;
}
public static function getName() : string
{
return self::NAME;
}
public function toArray() : array
{
return ['resolver' => $this->resolver->getName(), 'hostname' => (string) $this->hostname, 'type' => (string) $this->recordType, 'records' => $this->formatCollection($this->recordCollection), 'empty' => $this->recordCollection->isEmpty()];
}
private function formatCollection(DNSRecordCollection $recordCollection) : array
{
$formatted = [];
foreach ($recordCollection as $record) {
if ($record) {
$formatted[] = $record->toArray();
}
}
return $formatted;
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions\Exception;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces\Resolver;
final class DNSQueryFailed extends ObservableEventAbstract
{
public const NAME = 'dns.query.failed';
private Resolver $resolver;
private Hostname $hostname;
private DNSRecordType $recordType;
private Exception $error;
public function __construct(Resolver $resolver, Hostname $hostname, DNSRecordType $recordType, Exception $error)
{
parent::__construct();
$this->resolver = $resolver;
$this->hostname = $hostname;
$this->recordType = $recordType;
$this->error = $error;
}
public function getResolver() : Resolver
{
return $this->resolver;
}
public function getHostName() : Hostname
{
return $this->hostname;
}
public function getRecordType() : DNSRecordType
{
return $this->recordType;
}
public function getError() : Exception
{
return $this->error;
}
public static function getName() : string
{
return self::NAME;
}
public function toArray() : array
{
return ['resolver' => $this->resolver->getName(), 'hostname' => (string) $this->hostname, 'type' => (string) $this->recordType, 'error' => $this->error];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance\Interfaces\ProfileInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance\Profile;
final class DNSQueryProfiled extends ObservableEventAbstract
{
public const NAME = 'dns.query.profiled';
private ProfileInterface $profile;
public function __construct(ProfileInterface $profile)
{
parent::__construct();
$this->profile = $profile;
}
public function getProfile() : ProfileInterface
{
return $this->profile;
}
public static function getName() : string
{
return self::NAME;
}
public function toArray() : array
{
return ['elapsedSeconds' => $this->profile->getElapsedSeconds(), 'transactionName' => $this->profile->getTransactionName(), 'peakMemoryUsage' => $this->profile->getPeakMemoryUsage()];
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events;
use JsonSerializable;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\Arrayable;
use WP_Ultimo\Dependencies\Symfony\Component\EventDispatcher\GenericEvent;
abstract class ObservableEventAbstract extends GenericEvent implements JsonSerializable, Arrayable
{
public static abstract function getName() : string;
public function jsonSerialize() : array
{
return [$this::getName() => $this->toArray()];
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Interfaces;
use WP_Ultimo\Dependencies\Symfony\Component\EventDispatcher\EventSubscriberInterface;
interface Observable
{
public function addSubscriber(EventSubscriberInterface $subscriber) : void;
public function addListener(string $eventName, callable $listener, int $priority = 0) : void;
}

View File

@ -0,0 +1,13 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance\Interfaces;
interface ProfileInterface
{
public function startTransaction() : void;
public function endTransaction() : void;
public function getTransactionName() : string;
public function getElapsedSeconds() : float;
public function samplePeakMemoryUsage() : void;
public function getPeakMemoryUsage() : int;
}

View File

@ -0,0 +1,10 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance\Interfaces;
use DateTimeInterface;
interface Time
{
public function getMicrotime() : float;
public function now() : DateTimeInterface;
}

View File

@ -0,0 +1,44 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance\Interfaces\ProfileInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance\Interfaces\Time;
use function memory_get_peak_usage;
final class Profile implements ProfileInterface
{
private string $transactionName;
private float $startTime = 0.0;
private float $stopTime = 0.0;
private int $peakMemoryUsage = 0;
private Time $time;
public function __construct(string $transactionName, Time $time = null)
{
$this->transactionName = $transactionName;
$this->time = $time ?? new Timer();
}
public function startTransaction() : void
{
$this->startTime = $this->time->getMicroTime();
}
public function endTransaction() : void
{
$this->stopTime = $this->time->getMicroTime();
}
public function getTransactionName() : string
{
return $this->transactionName;
}
public function getElapsedSeconds() : float
{
return $this->stopTime - $this->startTime;
}
public function samplePeakMemoryUsage() : void
{
$this->peakMemoryUsage = memory_get_peak_usage();
}
public function getPeakMemoryUsage() : int
{
return $this->peakMemoryUsage;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance;
class ProfileFactory
{
public function create(string $transactionName) : Profile
{
return new Profile($transactionName);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance;
use DateTimeImmutable;
use DateTimeInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance\Interfaces\Time;
use function microtime;
final class Timer implements Time
{
public function getMicroTime() : float
{
return microtime(\true);
}
public function now() : DateTimeInterface
{
return new DateTimeImmutable('now');
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Subscribers;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events\DNSQueried;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events\DNSQueryFailed;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events\DNSQueryProfiled;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events\ObservableEventAbstract;
use SplFileObject;
use WP_Ultimo\Dependencies\Symfony\Component\EventDispatcher\EventSubscriberInterface;
use function json_encode;
final class STDIOSubscriber implements EventSubscriberInterface
{
private SplFileObject $STDOUT;
private SplFileObject $STDERR;
public function __construct(SplFileObject $stdOut, SplFileObject $stdErr)
{
$this->STDOUT = $stdOut;
$this->STDERR = $stdErr;
}
public static function getSubscribedEvents()
{
return [DNSQueryFailed::getName() => 'onDNSQueryFailed', DNSQueried::getName() => 'onDNSQueried', DNSQueryProfiled::getName() => 'onDNSQueryProfiled'];
}
public function onDNSQueryFailed(ObservableEventAbstract $event) : void
{
$this->STDERR->fwrite(json_encode($event, \JSON_PRETTY_PRINT) . \PHP_EOL);
}
public function onDNSQueried(ObservableEventAbstract $event) : void
{
$this->STDOUT->fwrite(json_encode($event, \JSON_PRETTY_PRINT) . \PHP_EOL);
}
public function onDNSQueryProfiled(ObservableEventAbstract $event) : void
{
$this->STDOUT->fwrite(json_encode($event, \JSON_PRETTY_PRINT) . \PHP_EOL);
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Traits;
use LogicException;
use ReflectionClass;
use ReflectionMethod;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events\ObservableEventAbstract;
use WP_Ultimo\Dependencies\Symfony\Component\EventDispatcher\EventDispatcher;
use WP_Ultimo\Dependencies\Symfony\Component\EventDispatcher\EventDispatcherInterface;
use WP_Ultimo\Dependencies\Symfony\Component\EventDispatcher\EventSubscriberInterface;
use function call_user_func_array;
trait Dispatcher
{
private ?EventDispatcherInterface $dispatcher = null;
public function setDispatcher(EventDispatcherInterface $dispatcher) : void
{
$this->dispatcher = $dispatcher;
}
public function addSubscriber(EventSubscriberInterface $subscriber) : void
{
$this->getDispatcher()->addSubscriber($subscriber);
}
public function addListener(string $eventName, callable $listener, int $priority = 0) : void
{
$this->getDispatcher()->addListener($eventName, $listener, $priority);
}
public function dispatch(ObservableEventAbstract $event) : void
{
call_user_func_array([$this->getDispatcher(), 'dispatch'], $this->getOrderedDispatcherArguments($event));
}
private function getOrderedDispatcherArguments(ObservableEventAbstract $event) : array
{
$reflection = new ReflectionClass($this->getDispatcher());
foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
if ($method->getName() !== 'dispatch') {
continue;
}
// handle the reverse argument BC from symfony dispatcher 3.* to 4.*
foreach ($method->getParameters() as $parameter) {
return $parameter->getName() === 'event' ? [$event, $event::getName()] : [$event::getName(), $event];
}
}
throw new LogicException('Could not determine argument order for dispatcher');
}
private function getDispatcher() : EventDispatcherInterface
{
if ($this->dispatcher === null) {
$this->dispatcher = new EventDispatcher();
}
return $this->dispatcher;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Traits;
use WP_Ultimo\Dependencies\Psr\Log\LoggerInterface;
use WP_Ultimo\Dependencies\Psr\Log\NullLogger;
trait Logger
{
private ?LoggerInterface $logger = null;
public function setLogger(LoggerInterface $logger) : void
{
$this->logger = $logger;
}
protected function getLogger() : LoggerInterface
{
if ($this->logger === null) {
$this->logger = new NullLogger();
}
return $this->logger;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Traits;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance\Profile;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Performance\ProfileFactory;
trait Profileable
{
private ?ProfileFactory $profileFactory = null;
public function createProfile(string $transactionName) : Profile
{
return $this->getProfileFactory()->create($transactionName);
}
public function setProfileFactory(ProfileFactory $profileFactory) : void
{
$this->profileFactory = $profileFactory;
}
private function getProfileFactory() : ProfileFactory
{
if ($this->profileFactory === null) {
$this->profileFactory = new ProfileFactory();
}
return $this->profileFactory;
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers;
use WP_Ultimo\Dependencies\Psr\Cache\CacheItemPoolInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Traits\Time;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces\Resolver;
use function count;
use function max;
use function md5;
use function min;
use function sprintf;
final class Cached extends ResolverAbstract
{
use Time;
protected const DEFAULT_CACHE_TTL = 300;
private const CACHE_KEY_TEMPLATE = '%s:%s:%s';
private CacheItemPoolInterface $cache;
private Resolver $resolver;
/**
* Bump this number on breaking changes to invalidate cache
*/
private const NAMESPACE = 'php-dns-v4.0.1';
private ?int $ttlSeconds;
private bool $shouldCacheEmptyResults = \true;
public function __construct(CacheItemPoolInterface $cache, Resolver $resolver, int $ttlSeconds = null)
{
$this->cache = $cache;
$this->resolver = $resolver;
$this->ttlSeconds = $ttlSeconds;
}
public function flush() : void
{
$this->cache->clear();
}
public function withEmptyResultCachingDisabled() : self
{
$emptyCachingDisabled = new self($this->cache, $this->resolver, $this->ttlSeconds);
$emptyCachingDisabled->shouldCacheEmptyResults = \false;
return $emptyCachingDisabled;
}
protected function doQuery(Hostname $hostname, DNSRecordType $recordType) : DNSRecordCollection
{
$cachedResult = $this->cache->getItem($this->buildCacheKey($hostname, $recordType));
if ($cachedResult->isHit()) {
return $this->unwrapResults($cachedResult->get());
}
$dnsRecords = $this->resolver->getRecords((string) $hostname, (string) $recordType);
if ($dnsRecords->isEmpty() && $this->shouldCacheEmptyResults === \false) {
return $dnsRecords;
}
$ttlSeconds = $this->ttlSeconds ?? $this->extractLowestTTL($dnsRecords);
$cachedResult->expiresAfter($ttlSeconds);
$cachedResult->set(['recordCollection' => $dnsRecords, 'timestamp' => $this->getTimeStamp()]);
$this->cache->save($cachedResult);
return $dnsRecords;
}
private function buildCacheKey(Hostname $hostname, DNSRecordType $recordType) : string
{
return md5(sprintf(self::CACHE_KEY_TEMPLATE, self::NAMESPACE, (string) $hostname, (string) $recordType));
}
private function extractLowestTTL(DNSRecordCollection $recordCollection) : int
{
$ttls = [];
/** @var \RemotelyLiving\PHPDNS\Entities\DNSRecord $record */
foreach ($recordCollection as $record) {
/** @scrutinizer ignore-call */
if ($record->getTTL() <= 0) {
continue;
}
$ttls[] = $record->getTTL();
}
return count($ttls) ? min($ttls) : self::DEFAULT_CACHE_TTL;
}
/**
* @param array $results ['recordCollection' => $recordCollection, 'timestamp' => $timeStamp]
*/
private function unwrapResults(array $results) : DNSRecordCollection
{
/** @var DNSRecordCollection $records */
$records = $results['recordCollection'];
/**
* @var int $key
* @var \RemotelyLiving\PHPDNS\Entities\DNSRecord $record
*/
foreach ($records as $key => $record) {
$records[$key] = $record->setTTL(max($record->getTTL() - ($this->getTimeStamp() - (int) $results['timestamp']), 0));
}
return $records;
}
}

View File

@ -0,0 +1,119 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers;
use WP_Ultimo\Dependencies\Psr\Log\LoggerAwareInterface;
use WP_Ultimo\Dependencies\Psr\Log\LoggerInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecord;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions\Exception;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Interfaces\Observable;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces\Resolver;
use WP_Ultimo\Dependencies\Symfony\Component\EventDispatcher\EventSubscriberInterface;
use function json_encode;
use function shuffle;
final class Chain extends ResolverAbstract implements Interfaces\Chain
{
public const STRATEGY_FIRST_TO_FIND = 0;
public const STRATEGY_ALL_RESULTS = 1;
public const STRATEGY_CONSENSUS = 2;
/**
* @var \RemotelyLiving\PHPDNS\Resolvers\Interfaces\Resolver[]
*/
private array $resolvers = [];
private int $callThroughStrategy = self::STRATEGY_FIRST_TO_FIND;
public function __construct(Resolver ...$resolvers)
{
foreach ($resolvers as $resolver) {
$this->pushResolver($resolver);
}
}
public function pushResolver(Resolver ...$resolvers) : void
{
foreach ($resolvers as $resolver) {
$this->resolvers[] = $resolver;
}
}
public function withAllResults() : Interfaces\Chain
{
$all = new self(...$this->resolvers);
$all->callThroughStrategy = self::STRATEGY_ALL_RESULTS;
return $all;
}
public function withFirstResults() : Interfaces\Chain
{
$first = new self(...$this->resolvers);
$first->callThroughStrategy = self::STRATEGY_FIRST_TO_FIND;
return $first;
}
public function withConsensusResults() : Interfaces\Chain
{
$consensus = new self(...$this->resolvers);
$consensus->callThroughStrategy = self::STRATEGY_CONSENSUS;
return $consensus;
}
public function randomly() : Interfaces\Chain
{
$randomized = clone $this;
shuffle($randomized->resolvers);
return $randomized;
}
public function addSubscriber(EventSubscriberInterface $subscriber) : void
{
foreach ($this->resolvers as $resolver) {
if ($resolver instanceof Observable) {
$resolver->addSubscriber($subscriber);
}
}
}
public function addListener(string $eventName, callable $listener, int $priority = 0) : void
{
foreach ($this->resolvers as $resolver) {
if ($resolver instanceof Observable) {
$resolver->addListener($eventName, $listener, $priority);
}
}
}
public function setLogger(LoggerInterface $logger) : void
{
foreach ($this->resolvers as $resolver) {
if ($resolver instanceof LoggerAwareInterface) {
$resolver->setLogger($logger);
}
}
}
public function hasRecord(DNSRecordInterface $record) : bool
{
foreach ($this->resolvers as $resolver) {
if ($resolver->hasRecord($record)) {
return \true;
}
}
return \false;
}
protected function doQuery(Hostname $hostname, DNSRecordType $recordType) : DNSRecordCollection
{
$merged = [];
foreach ($this->resolvers as $resolver) {
try {
$records = $resolver->getRecords((string) $hostname, (string) $recordType);
} catch (Exception $e) {
$this->getLogger()->error('Something went wrong in the chain of resolvers', ['exception' => json_encode($e), 'resolver' => $resolver->getName()]);
continue;
}
if ($this->callThroughStrategy === self::STRATEGY_FIRST_TO_FIND && !$records->isEmpty()) {
return $records;
}
/** @var DNSRecord $record */
foreach ($records as $record) {
$merged[] = $record;
}
}
$collection = new DNSRecordCollection(...$merged);
return $this->callThroughStrategy === self::STRATEGY_CONSENSUS ? $collection->withUniqueValuesExcluded() : $collection->withUniqueValues();
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers;
use Generator;
use WP_Ultimo\Dependencies\GuzzleHttp\Client;
use WP_Ultimo\Dependencies\GuzzleHttp\ClientInterface;
use WP_Ultimo\Dependencies\GuzzleHttp\Promise\EachPromise;
use WP_Ultimo\Dependencies\Psr\Http\Message\ResponseInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers\CloudFlare as CloudFlareMapper;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure;
use Throwable;
use function array_merge;
use function http_build_query;
use function json_decode;
final class CloudFlare extends ResolverAbstract
{
protected const BASE_URI = 'https://cloudflare-dns.com';
protected const DEFAULT_TIMEOUT = 5.0;
public const DEFAULT_OPTIONS = ['base_uri' => self::BASE_URI, 'connect_timeout' => self::DEFAULT_TIMEOUT, 'strict' => \true, 'allow_redirects' => \false, 'protocols' => ['https'], 'headers' => ['Accept' => 'application/dns-json']];
private ClientInterface $http;
private CloudFlareMapper $mapper;
/**
* @var array<string, mixed>
*/
private array $options;
public function __construct(ClientInterface $http = null, CloudFlareMapper $mapper = null, array $options = self::DEFAULT_OPTIONS)
{
$this->http = $http ?? new Client();
$this->mapper = $mapper ?? new CloudFlareMapper();
$this->options = $options;
}
protected function doQuery(Hostname $hostname, DNSRecordType $recordType) : DNSRecordCollection
{
try {
return !$recordType->isA(DNSRecordType::TYPE_ANY) ? $this->doApiQuery($hostname, $recordType) : $this->doAnyApiQuery($hostname);
} catch (Throwable $e) {
throw new QueryFailure("Unable to query CloudFlare API", 0, $e);
}
}
/**
* Cloudflare does not support ANY queries, so we must ask for all record types individually
*/
private function doAnyApiQuery(Hostname $hostname) : DNSRecordCollection
{
$results = [];
$eachPromise = new EachPromise($this->generateEachTypeQuery($hostname), ['concurrency' => 4, 'fulfilled' => function (ResponseInterface $response) use(&$results) {
$results = array_merge($results, $this->parseResult((array) json_decode((string) $response->getBody(), \true)));
}, 'rejected' => function (Throwable $e) : void {
throw $e;
}]);
$eachPromise->promise()->wait(\true);
return $this->mapResults($this->mapper, $results);
}
private function generateEachTypeQuery(Hostname $hostname) : Generator
{
foreach (DNSRecordType::VALID_TYPES as $type) {
if ($type === DNSRecordType::TYPE_ANY) {
continue 1;
}
(yield $this->http->requestAsync('GET', '/dns-query?' . http_build_query(['name' => (string) $hostname, 'type' => $type]), $this->options));
}
}
private function doApiQuery(Hostname $hostname, DNSRecordType $type) : DNSRecordCollection
{
$url = '/dns-query?' . http_build_query(['name' => (string) $hostname, 'type' => (string) $type]);
$decoded = (array) json_decode((string) $this->http->requestAsync('GET', $url, $this->options)->wait(\true)->getBody(), \true);
return $this->mapResults($this->mapper, $this->parseResult($decoded));
}
private function parseResult(array $result) : array
{
if (isset($result['Answer'])) {
return $result['Answer'];
}
if (isset($result['Authority'])) {
return $result['Authority'];
}
return [];
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Factories\SpatieDNS;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers\Dig as DigMapper;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure;
use Throwable;
use function array_slice;
use function explode;
use function implode;
use function preg_replace;
use function trim;
final class Dig extends ResolverAbstract
{
public const SUPPORTED_QUERY_TYPES = [DNSRecordType::TYPE_A, DNSRecordType::TYPE_AAAA, DNSRecordType::TYPE_CNAME, DNSRecordType::TYPE_NS, DNSRecordType::TYPE_SOA, DNSRecordType::TYPE_MX, DNSRecordType::TYPE_SRV, DNSRecordType::TYPE_TXT, DNSRecordType::TYPE_CAA, DNSRecordType::TYPE_NAPTR];
private SpatieDNS $spatieDNSFactory;
private DigMapper $mapper;
private ?Hostname $nameserver;
public function __construct(SpatieDNS $spatieDNSFactory = null, DigMapper $mapper = null, Hostname $nameserver = null)
{
$this->spatieDNSFactory = $spatieDNSFactory ?? new SpatieDNS();
$this->mapper = $mapper ?? new DigMapper();
$this->nameserver = $nameserver;
}
protected function doQuery(Hostname $hostname, DNSRecordType $recordType) : DNSRecordCollection
{
if (!self::isSupportedQueryType($recordType)) {
return new DNSRecordCollection();
}
$dig = $this->spatieDNSFactory->createResolver($hostname, $this->nameserver);
try {
$response = $recordType->equals(DNSRecordType::createANY()) ? $dig->getRecords(...self::SUPPORTED_QUERY_TYPES) : $dig->getRecords((string) $recordType);
} catch (Throwable $e) {
throw new QueryFailure($e->getMessage(), 0, $e);
}
return $this->mapResults($this->mapper, self::parseDigResponseToRows($response));
}
private static function parseDigResponseToRows(string $digResponse) : array
{
$rows = [];
foreach (explode(\PHP_EOL, self::normalizeColumns($digResponse)) as $line) {
if (!trim($line)) {
continue;
}
$columns = explode(' ', $line);
$rows[] = [$columns[0], $columns[1], $columns[2], $columns[3], implode(' ', array_slice($columns, 4))];
}
return $rows;
}
private static function normalizeColumns(string $response) : string
{
$keysRemoved = preg_replace('/;(.*)/m', ' ', trim($response));
$tabsRemoved = preg_replace('/(\\t+)/m', ' ', (string) $keysRemoved);
$breaksRemoved = preg_replace('/\\s\\s/m', '', (string) $tabsRemoved);
return (string) preg_replace('/(\\(\\s|(\\s\\)))/m', '', (string) $breaksRemoved);
}
private static function isSupportedQueryType(DNSRecordType $type) : bool
{
if ($type->isA(DNSRecordType::TYPE_ANY)) {
return \true;
}
foreach (self::SUPPORTED_QUERY_TYPES as $queryType) {
if ($type->isA($queryType)) {
return \true;
}
}
return \false;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Exceptions;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions\Exception;
class QueryFailure extends Exception
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Exceptions;
final class ReverseLookupFailure extends QueryFailure
{
}

View File

@ -0,0 +1,73 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers;
use WP_Ultimo\Dependencies\GuzzleHttp\Client;
use WP_Ultimo\Dependencies\GuzzleHttp\ClientInterface;
use WP_Ultimo\Dependencies\GuzzleHttp\Exception\RequestException;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecord;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers\GoogleDNS as GoogleDNSMapper;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure;
use Throwable;
use function http_build_query;
use function json_decode;
final class GoogleDNS extends ResolverAbstract
{
protected const BASE_URI = 'https://dns.google.com';
protected const DEFAULT_TIMEOUT = 5.0;
public const DEFAULT_OPTIONS = ['base_uri' => self::BASE_URI, 'strict' => \true, 'allow_redirects' => \false, 'connect_timeout' => self::DEFAULT_TIMEOUT, 'protocols' => ['https'], 'headers' => ['Accept' => 'application/json']];
private ClientInterface $http;
private GoogleDNSMapper $mapper;
private int $consensusAttempts;
/**
* @var array<string, mixed>
*/
private array $options;
public function __construct(ClientInterface $http = null, GoogleDNSMapper $mapper = null, int $consensusAttempts = 3, array $options = self::DEFAULT_OPTIONS)
{
$this->http = $http ?? new Client();
$this->mapper = $mapper ?? new GoogleDNSMapper();
$this->consensusAttempts = $consensusAttempts;
$this->options = $options;
}
/**
* Google DNS has consistency issues so this tries a few times to get an answer
*/
public function hasRecord(DNSRecordInterface $record) : bool
{
$attempts = 0;
do {
$hasRecord = $this->getRecords((string) $record->getHostname(), (string) $record->getType())->has($record);
++$attempts;
} while ($hasRecord === \false && $attempts < $this->consensusAttempts);
return $hasRecord;
}
protected function doQuery(Hostname $hostname, DNSRecordType $recordType) : DNSRecordCollection
{
$results = $this->doApiQuery(['name' => (string) $hostname, 'type' => (string) $recordType]);
return $this->mapResults($this->mapper, $results);
}
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
private function doApiQuery(array $query = []) : array
{
try {
$response = $this->http->request('GET', '/resolve?' . http_build_query($query), $this->options);
} catch (Throwable $e) {
throw new QueryFailure("Unable to query GoogleDNS API", 0, $e);
}
$result = (array) json_decode((string) $response->getBody(), \true);
if (isset($result['Answer'])) {
return $result['Answer'];
}
if (isset($result['Authority'])) {
return $result['Authority'];
}
return [];
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces;
interface Chain extends Resolver
{
/**
* Randomizes the order in with the chain is called
*/
public function randomly() : Chain;
/**
* Returns all the records from each Resolver together.
* This calls all Resolvers in the chain.
*/
public function withAllResults() : Chain;
/**
* Returns the results from the first Resolver to have results.
* This calls as many Resolvers as it needs to find the first result.
*/
public function withFirstResults() : Chain;
/**
* Returns only the common results between Resolvers that had results.
* This calls through all Resolvers in the chain
*/
public function withConsensusResults() : Chain;
}

View File

@ -0,0 +1,44 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecord;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
interface DNSQuery
{
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
public function getARecords(string $hostname) : DNSRecordCollection;
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
public function getAAAARecords(string $hostname) : DNSRecordCollection;
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
public function getCNAMERecords(string $hostname) : DNSRecordCollection;
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
public function getTXTRecords(string $hostname) : DNSRecordCollection;
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
public function getMXRecords(string $hostname) : DNSRecordCollection;
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
public function getRecords(string $hostname, string $recordType = null) : DNSRecordCollection;
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
public function recordTypeExists(string $hostname, string $recordType) : bool;
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
public function hasRecord(DNSRecordInterface $record) : bool;
}

View File

@ -0,0 +1,9 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces;
use WP_Ultimo\Dependencies\Psr\Log\LoggerAwareInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Interfaces\Observable;
interface ObservableResolver extends Resolver, Observable, LoggerAwareInterface
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces;
interface Resolver extends DNSQuery
{
public function getName() : string;
}

View File

@ -0,0 +1,13 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\IPAddress;
interface ReverseDNSQuery
{
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\ReverseLookupFailure
*/
public function getHostnameByAddress(string $IPAddress) : Hostname;
}

View File

@ -0,0 +1,40 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\IPAddress;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers\LocalSystem as LocalMapper;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces\ReverseDNSQuery;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Services\Interfaces\LocalSystemDNS;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Services\LocalSystemDNS as LocalDNSService;
final class LocalSystem extends ResolverAbstract implements ReverseDNSQuery
{
private LocalSystemDNS $systemDNS;
private LocalMapper $mapper;
public function __construct(LocalSystemDNS $systemDNS = null, LocalMapper $mapper = null)
{
$this->systemDNS = $systemDNS ?? new LocalDNSService();
$this->mapper = $mapper ?? new LocalMapper();
}
public function getHostnameByAddress(string $IPAddress) : Hostname
{
$result = $this->systemDNS->getHostnameByAddress((string) new IPAddress($IPAddress));
return Hostname::createFromString($result);
}
protected function doQuery(Hostname $hostname, DNSRecordType $recordType) : DNSRecordCollection
{
$results = $this->systemDNS->getRecord(
$hostname->getHostnameWithoutTrailingDot(),
// dns_get_record doesn't like trailing dot as much!
$this->mapper->getTypeCodeFromType($recordType)
);
$collection = new DNSRecordCollection();
foreach ($results as $result) {
$collection[] = $this->mapper->mapFields($result)->toDNSRecord();
}
return $collection;
}
}

View File

@ -0,0 +1,115 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecord;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers\MapperInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events\DNSQueried;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events\DNSQueryFailed;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events\DNSQueryProfiled;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Traits\Dispatcher;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Traits\Logger;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Traits\Profileable;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces\ObservableResolver;
use function array_map;
use function array_pop;
use function explode;
use function get_class;
use function json_encode;
abstract class ResolverAbstract implements ObservableResolver
{
use Logger;
use Dispatcher;
use Profileable;
private string $name;
/**
* @var string
*/
private const EVENT = 'event';
public function getName() : string
{
if (!isset($this->name)) {
$explodedClass = explode('\\', get_class($this));
$this->name = array_pop($explodedClass);
}
return $this->name;
}
public function getARecords(string $hostname) : DNSRecordCollection
{
return $this->getRecords($hostname, (string) DNSRecordType::createA());
}
public function getAAAARecords(string $hostname) : DNSRecordCollection
{
return $this->getRecords($hostname, (string) DNSRecordType::createAAAA());
}
public function getCNAMERecords(string $hostname) : DNSRecordCollection
{
return $this->getRecords($hostname, (string) DNSRecordType::createCNAME());
}
public function getTXTRecords(string $hostname) : DNSRecordCollection
{
return $this->getRecords($hostname, (string) DNSRecordType::createTXT());
}
public function getMXRecords(string $hostname) : DNSRecordCollection
{
return $this->getRecords($hostname, (string) DNSRecordType::createMX());
}
public function recordTypeExists(string $hostname, string $recordType) : bool
{
return !$this->getRecords($hostname, $recordType)->isEmpty();
}
public function hasRecord(DNSRecordInterface $record) : bool
{
return $this->getRecords((string) $record->getHostname(), (string) $record->getType())->has($record);
}
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
public function getRecords(string $hostname, string $recordType = null) : DNSRecordCollection
{
$recordType = DNSRecordType::createFromString($recordType ?? 'ANY');
$hostname = Hostname::createFromString($hostname);
$profile = $this->createProfile("{$this->getName()}:{$hostname}:{$recordType}");
$profile->startTransaction();
try {
$result = $recordType->equals(DNSRecordType::createANY()) ? $this->doQuery($hostname, $recordType) : $this->doQuery($hostname, $recordType)->filteredByType($recordType);
} catch (QueryFailure $e) {
$dnsQueryFailureEvent = new DNSQueryFailed($this, $hostname, $recordType, $e);
$this->dispatch($dnsQueryFailureEvent);
$this->getLogger()->error('DNS query failed', [self::EVENT => json_encode($dnsQueryFailureEvent), 'exception' => $e]);
throw $e;
} finally {
$profile->endTransaction();
$profile->samplePeakMemoryUsage();
$dnsQueryProfiledEvent = new DNSQueryProfiled($profile);
$this->dispatch($dnsQueryProfiledEvent);
$this->getLogger()->info('DNS query profiled', [self::EVENT => json_encode($dnsQueryProfiledEvent)]);
}
$dnsQueriedEvent = new DNSQueried($this, $hostname, $recordType, $result);
$this->dispatch($dnsQueriedEvent);
$this->getLogger()->info('DNS queried', [self::EVENT => json_encode($dnsQueriedEvent)]);
return $result;
}
public function mapResults(MapperInterface $mapper, array $results) : DNSRecordCollection
{
$collection = new DNSRecordCollection();
array_map(function (array $fields) use(&$collection, $mapper) {
try {
$collection[] = $mapper->mapFields($fields)->toDNSRecord();
} catch (InvalidArgumentException $e) {
$this->getLogger()->warning('Invalid fields passed to mapper', $fields);
}
}, $results);
return $collection;
}
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
protected abstract function doQuery(Hostname $hostname, DNSRecordType $recordType) : DNSRecordCollection;
}

View File

@ -0,0 +1,24 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Traits;
use DateTimeImmutable;
trait Time
{
private ?DateTimeImmutable $dateTimeImmutable = null;
public function setDateTimeImmutable(DateTimeImmutable $dateTimeImmutable) : void
{
$this->dateTimeImmutable = $dateTimeImmutable;
}
public function getTimeStamp() : int
{
return $this->getNewDateTimeImmutable()->getTimestamp();
}
private function getNewDateTimeImmutable() : DateTimeImmutable
{
if (!$this->dateTimeImmutable) {
$this->dateTimeImmutable = new DateTimeImmutable();
}
return $this->dateTimeImmutable->setTimestamp(\time());
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Services\Interfaces;
interface LocalSystemDNS
{
/**
* @param string $IPAddress
* @return string
*
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\ReverseLookupFailure
*/
public function getHostnameByAddress(string $IPAddress) : string;
/**
* @param string $hostname
* @param int $type
*
* @return array
*
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
public function getRecord(string $hostname, int $type) : array;
}

View File

@ -0,0 +1,37 @@
<?php
namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Services;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Exceptions\ReverseLookupFailure;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Services\Interfaces\LocalSystemDNS as LocalSystemDNSInterface;
use function dns_get_record;
use function gethostbyaddr;
final class LocalSystemDNS implements LocalSystemDNSInterface
{
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
*/
public function getRecord(string $hostname, int $type) : array
{
$results = @dns_get_record($hostname, $type);
// this is untestable without creating a system networking failure
// @codeCoverageIgnoreStart
if ($results === \false) {
throw new QueryFailure();
}
// @codeCoverageIgnoreEnd
return $results;
}
/**
* @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\ReverseLookupFailure
*/
public function getHostnameByAddress(string $IPAddress) : string
{
$hostname = @gethostbyaddr($IPAddress);
if ($hostname === $IPAddress || $hostname === \false) {
throw new ReverseLookupFailure();
}
return $hostname;
}
}