diff options
Diffstat (limited to 'vendor/react/dns/src/Resolver')
-rw-r--r-- | vendor/react/dns/src/Resolver/Factory.php | 214 | ||||
-rw-r--r-- | vendor/react/dns/src/Resolver/Resolver.php | 147 | ||||
-rw-r--r-- | vendor/react/dns/src/Resolver/ResolverInterface.php | 94 |
3 files changed, 455 insertions, 0 deletions
diff --git a/vendor/react/dns/src/Resolver/Factory.php b/vendor/react/dns/src/Resolver/Factory.php new file mode 100644 index 0000000..5fe608c --- /dev/null +++ b/vendor/react/dns/src/Resolver/Factory.php @@ -0,0 +1,214 @@ +<?php + +namespace React\Dns\Resolver; + +use React\Cache\ArrayCache; +use React\Cache\CacheInterface; +use React\Dns\Config\Config; +use React\Dns\Config\HostsFile; +use React\Dns\Query\CachingExecutor; +use React\Dns\Query\CoopExecutor; +use React\Dns\Query\ExecutorInterface; +use React\Dns\Query\FallbackExecutor; +use React\Dns\Query\HostsFileExecutor; +use React\Dns\Query\RetryExecutor; +use React\Dns\Query\SelectiveTransportExecutor; +use React\Dns\Query\TcpTransportExecutor; +use React\Dns\Query\TimeoutExecutor; +use React\Dns\Query\UdpTransportExecutor; +use React\EventLoop\Loop; +use React\EventLoop\LoopInterface; + +final class Factory +{ + /** + * Creates a DNS resolver instance for the given DNS config + * + * As of v1.7.0 it's recommended to pass a `Config` object instead of a + * single nameserver address. If the given config contains more than one DNS + * nameserver, all DNS nameservers will be used in order. The primary DNS + * server will always be used first before falling back to the secondary or + * tertiary DNS server. + * + * @param Config|string $config DNS Config object (recommended) or single nameserver address + * @param ?LoopInterface $loop + * @return \React\Dns\Resolver\ResolverInterface + * @throws \InvalidArgumentException for invalid DNS server address + * @throws \UnderflowException when given DNS Config object has an empty list of nameservers + */ + public function create($config, LoopInterface $loop = null) + { + $executor = $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get())); + + return new Resolver($executor); + } + + /** + * Creates a cached DNS resolver instance for the given DNS config and cache + * + * As of v1.7.0 it's recommended to pass a `Config` object instead of a + * single nameserver address. If the given config contains more than one DNS + * nameserver, all DNS nameservers will be used in order. The primary DNS + * server will always be used first before falling back to the secondary or + * tertiary DNS server. + * + * @param Config|string $config DNS Config object (recommended) or single nameserver address + * @param ?LoopInterface $loop + * @param ?CacheInterface $cache + * @return \React\Dns\Resolver\ResolverInterface + * @throws \InvalidArgumentException for invalid DNS server address + * @throws \UnderflowException when given DNS Config object has an empty list of nameservers + */ + public function createCached($config, LoopInterface $loop = null, CacheInterface $cache = null) + { + // default to keeping maximum of 256 responses in cache unless explicitly given + if (!($cache instanceof CacheInterface)) { + $cache = new ArrayCache(256); + } + + $executor = $this->createExecutor($config, $loop ?: Loop::get()); + $executor = new CachingExecutor($executor, $cache); + $executor = $this->decorateHostsFileExecutor($executor); + + return new Resolver($executor); + } + + /** + * Tries to load the hosts file and decorates the given executor on success + * + * @param ExecutorInterface $executor + * @return ExecutorInterface + * @codeCoverageIgnore + */ + private function decorateHostsFileExecutor(ExecutorInterface $executor) + { + try { + $executor = new HostsFileExecutor( + HostsFile::loadFromPathBlocking(), + $executor + ); + } catch (\RuntimeException $e) { + // ignore this file if it can not be loaded + } + + // Windows does not store localhost in hosts file by default but handles this internally + // To compensate for this, we explicitly use hard-coded defaults for localhost + if (DIRECTORY_SEPARATOR === '\\') { + $executor = new HostsFileExecutor( + new HostsFile("127.0.0.1 localhost\n::1 localhost"), + $executor + ); + } + + return $executor; + } + + /** + * @param Config|string $nameserver + * @param LoopInterface $loop + * @return CoopExecutor + * @throws \InvalidArgumentException for invalid DNS server address + * @throws \UnderflowException when given DNS Config object has an empty list of nameservers + */ + private function createExecutor($nameserver, LoopInterface $loop) + { + if ($nameserver instanceof Config) { + if (!$nameserver->nameservers) { + throw new \UnderflowException('Empty config with no DNS servers'); + } + + // Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config). + // Note to future self: Recursion isn't too hard, but how deep do we really want to go? + $primary = reset($nameserver->nameservers); + $secondary = next($nameserver->nameservers); + $tertiary = next($nameserver->nameservers); + + if ($tertiary !== false) { + // 3 DNS servers given => nest first with fallback for second and third + return new CoopExecutor( + new RetryExecutor( + new FallbackExecutor( + $this->createSingleExecutor($primary, $loop), + new FallbackExecutor( + $this->createSingleExecutor($secondary, $loop), + $this->createSingleExecutor($tertiary, $loop) + ) + ) + ) + ); + } elseif ($secondary !== false) { + // 2 DNS servers given => fallback from first to second + return new CoopExecutor( + new RetryExecutor( + new FallbackExecutor( + $this->createSingleExecutor($primary, $loop), + $this->createSingleExecutor($secondary, $loop) + ) + ) + ); + } else { + // 1 DNS server given => use single executor + $nameserver = $primary; + } + } + + return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop))); + } + + /** + * @param string $nameserver + * @param LoopInterface $loop + * @return ExecutorInterface + * @throws \InvalidArgumentException for invalid DNS server address + */ + private function createSingleExecutor($nameserver, LoopInterface $loop) + { + $parts = \parse_url($nameserver); + + if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') { + $executor = $this->createTcpExecutor($nameserver, $loop); + } elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') { + $executor = $this->createUdpExecutor($nameserver, $loop); + } else { + $executor = new SelectiveTransportExecutor( + $this->createUdpExecutor($nameserver, $loop), + $this->createTcpExecutor($nameserver, $loop) + ); + } + + return $executor; + } + + /** + * @param string $nameserver + * @param LoopInterface $loop + * @return TimeoutExecutor + * @throws \InvalidArgumentException for invalid DNS server address + */ + private function createTcpExecutor($nameserver, LoopInterface $loop) + { + return new TimeoutExecutor( + new TcpTransportExecutor($nameserver, $loop), + 5.0, + $loop + ); + } + + /** + * @param string $nameserver + * @param LoopInterface $loop + * @return TimeoutExecutor + * @throws \InvalidArgumentException for invalid DNS server address + */ + private function createUdpExecutor($nameserver, LoopInterface $loop) + { + return new TimeoutExecutor( + new UdpTransportExecutor( + $nameserver, + $loop + ), + 5.0, + $loop + ); + } +} diff --git a/vendor/react/dns/src/Resolver/Resolver.php b/vendor/react/dns/src/Resolver/Resolver.php new file mode 100644 index 0000000..92926f3 --- /dev/null +++ b/vendor/react/dns/src/Resolver/Resolver.php @@ -0,0 +1,147 @@ +<?php + +namespace React\Dns\Resolver; + +use React\Dns\Model\Message; +use React\Dns\Query\ExecutorInterface; +use React\Dns\Query\Query; +use React\Dns\RecordNotFoundException; + +/** + * @see ResolverInterface for the base interface + */ +final class Resolver implements ResolverInterface +{ + private $executor; + + public function __construct(ExecutorInterface $executor) + { + $this->executor = $executor; + } + + public function resolve($domain) + { + return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) { + return $ips[array_rand($ips)]; + }); + } + + public function resolveAll($domain, $type) + { + $query = new Query($domain, $type, Message::CLASS_IN); + $that = $this; + + return $this->executor->query( + $query + )->then(function (Message $response) use ($query, $that) { + return $that->extractValues($query, $response); + }); + } + + /** + * [Internal] extract all resource record values from response for this query + * + * @param Query $query + * @param Message $response + * @return array + * @throws RecordNotFoundException when response indicates an error or contains no data + * @internal + */ + public function extractValues(Query $query, Message $response) + { + // reject if response code indicates this is an error response message + $code = $response->rcode; + if ($code !== Message::RCODE_OK) { + switch ($code) { + case Message::RCODE_FORMAT_ERROR: + $message = 'Format Error'; + break; + case Message::RCODE_SERVER_FAILURE: + $message = 'Server Failure'; + break; + case Message::RCODE_NAME_ERROR: + $message = 'Non-Existent Domain / NXDOMAIN'; + break; + case Message::RCODE_NOT_IMPLEMENTED: + $message = 'Not Implemented'; + break; + case Message::RCODE_REFUSED: + $message = 'Refused'; + break; + default: + $message = 'Unknown error response code ' . $code; + } + throw new RecordNotFoundException( + 'DNS query for ' . $query->describe() . ' returned an error response (' . $message . ')', + $code + ); + } + + $answers = $response->answers; + $addresses = $this->valuesByNameAndType($answers, $query->name, $query->type); + + // reject if we did not receive a valid answer (domain is valid, but no record for this type could be found) + if (0 === count($addresses)) { + throw new RecordNotFoundException( + 'DNS query for ' . $query->describe() . ' did not return a valid answer (NOERROR / NODATA)' + ); + } + + return array_values($addresses); + } + + /** + * @param \React\Dns\Model\Record[] $answers + * @param string $name + * @param int $type + * @return array + */ + private function valuesByNameAndType(array $answers, $name, $type) + { + // return all record values for this name and type (if any) + $named = $this->filterByName($answers, $name); + $records = $this->filterByType($named, $type); + if ($records) { + return $this->mapRecordData($records); + } + + // no matching records found? check if there are any matching CNAMEs instead + $cnameRecords = $this->filterByType($named, Message::TYPE_CNAME); + if ($cnameRecords) { + $cnames = $this->mapRecordData($cnameRecords); + foreach ($cnames as $cname) { + $records = array_merge( + $records, + $this->valuesByNameAndType($answers, $cname, $type) + ); + } + } + + return $records; + } + + private function filterByName(array $answers, $name) + { + return $this->filterByField($answers, 'name', $name); + } + + private function filterByType(array $answers, $type) + { + return $this->filterByField($answers, 'type', $type); + } + + private function filterByField(array $answers, $field, $value) + { + $value = strtolower($value); + return array_filter($answers, function ($answer) use ($field, $value) { + return $value === strtolower($answer->$field); + }); + } + + private function mapRecordData(array $records) + { + return array_map(function ($record) { + return $record->data; + }, $records); + } +} diff --git a/vendor/react/dns/src/Resolver/ResolverInterface.php b/vendor/react/dns/src/Resolver/ResolverInterface.php new file mode 100644 index 0000000..fe937dc --- /dev/null +++ b/vendor/react/dns/src/Resolver/ResolverInterface.php @@ -0,0 +1,94 @@ +<?php + +namespace React\Dns\Resolver; + +interface ResolverInterface +{ + /** + * Resolves the given $domain name to a single IPv4 address (type `A` query). + * + * ```php + * $resolver->resolve('reactphp.org')->then(function ($ip) { + * echo 'IP for reactphp.org is ' . $ip . PHP_EOL; + * }); + * ``` + * + * This is one of the main methods in this package. It sends a DNS query + * for the given $domain name to your DNS server and returns a single IP + * address on success. + * + * If the DNS server sends a DNS response message that contains more than + * one IP address for this query, it will randomly pick one of the IP + * addresses from the response. If you want the full list of IP addresses + * or want to send a different type of query, you should use the + * [`resolveAll()`](#resolveall) method instead. + * + * If the DNS server sends a DNS response message that indicates an error + * code, this method will reject with a `RecordNotFoundException`. Its + * message and code can be used to check for the response code. + * + * If the DNS communication fails and the server does not respond with a + * valid response message, this message will reject with an `Exception`. + * + * Pending DNS queries can be cancelled by cancelling its pending promise like so: + * + * ```php + * $promise = $resolver->resolve('reactphp.org'); + * + * $promise->cancel(); + * ``` + * + * @param string $domain + * @return \React\Promise\PromiseInterface<string,\Exception> + * resolves with a single IP address on success or rejects with an Exception on error. + */ + public function resolve($domain); + + /** + * Resolves all record values for the given $domain name and query $type. + * + * ```php + * $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) { + * echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL; + * }); + * + * $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) { + * echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL; + * }); + * ``` + * + * This is one of the main methods in this package. It sends a DNS query + * for the given $domain name to your DNS server and returns a list with all + * record values on success. + * + * If the DNS server sends a DNS response message that contains one or more + * records for this query, it will return a list with all record values + * from the response. You can use the `Message::TYPE_*` constants to control + * which type of query will be sent. Note that this method always returns a + * list of record values, but each record value type depends on the query + * type. For example, it returns the IPv4 addresses for type `A` queries, + * the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`, + * `CNAME` and `PTR` queries and structured data for other queries. See also + * the `Record` documentation for more details. + * + * If the DNS server sends a DNS response message that indicates an error + * code, this method will reject with a `RecordNotFoundException`. Its + * message and code can be used to check for the response code. + * + * If the DNS communication fails and the server does not respond with a + * valid response message, this message will reject with an `Exception`. + * + * Pending DNS queries can be cancelled by cancelling its pending promise like so: + * + * ```php + * $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA); + * + * $promise->cancel(); + * ``` + * + * @param string $domain + * @return \React\Promise\PromiseInterface<array,\Exception> + * Resolves with all record values on success or rejects with an Exception on error. + */ + public function resolveAll($domain, $type); +} |