mirror of
https://github.com/danog/LibDNSNative.git
synced 2024-11-29 20:19:08 +01:00
First commit
This commit is contained in:
commit
5a3a88d018
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
build
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
vendor
|
||||
.php_cs.cache
|
||||
coverage
|
13
.php_cs.dist
Normal file
13
.php_cs.dist
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
$config = new Amp\CodeStyle\Config();
|
||||
$config->getFinder()
|
||||
->in(__DIR__ . '/examples')
|
||||
->in(__DIR__ . '/lib')
|
||||
->in(__DIR__ . '/test');
|
||||
|
||||
$cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__;
|
||||
|
||||
$config->setCacheFile($cacheDir . '/.php_cs.cache');
|
||||
|
||||
return $config;
|
37
.travis.yml
Normal file
37
.travis.yml
Normal file
@ -0,0 +1,37 @@
|
||||
sudo: false
|
||||
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- nightly
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
fast_finish: true
|
||||
|
||||
|
||||
before_install:
|
||||
- phpenv config-rm xdebug.ini || echo "No xdebug config."
|
||||
|
||||
install:
|
||||
- composer update -n --prefer-dist
|
||||
- wget https://github.com/php-coveralls/php-coveralls/releases/download/v1.0.2/coveralls.phar
|
||||
- chmod +x coveralls.phar
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-text --coverage-clover build/logs/clover.xml
|
||||
- PHP_CS_FIXER_IGNORE_ENV=1 php vendor/bin/php-cs-fixer --diff --dry-run -v fix
|
||||
|
||||
after_script:
|
||||
- ./coveralls.phar -v
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
- $HOME/.php-cs-fixer
|
||||
- $HOME/.local
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2017 amphp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
47
README.md
Normal file
47
README.md
Normal file
@ -0,0 +1,47 @@
|
||||
# LibDNSNative
|
||||
|
||||
[![Build Status](https://img.shields.io/travis/danog/libdnsnative/master.svg?style=flat-square)](https://travis-ci.org/danog/libdnsnative)
|
||||
![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)
|
||||
|
||||
|
||||
Encoder/decoder for the raw format of [PHP's dns_get_record function](https://www.php.net/manual/en/function.dns-get-record.php) based on [libdns](https://github.com/DaveRandom/LibDNS/): allows usage of the function to fetch **all kinds of DNS records**, not just the ones supported by the `DNS_` constants.
|
||||
|
||||
The API consists of a `NativeEncoderFactory` that creates `NativeEncoder` objects, that can encode libdns `Message` objects to a list of parameters that that must be passed to the `dns_get_record` function.
|
||||
|
||||
The `NativeDecoderFactory` creates `NativeDecoder` objects, that accept the results of the `dns_get_record` function and decode them back to `Message` objects.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
composer require danog/libdns-native
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use danog\LibDNSNative\NativeEncoderFactory;
|
||||
use danog\LibDNSNative\NativeDecoderFactory;
|
||||
use LibDNS\Records\QuestionFactory;
|
||||
use LibDNS\Records\ResourceQTypes;
|
||||
use LibDNS\Messages\MessageFactory;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
|
||||
$question = (new QuestionFactory)->create(ResourceQTypes::DNSKEY);
|
||||
$question->setName('daniil.it');
|
||||
|
||||
$message = (new MessageFactory)->create(MessageTypes::QUERY);
|
||||
$records = $message->getQuestionRecords();
|
||||
$records->add($question);
|
||||
|
||||
$encoder = (new NativeEncoderFactory)->create();
|
||||
$question = $encoder->encode($message);
|
||||
|
||||
$result = dns_get_record(...$question);
|
||||
|
||||
$decoder = (new NativeDecoderFactory)->create();
|
||||
$result = $decoder->decode($result, ...$question);
|
||||
```
|
38
appveyor.yml
Normal file
38
appveyor.yml
Normal file
@ -0,0 +1,38 @@
|
||||
build: false
|
||||
shallow_clone: false
|
||||
|
||||
platform:
|
||||
- x86
|
||||
- x64
|
||||
|
||||
clone_folder: c:\projects\libdns
|
||||
|
||||
cache:
|
||||
- c:\tools\php73 -> appveyor.yml
|
||||
|
||||
init:
|
||||
- SET PATH=C:\Program Files\OpenSSL;c:\tools\php73;%PATH%
|
||||
- SET COMPOSER_NO_INTERACTION=1
|
||||
- SET PHP=1
|
||||
- SET ANSICON=121x90 (121x90)
|
||||
|
||||
install:
|
||||
- IF EXIST c:\tools\php73 (SET PHP=0)
|
||||
- IF %PHP%==1 sc config wuauserv start= auto
|
||||
- IF %PHP%==1 net start wuauserv
|
||||
- IF %PHP%==1 cinst -y OpenSSL.Light
|
||||
- IF %PHP%==1 cinst -y php
|
||||
- cd c:\tools\php73
|
||||
- IF %PHP%==1 copy php.ini-production php.ini /Y
|
||||
- IF %PHP%==1 echo date.timezone="UTC" >> php.ini
|
||||
- IF %PHP%==1 echo extension_dir=ext >> php.ini
|
||||
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
|
||||
- IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini
|
||||
- IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini
|
||||
- cd c:\projects\libdns
|
||||
- appveyor DownloadFile https://getcomposer.org/composer.phar
|
||||
- php composer.phar install --prefer-dist --no-progress
|
||||
|
||||
test_script:
|
||||
- cd c:\projects\libdns
|
||||
- vendor/bin/phpunit --colors=always
|
49
composer.json
Normal file
49
composer.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "danog/libdns-native",
|
||||
"homepage": "https://github.com/danog/libdns-natie",
|
||||
"description": "Encoder/decoder for PHP's dns_get_record raw format based on libdns",
|
||||
"keywords": [
|
||||
"dns",
|
||||
"message",
|
||||
"dns_get_record",
|
||||
"php",
|
||||
"native function"
|
||||
],
|
||||
"license": "MIT",
|
||||
"authors": [{
|
||||
"name": "Daniil Gentili",
|
||||
"email": "daniil@daniil.it"
|
||||
},
|
||||
{
|
||||
"name": "Chris Wright",
|
||||
"email": "addr@daverandom.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"daverandom/libdns": "^2.0.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"amphp/php-cs-fixer-config": "dev-master",
|
||||
"phpunit/phpunit": "^6"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"danog\\LibDNSNative\\": "lib"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"danog\\LibDNSNative\\Test\\": "test"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"check": [
|
||||
"@cs",
|
||||
"@test"
|
||||
],
|
||||
"cs": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -v --diff --dry-run",
|
||||
"cs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -v --diff",
|
||||
"test": "@php -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit --coverage-text"
|
||||
}
|
||||
}
|
0
examples/.gitkeep
Normal file
0
examples/.gitkeep
Normal file
26
examples/simple.php
Normal file
26
examples/simple.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
use danog\LibDNSNative\NativeEncoderFactory;
|
||||
use LibDNS\Records\QuestionFactory;
|
||||
use danog\LibDNSNative\NativeDecoderFactory;
|
||||
use LibDNS\Records\ResourceQTypes;
|
||||
use LibDNS\Messages\MessageFactory;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
$question = (new QuestionFactory)->create(ResourceQTypes::DNSKEY);
|
||||
$question->setName('daniil.it');
|
||||
|
||||
$message = (new MessageFactory)->create(MessageTypes::QUERY);
|
||||
$records = $message->getQuestionRecords();
|
||||
$records->add($question);
|
||||
|
||||
$encoder = (new NativeEncoderFactory)->create();
|
||||
$question = $encoder->encode($message);
|
||||
|
||||
$result = dns_get_record(...$question);
|
||||
|
||||
$decoder = (new NativeDecoderFactory)->create();
|
||||
$result = $decoder->decode($result, ...$question);
|
||||
|
||||
var_dump($result);
|
282
lib/NativeDecoder.php
Normal file
282
lib/NativeDecoder.php
Normal file
@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
namespace danog\LibDNSNative;
|
||||
|
||||
use LibDNS\Decoder\Decoder;
|
||||
use LibDNS\Decoder\DecoderFactory;
|
||||
use LibDNS\Encoder\EncodingContext;
|
||||
use LibDNS\Encoder\EncodingContextFactory;
|
||||
use LibDNS\Messages\Message;
|
||||
use LibDNS\Messages\MessageFactory;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
use LibDNS\Packets\PacketFactory;
|
||||
use LibDNS\Records\Question;
|
||||
use LibDNS\Records\QuestionFactory;
|
||||
use LibDNS\Records\Resource;
|
||||
use LibDNS\Records\Types\DomainName;
|
||||
use LibDNS\Records\Types\Type;
|
||||
use LibDNS\Records\Types\TypeBuilder;
|
||||
use LibDNS\Records\Types\Types;
|
||||
|
||||
/**
|
||||
* Decodes JSON DNS strings to Message objects.
|
||||
*
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
*/
|
||||
class NativeDecoder
|
||||
{
|
||||
/**
|
||||
* @var \LibDNS\Packets\PacketFactory
|
||||
*/
|
||||
private $packetFactory;
|
||||
|
||||
/**
|
||||
* @var \LibDNS\Messages\MessageFactory
|
||||
*/
|
||||
private $messageFactory;
|
||||
|
||||
/**
|
||||
* @var \LibDNS\Records\QuestionFactory
|
||||
*/
|
||||
private $questionFactory;
|
||||
|
||||
/**
|
||||
* @var \LibDNS\Records\Types\TypeBuilder
|
||||
*/
|
||||
private $typeBuilder;
|
||||
/**
|
||||
* @var \LibDNS\Decoder\DecoderFactory
|
||||
*/
|
||||
private $decoderFactory;
|
||||
|
||||
/**
|
||||
* Map class names to IDs
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $classMap = [];
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \LibDNS\Packets\PacketFactory $packetFactory
|
||||
* @param \LibDNS\Messages\MessageFactory $messageFactory
|
||||
* @param \LibDNS\Records\QuestionFactory $questionFactory
|
||||
* @param \LibDNS\Records\Types\TypeBuilder $typeBuilder
|
||||
* @param \LibDNS\Encoder\EncodingContextFactory $encodingContextFactory
|
||||
* @param \LibDNS\Decoder\DecoderFactory $decoderFactory
|
||||
* @param bool $allowTrailingData
|
||||
*/
|
||||
public function __construct(
|
||||
PacketFactory $packetFactory,
|
||||
MessageFactory $messageFactory,
|
||||
QuestionFactory $questionFactory,
|
||||
TypeBuilder $typeBuilder,
|
||||
EncodingContextFactory $encodingContextFactory,
|
||||
DecoderFactory $decoderFactory
|
||||
) {
|
||||
$this->packetFactory = $packetFactory;
|
||||
$this->messageFactory = $messageFactory;
|
||||
$this->questionFactory = $questionFactory;
|
||||
$this->typeBuilder = $typeBuilder;
|
||||
$this->encodingContextFactory = $encodingContextFactory;
|
||||
$this->decoderFactory = $decoderFactory;
|
||||
|
||||
$classes = new \ReflectionClass('\\LibDNS\\Records\\ResourceClasses');
|
||||
foreach ($classes->getConstants() as $name => $value) {
|
||||
$this->classMap[$name] = $value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Decode a question record.
|
||||
*
|
||||
*
|
||||
* @return \LibDNS\Records\Question
|
||||
* @throws \UnexpectedValueException When the record is invalid
|
||||
*/
|
||||
private function decodeQuestionRecord(string $name, int $type): Question
|
||||
{
|
||||
/** @var \LibDNS\Records\Types\DomainName $domainName */
|
||||
$domainName = $this->typeBuilder->build(Types::DOMAIN_NAME);
|
||||
$labels = \explode('.', $name);
|
||||
if (!empty($last = \array_pop($labels))) {
|
||||
$labels[] = $last;
|
||||
}
|
||||
$domainName->setLabels($labels);
|
||||
|
||||
$question = $this->questionFactory->create($type);
|
||||
$question->setName($domainName);
|
||||
//$question->setClass($meta['class']);
|
||||
return $question;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a question record
|
||||
*
|
||||
* @param \LibDNS\Encoder\EncodingContext $encodingContext
|
||||
* @param \LibDNS\Records\Question $record
|
||||
*/
|
||||
private function encodeQuestionRecord(EncodingContext $encodingContext, string $name, int $type)
|
||||
{
|
||||
if (!$encodingContext->isTruncated()) {
|
||||
$packet = $encodingContext->getPacket();
|
||||
$record = $this->decodeQuestionRecord($name, $type);
|
||||
$name = $this->encodeDomainName($record->getName(), $encodingContext);
|
||||
$meta = \pack('n*', $record->getType(), $record->getClass());
|
||||
|
||||
if (12 + $packet->getLength()+\strlen($name) + 4 > 512) {
|
||||
$encodingContext->isTruncated(true);
|
||||
} else {
|
||||
$packet->write($name);
|
||||
$packet->write($meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Encode a DomainName field
|
||||
*
|
||||
* @param \LibDNS\Records\Types\DomainName $domainName
|
||||
* @param \LibDNS\Encoder\EncodingContext $encodingContext
|
||||
* @return string
|
||||
*/
|
||||
private function encodeDomainName(DomainName $domainName, EncodingContext $encodingContext): string
|
||||
{
|
||||
$packetIndex = $encodingContext->getPacket()->getLength() + 12;
|
||||
$labelRegistry = $encodingContext->getLabelRegistry();
|
||||
|
||||
$result = '';
|
||||
$labels = $domainName->getLabels();
|
||||
|
||||
if ($encodingContext->useCompression()) {
|
||||
do {
|
||||
$part = \implode('.', $labels);
|
||||
$index = $labelRegistry->lookupIndex($part);
|
||||
|
||||
if ($index === null) {
|
||||
$labelRegistry->register($part, $packetIndex);
|
||||
|
||||
$label = \array_shift($labels);
|
||||
$length = \strlen($label);
|
||||
|
||||
$result .= \chr($length).$label;
|
||||
$packetIndex += $length + 1;
|
||||
} else {
|
||||
$result .= \pack('n', 0b1100000000000000 | $index);
|
||||
break;
|
||||
}
|
||||
} while ($labels);
|
||||
|
||||
if (!$labels) {
|
||||
$result .= "\x00";
|
||||
}
|
||||
} else {
|
||||
foreach ($labels as $label) {
|
||||
$result .= \chr(\strlen($label)).$label;
|
||||
}
|
||||
|
||||
$result .= "\x00";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a resource record
|
||||
*
|
||||
* @param \LibDNS\Encoder\EncodingContext $encodingContext
|
||||
* @param array $record
|
||||
*/
|
||||
private function encodeResourceRecord(EncodingContext $encodingContext, array $record)
|
||||
{
|
||||
if (!$encodingContext->isTruncated()) {
|
||||
/** @var \LibDNS\Records\Types\DomainName $domainName */
|
||||
$domainName = $this->typeBuilder->build(Types::DOMAIN_NAME);
|
||||
$labels = \explode('.', $record['host']);
|
||||
if (!empty($last = \array_pop($labels))) {
|
||||
$labels[] = $last;
|
||||
}
|
||||
$domainName->setLabels($labels);
|
||||
|
||||
$packet = $encodingContext->getPacket();
|
||||
$name = $this->encodeDomainName($domainName, $encodingContext);
|
||||
|
||||
$data = $record['data'];
|
||||
$meta = \pack('n2Nn', $record['type'], $this->classMap[$record['class']], $record['ttl'], \strlen($data));
|
||||
|
||||
if (12 + $packet->getLength()+\strlen($name) + 10+\strlen($data) > 512) {
|
||||
$encodingContext->isTruncated(true);
|
||||
} else {
|
||||
$packet->write($name);
|
||||
$packet->write($meta);
|
||||
$packet->write($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a Message from JSON-encoded string.
|
||||
*
|
||||
* @param array $result The actual response
|
||||
* @param string $domain The domain name that was queried
|
||||
* @param int $type The record type that was queried
|
||||
* @param array $authoritative Authoritative NS results
|
||||
* @param array $additional Additional results
|
||||
* @return \LibDNS\Messages\Message
|
||||
* @throws \UnexpectedValueException When the packet data is invalid
|
||||
* @throws \InvalidArgumentException When a type subtype is unknown
|
||||
*/
|
||||
public function decode(array $result, string $domain, int $type, array $authoritative = null, array $additional = null): Message
|
||||
{
|
||||
$additional = $additional ?? [];
|
||||
$authoritative = $additional ?? [];
|
||||
|
||||
$packet = $this->packetFactory->create();
|
||||
$encodingContext = $this->encodingContextFactory->create($packet, false);
|
||||
$message = $this->messageFactory->create();
|
||||
|
||||
//$message->isAuthoritative(true);
|
||||
$message->setType(MessageTypes::RESPONSE);
|
||||
//$message->setID($requestId);
|
||||
$message->setResponseCode(0);
|
||||
$message->isTruncated(false);
|
||||
$message->isRecursionDesired(false);
|
||||
$message->isRecursionAvailable(false);
|
||||
|
||||
$this->encodeQuestionRecord($encodingContext, $domain, $type);
|
||||
|
||||
$expectedAnswers = count($result);
|
||||
for ($i = 0; $i < $expectedAnswers; $i++) {
|
||||
$this->encodeResourceRecord($encodingContext, $result[$i]);
|
||||
}
|
||||
|
||||
$expectedAuth = count($authoritative);
|
||||
for ($i = 0; $i < $expectedAuth; $i++) {
|
||||
$this->encodeResourceRecord($encodingContext, $authoritative[$i]);
|
||||
}
|
||||
|
||||
$expectedAdditional = count($additional);
|
||||
for ($i = 0; $i < $expectedAdditional; $i++) {
|
||||
$this->encodeResourceRecord($encodingContext, $additional[$i]);
|
||||
}
|
||||
|
||||
$header = [
|
||||
'id' => $message->getID(),
|
||||
'meta' => 0,
|
||||
'qd' => 1,
|
||||
'an' => $expectedAnswers,
|
||||
'ns' => $expectedAuth,
|
||||
'ar' => $expectedAdditional,
|
||||
];
|
||||
|
||||
$header['meta'] |= $message->getType() << 15;
|
||||
$header['meta'] |= $message->getOpCode() << 11;
|
||||
$header['meta'] |= ((int) $message->isAuthoritative()) << 10;
|
||||
$header['meta'] |= ((int) $encodingContext->isTruncated()) << 9;
|
||||
$header['meta'] |= ((int) $message->isRecursionDesired()) << 8;
|
||||
$header['meta'] |= ((int) $message->isRecursionAvailable()) << 7;
|
||||
$header['meta'] |= $message->getResponseCode();
|
||||
|
||||
$data = \pack('n*', $header['id'], $header['meta'], $header['qd'], $header['an'], $header['ns'], $header['ar']).$packet->read($packet->getLength());
|
||||
|
||||
return $this->decoderFactory->create()->decode($data);
|
||||
}
|
||||
}
|
55
lib/NativeDecoderFactory.php
Normal file
55
lib/NativeDecoderFactory.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* Creates NativeDecoder objects.
|
||||
*
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
* @copyright Copyright (c) Chris Wright <https://github.com/DaveRandom>,
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
*/
|
||||
|
||||
namespace danog\LibDNSNative;
|
||||
|
||||
use LibDNS\Decoder\DecodingContextFactory;
|
||||
use \LibDNS\Messages\MessageFactory;
|
||||
use \LibDNS\Packets\PacketFactory;
|
||||
use \LibDNS\Records\QuestionFactory;
|
||||
use \LibDNS\Records\RDataBuilder;
|
||||
use \LibDNS\Records\RDataFactory;
|
||||
use \LibDNS\Records\RecordCollectionFactory;
|
||||
use \LibDNS\Records\ResourceBuilder;
|
||||
use \LibDNS\Records\ResourceFactory;
|
||||
use \LibDNS\Records\TypeDefinitions\FieldDefinitionFactory;
|
||||
use \LibDNS\Records\TypeDefinitions\TypeDefinitionFactory;
|
||||
use \LibDNS\Records\TypeDefinitions\TypeDefinitionManager;
|
||||
use \LibDNS\Records\Types\TypeBuilder;
|
||||
use \LibDNS\Records\Types\TypeFactory;
|
||||
use LibDNS\Decoder\DecoderFactory;
|
||||
use LibDNS\Encoder\EncodingContextFactory;
|
||||
|
||||
/**
|
||||
* Creates NativeDecoder objects.
|
||||
*
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
*/
|
||||
class NativeDecoderFactory
|
||||
{
|
||||
/**
|
||||
* Create a new NativeDecoder object.
|
||||
*
|
||||
* @param \LibDNS\Records\TypeDefinitions\TypeDefinitionManager $typeDefinitionManager
|
||||
* @return NativeDecoder
|
||||
*/
|
||||
public function create(TypeDefinitionManager $typeDefinitionManager = null): NativeDecoder
|
||||
{
|
||||
$typeBuilder = new TypeBuilder(new TypeFactory);
|
||||
|
||||
return new NativeDecoder(
|
||||
new PacketFactory,
|
||||
new MessageFactory(new RecordCollectionFactory),
|
||||
new QuestionFactory,
|
||||
$typeBuilder,
|
||||
new EncodingContextFactory,
|
||||
new DecoderFactory
|
||||
);
|
||||
}
|
||||
}
|
59
lib/NativeEncoder.php
Normal file
59
lib/NativeEncoder.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php declare (strict_types = 1);
|
||||
/**
|
||||
* Encodes Message objects to query strings.
|
||||
*
|
||||
* PHP version 5.4
|
||||
*
|
||||
* @category LibDNS
|
||||
* @package Encoder
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
* @copyright Copyright (c) Chris Wright <https://github.com/DaveRandom>
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 2.0.0
|
||||
*/
|
||||
|
||||
namespace danog\LibDNSNative;
|
||||
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
use \LibDNS\Messages\Message;
|
||||
use LibDNS\Records\ResourceQTypes;
|
||||
|
||||
/**
|
||||
* Encodes Message objects to query strings.
|
||||
*
|
||||
* @category LibDNS
|
||||
* @package Encoder
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
*/
|
||||
class NativeEncoder
|
||||
{
|
||||
/**
|
||||
* Encode a Message to URL payload.
|
||||
*
|
||||
* @param \LibDNS\Messages\Message $message The Message to encode
|
||||
* @return array Array of parameters to pass to the \dns_get_record function
|
||||
*/
|
||||
public function encode(Message $message): array
|
||||
{
|
||||
if ($message->getType() !== MessageTypes::QUERY) {
|
||||
throw new \InvalidArgumentException('Invalid question: is not a question record');
|
||||
}
|
||||
$questions = $message->getQuestionRecords();
|
||||
if ($questions->count() === 0) {
|
||||
throw new \InvalidArgumentException('Invalid question: 0 question records provided');
|
||||
}
|
||||
if ($questions->count() !== 1) {
|
||||
throw new \InvalidArgumentException('Invalid question: only one question record can be provided at a time');
|
||||
}
|
||||
|
||||
$question = $questions->getRecordByIndex(0);
|
||||
|
||||
return [
|
||||
\implode('.', $question->getName()->getLabels()), // Name
|
||||
$question->getType(), // Type
|
||||
null, // Authority records
|
||||
null, // Additional records
|
||||
true, // Raw results
|
||||
];
|
||||
}
|
||||
}
|
35
lib/NativeEncoderFactory.php
Normal file
35
lib/NativeEncoderFactory.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php declare (strict_types = 1);
|
||||
/**
|
||||
* Creates NativeEncoder objects.
|
||||
*
|
||||
* PHP version 5.4
|
||||
*
|
||||
* @category LibDNS
|
||||
* @package Encoder
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
* @copyright Copyright (c) Chris Wright <https://github.com/DaveRandom>
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 2.0.0
|
||||
*/
|
||||
|
||||
namespace danog\LibDNSNative;
|
||||
|
||||
/**
|
||||
* Creates NativeEncoder objects.
|
||||
*
|
||||
* @category LibDNS
|
||||
* @package Encoder
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
*/
|
||||
class NativeEncoderFactory
|
||||
{
|
||||
/**
|
||||
* Create a new Encoder object.
|
||||
*
|
||||
* @return \LibDNS\Encoder\Encoder
|
||||
*/
|
||||
public function create(): NativeEncoder
|
||||
{
|
||||
return new NativeEncoder();
|
||||
}
|
||||
}
|
25
phpunit.xml.dist
Normal file
25
phpunit.xml.dist
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/6.0/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Main">
|
||||
<directory>test</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">lib</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
15
test/NativeDecoderFactoryTest.php
Normal file
15
test/NativeDecoderFactoryTest.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace danog\LibDNSNative\Test;
|
||||
|
||||
use danog\LibDNSNative\NativeDecoder;
|
||||
use danog\LibDNSNative\NativeDecoderFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class NativeDecoderFactoryTest extends TestCase
|
||||
{
|
||||
public function testNativeDecoderFactoryWorks()
|
||||
{
|
||||
$this->assertInstanceOf(NativeDecoder::class, (new NativeDecoderFactory)->create());
|
||||
}
|
||||
}
|
155
test/NativeDecoderTest.php
Normal file
155
test/NativeDecoderTest.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace danog\LibDNSNative\Test;
|
||||
|
||||
use danog\LibDNSNative\NativeDecoderFactory;
|
||||
use LibDNS\Messages\Message;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
use LibDNS\Records\ResourceQTypes;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class NativeDecoderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test decoding of valid Native DNS payloads.
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $requestId
|
||||
* @return void
|
||||
*
|
||||
* @dataProvider provideValidNativePayloads
|
||||
*/
|
||||
public function testDecodesValidNativePayloads(array $result, string $name, int $type, array $auth = null, array $additional = null)
|
||||
{
|
||||
$decoder = (new NativeDecoderFactory)->create();
|
||||
$response = $decoder->decode($result, $name, $type, $auth, $additional);
|
||||
|
||||
$this->assertInstanceOf(Message::class, $response);
|
||||
$this->assertEquals(MessageTypes::RESPONSE, $response->getType());
|
||||
}
|
||||
|
||||
public function provideValidNativePayloads()
|
||||
{
|
||||
return [
|
||||
[
|
||||
[
|
||||
[
|
||||
'host' => 'daniil.it',
|
||||
'class' => 'IN',
|
||||
'ttl' => 3600,
|
||||
'type' => 48,
|
||||
'data' => base64_decode('AQADDaCTEREs+ROIGM0v6ulw671NajD2CIwlsyWjmrvFzRGXqgmCg+Wq9CEXfCql1xSZKplX0bzBj5jNcfHxgGtl4Ug='),
|
||||
],
|
||||
|
||||
[
|
||||
'host' => 'daniil.it',
|
||||
'class' => 'IN',
|
||||
'ttl' => 3600,
|
||||
'type' => 48,
|
||||
'data' => base64_decode('AQEDDZnbLMFMq9wz1td9pjovFfcRElhPI06NHcQo456KSpfhqicaVV3JBwHhfipMS28SC3wy1E9KwCvYlM8tS+d3ihk='),
|
||||
],
|
||||
],
|
||||
'daniil.it',
|
||||
ResourceQTypes::DNSKEY,
|
||||
[
|
||||
],
|
||||
null,
|
||||
],
|
||||
|
||||
[
|
||||
|
||||
[
|
||||
|
||||
[
|
||||
'host' => 'apple.com',
|
||||
'class' => 'IN',
|
||||
'ttl' => 2898,
|
||||
'type' => 1,
|
||||
'data' => base64_decode('EazgLw=='),
|
||||
],
|
||||
|
||||
[
|
||||
'host' => 'apple.com',
|
||||
'class' => 'IN',
|
||||
'ttl' => 2898,
|
||||
'type' => 1,
|
||||
'data' => base64_decode('EY6gOw=='),
|
||||
],
|
||||
|
||||
[
|
||||
'host' => 'apple.com',
|
||||
'class' => 'IN',
|
||||
'ttl' => 2898,
|
||||
'type' => 1,
|
||||
'data' => base64_decode('EbJgOw=='),
|
||||
],
|
||||
],
|
||||
'apple.com',
|
||||
ResourceQTypes::A,
|
||||
[
|
||||
],
|
||||
null,
|
||||
],
|
||||
|
||||
[
|
||||
|
||||
[
|
||||
|
||||
[
|
||||
'host' => 'amphp.org',
|
||||
'class' => 'IN',
|
||||
'ttl' => 166,
|
||||
'type' => 1,
|
||||
'data' => base64_decode('aBgUIg=='),
|
||||
],
|
||||
|
||||
[
|
||||
'host' => 'amphp.org',
|
||||
'class' => 'IN',
|
||||
'ttl' => 166,
|
||||
'type' => 1,
|
||||
'data' => base64_decode('aBgVIg=='),
|
||||
],
|
||||
],
|
||||
'amphp.org',
|
||||
ResourceQTypes::A,
|
||||
|
||||
[
|
||||
],
|
||||
null,
|
||||
],
|
||||
|
||||
[
|
||||
[
|
||||
[
|
||||
'host' => 'tssthacks.daniil.it',
|
||||
'class' => 'IN',
|
||||
'ttl' => 282,
|
||||
'type' => 5,
|
||||
'data' => base64_decode('A2docwxnb29nbGVob3N0ZWQDY29tAA=='),
|
||||
],
|
||||
],
|
||||
'tssthacks.daniil.it',
|
||||
ResourceQTypes::CNAME,
|
||||
null,
|
||||
null,
|
||||
],
|
||||
[
|
||||
[
|
||||
[
|
||||
'host' => 'daniil.it',
|
||||
'class' => 'IN',
|
||||
'ttl' => 289,
|
||||
'type' => 15,
|
||||
'data' => base64_decode('AAoCbXgGeWFuZGV4A25ldAA='),
|
||||
],
|
||||
],
|
||||
'daniil.it',
|
||||
ResourceQTypes::MX,
|
||||
[
|
||||
],
|
||||
null,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
15
test/NativeEncoderFactoryTest.php
Normal file
15
test/NativeEncoderFactoryTest.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace danog\LibDNSNative\Test;
|
||||
|
||||
use danog\LibDNSNative\NativeEncoder;
|
||||
use danog\LibDNSNative\NativeEncoderFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class NativeEncoderFactoryTest extends TestCase
|
||||
{
|
||||
public function testNativeEncoderFactoryWorks()
|
||||
{
|
||||
$this->assertInstanceOf(NativeEncoder::class, (new NativeEncoderFactory)->create());
|
||||
}
|
||||
}
|
50
test/NativeEncoderTest.php
Normal file
50
test/NativeEncoderTest.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace danog\LibDNSNative\Test;
|
||||
|
||||
use danog\LibDNSNative\NativeDecoderFactory;
|
||||
use danog\LibDNSNative\NativeEncoderFactory;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LibDNS\Records\ResourceQTypes;
|
||||
|
||||
class NativeEncoderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test encoding of valid DNS message payloads.
|
||||
*
|
||||
* @param string $message
|
||||
* @return void
|
||||
*
|
||||
* @dataProvider provideValidNativePayloads
|
||||
*/
|
||||
public function testEncodesValidNativePayloads(string $name, int $type)
|
||||
{
|
||||
$decoder = (new NativeDecoderFactory)->create();
|
||||
$response = $decoder->decode([], $name, $type, [], [], [], 0);
|
||||
$response->setType(MessageTypes::QUERY);
|
||||
|
||||
$encoder = (new NativeEncoderFactory)->create();
|
||||
$request = $encoder->encode($response);
|
||||
|
||||
$this->assertInternalType('array', $request, "Got a ".\gettype($request)." instead of an array");
|
||||
$this->assertCount(5, $request);
|
||||
$this->assertInternalType('string', $request[0], "Got a ".\gettype($request[0])." instead of a string");
|
||||
$this->assertInternalType('int', $request[1], "Got a ".\gettype($request[1])." instead of an int");
|
||||
$this->assertEquals($name, $request[0]);
|
||||
$this->assertEquals($type, $request[1]);
|
||||
$this->assertEquals($request[1], $response->getQuestionRecords()->getRecordByIndex(0)->getType());
|
||||
$this->assertEquals($request[0], \implode('.', $response->getQuestionRecords()->getRecordByIndex(0)->getName()->getLabels()));
|
||||
}
|
||||
|
||||
public function provideValidNativePayloads()
|
||||
{
|
||||
return [
|
||||
['apple.com', ResourceQTypes::A],
|
||||
['amphp.org', ResourceQTypes::A],
|
||||
['tssthacks.daniil.it', ResourceQTypes::CNAME],
|
||||
['daniil.it', ResourceQTypes::MX],
|
||||
['daniil.it', ResourceQTypes::DNSKEY],
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user