Skip to content

Commit

Permalink
Merge pull request #14 from hostinger/feat/add-dig-lookup-func
Browse files Browse the repository at this point in the history
feat: Add dig lookup functionality
  • Loading branch information
laurynasgadl authored Aug 27, 2024
2 parents 9ad43a2 + 36de378 commit 1ea6303
Show file tree
Hide file tree
Showing 11 changed files with 400 additions and 1 deletion.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
],
"license": "MIT",
"require": {
"php": ">=8.0",
"php": ">=8.1",
"psr/log": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
Expand Down
47 changes: 47 additions & 0 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

use Closure;
use ErrorException;
use Hostinger\Dig\Command\DigCommand;
use Hostinger\Dig\Command\DigOptions;
use Hostinger\Dig\Command\DigQuery;
use Hostinger\Dig\Exceptions\DigExecException;
use Hostinger\Dig\Exceptions\UnsupportedRecordTypeException;
use Hostinger\Dig\RecordType\RecordType;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
Expand Down Expand Up @@ -77,6 +82,48 @@ public function getRecord(string $domain, int $type, string $dnsProvider = '8.8.
return $this->executeDig($domain, $recordType, $dnsProvider, $timeout) ?? ($this->fallback)($domain, $type);
}

/**
* @param string $name
* @param int $type One of the DNS_* constants
* @param DigOptions|null $options
* @param DigQuery|null $query
* @return array
*/
public function lookup(
string $name,
int $type,
?DigOptions $options = null,
?DigQuery $query = null,
): array {
$recordTypeFactory = new RecordTypeFactory();
$recordType = $recordTypeFactory->make($type);

if (is_null($recordType)) {
throw new UnsupportedRecordTypeException($type);
}

$execState = $this->execEnabled();
if ($execState !== true) {
throw new DigExecException($execState);
}

$command = new DigCommand($name, strtoupper($recordType->getType()), $options, $query);
$cliCommand = $command->toCliCommand();

$this->logger->debug('executing dig lookup', [
'name' => $name,
'type' => $recordType->getType(),
'command' => $cliCommand,
]);

exec($cliCommand, $output, $code);
if ($code !== 0) {
throw new DigExecException((string) $code);
}

return $recordType->transform($output);
}

/**
* Use custom error handler to convert dns_get_record() errors to exceptions.
* It throws a warning when the DNS query fails.
Expand Down
33 changes: 33 additions & 0 deletions src/Command/DigCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Hostinger\Dig\Command;

class DigCommand
{
public function __construct(
private readonly string $name,
private readonly string $type,
private readonly ?DigOptions $options = null,
private readonly ?DigQuery $query = null,
) {
}

public function toCliCommand(): string
{
$args = [];
$args[] = escapeshellcmd('dig');

if ($this->options) {
$args = array_merge($args, array_values($this->options->toCliOptions()));
}

if ($this->query) {
$args = array_merge($args, array_values($this->query->toCliQuery()));
}

$args[] = escapeshellarg($this->name);
$args[] = escapeshellarg($this->type);

return join(' ', $args);
}
}
32 changes: 32 additions & 0 deletions src/Command/DigOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Hostinger\Dig\Command;

class DigOptions
{
public function __construct(
public readonly ?string $server = null,
public readonly ?string $name = null,
public readonly ?string $type = null,
) {
}

public function toCliOptions(): array
{
$opts = [];

if ($this->server !== null) {
$opts['server'] = escapeshellarg('@' . $this->server);
}

if ($this->name !== null) {
$opts['name'] = '-q ' . escapeshellarg($this->name);
}

if ($this->type !== null) {
$opts['type'] = '-t ' . escapeshellarg($this->type);
}

return $opts;
}
}
37 changes: 37 additions & 0 deletions src/Command/DigQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Hostinger\Dig\Command;

class DigQuery
{
public function __construct(
public readonly ?bool $all = null,
public readonly ?bool $answer = null,
public readonly ?bool $authority = null,
public readonly ?int $time = null,
) {
}

public function toCliQuery(): array
{
$opts = [];

if ($this->all !== null) {
$opts['all'] = escapeshellarg('+' . ($this->all ? 'all' : 'noall'));
}

if ($this->answer !== null) {
$opts['answer'] = escapeshellarg('+' . ($this->answer ? 'answer' : 'noanswer'));
}

if ($this->authority !== null) {
$opts['authority'] = escapeshellarg('+' . ($this->authority ? 'authority' : 'noauthority'));
}

if ($this->time !== null) {
$opts['time'] = escapeshellarg('+time=' . $this->time);
}

return $opts;
}
}
13 changes: 13 additions & 0 deletions src/Exceptions/DigExecException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Hostinger\Dig\Exceptions;

use RuntimeException;

class DigExecException extends RuntimeException
{
public function __construct(string $reason)
{
parent::__construct("Unable to run dig: $reason");
}
}
13 changes: 13 additions & 0 deletions src/Exceptions/UnsupportedRecordTypeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Hostinger\Dig\Exceptions;

use RuntimeException;

class UnsupportedRecordTypeException extends RuntimeException
{
public function __construct(int $type)
{
parent::__construct("Unsupported record type provided: $type");
}
}
36 changes: 36 additions & 0 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
namespace Hostinger\Dig\Tests;

use Hostinger\Dig\Client;
use Hostinger\Dig\Command\DigOptions;
use Hostinger\Dig\Command\DigQuery;
use Hostinger\Dig\Exceptions\UnsupportedRecordTypeException;
use PHPUnit\Framework\TestCase;

class ClientTest extends TestCase
Expand Down Expand Up @@ -51,4 +54,37 @@ public function testFallbacksToCustom(): void
$this->assertCount(1, $result);
$this->assertEquals('CUSTOM', $result[0]['type']);
}

public function testLookupFailsOnUnsupportedRecordType(): void
{
$this->expectException(UnsupportedRecordTypeException::class);

$client = new Client();
$client->lookup('test.com', 9999);
}

public function testLookup(): void
{
$domain = 'hostinger.com';
$type = DNS_A;

$client = new Client();
$result = $client->lookup(
$domain,
$type,
new DigOptions(
server: '8.8.8.8',
),
new DigQuery(
all: false,
answer: true,
),
);

$expected = dns_get_record($domain, $type);

$this->assertEquals($expected[0]['host'], $result[0]['host']);
$this->assertEquals($expected[0]['class'], $result[0]['class']);
$this->assertEquals($expected[0]['type'], $result[0]['type']);
}
}
69 changes: 69 additions & 0 deletions tests/Command/DigCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Hostinger\Dig\Tests\Command;

use Generator;
use Hostinger\Dig\Command\DigCommand;
use Hostinger\Dig\Command\DigOptions;
use Hostinger\Dig\Command\DigQuery;
use PHPUnit\Framework\TestCase;

class DigCommandTest extends TestCase
{
public static function dataProvider(): Generator
{
yield 'basic' => [
[
'test.com',
'NS',
],
"dig 'test.com' 'NS'",
];

yield 'with options' => [
[
'test.com',
'NS',
new DigOptions(
'ping.com',
),
],
"dig '@ping.com' 'test.com' 'NS'",
];

yield 'with options and query' => [
[
'test.com',
'NS',
new DigOptions(
'ping.com',
),
new DigQuery(
false,
true,
false,
10,
),
],
"dig '@ping.com' '+noall' '+answer' '+noauthority' '+time=10' 'test.com' 'NS'",
];

yield 'escapes args' => [
[
'test.com && exit 1',
'NS && ls -la',
],
"dig 'test.com && exit 1' 'NS && ls -la'",
];
}

/**
* @dataProvider dataProvider
*/
public function testToCliCommand(array $args, string $expected): void
{
$command = new DigCommand(...$args);

$this->assertEquals($expected, $command->toCliCommand());
}
}
56 changes: 56 additions & 0 deletions tests/Command/DigOptionsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace Hostinger\Dig\Tests\Command;

use Generator;
use Hostinger\Dig\Command\DigOptions;
use PHPUnit\Framework\TestCase;

class DigOptionsTest extends TestCase
{
public static function optionsDataProvider(): Generator
{
yield 'empty' => [
[
null,
null,
null,
],
[],
];

yield 'all set' => [
[
'test.com',
'ping.com',
'NS',
],
[
'server' => escapeshellarg('@test.com'),
'name' => '-q ' . escapeshellarg('ping.com'),
'type' => '-t ' . escapeshellarg('NS'),
],
];

yield 'some set' => [
[
null,
null,
'NS',
],
[
'type' => '-t ' . escapeshellarg('NS'),
],
];
}

/**
* @dataProvider optionsDataProvider
*/
public function testToCliOptions(array $args, array $expected): void
{
$query = new DigOptions(...$args);

$this->assertEquals($expected, $query->toCliOptions());
}
}
Loading

0 comments on commit 1ea6303

Please sign in to comment.