ParserTest   A
last analyzed

Complexity

Total Complexity 3

Size/Duplication

Total Lines 563
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 3
dl 0
loc 563
rs 10
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A hp$0 ➔ fetch() 0 4 1
B getTestZone() 0 74 1
A testParserCreatesValidDnsObject() 0 14 2
A testParserIgnoresControlEntriesOtherThanTtl() 0 9 1
A testParserCanHandleConvolutedZoneRecord() 0 23 1
A testCanHandlePolymorphicRdata() 0 17 2
A testParserCanHandleAplRecords() 0 13 1
A testParserCanHandleCaaRecords() 0 18 1
A testParserCanHandleSshfpRecords() 0 12 1
A testParserCanHandleUriRecords() 0 12 1
A testMalformedAplRecordThrowsException1() 0 8 1
A testUnknownRdataTypeThrowsException() 0 9 1
A testMalformedAplRecordThrowsException2() 0 8 1
B testAmbiguousRecordsParse() 0 39 8
A testAmbiguousRecord() 0 10 1
A testUnknownRdataTypesAreParsed() 0 29 1
A testParserRecognisesHumanReadableTimeFormats() 0 32 1
A testParserRecognisesResourceNameOnRrsigRecords() 0 46 1
A testParserDoesNotOverwritesZoneNameIfOriginControlEntryIsDifferent() 0 7 1
A findRecord() 0 12 5
A testParserHandlesMultipleOrigins() 0 11 1
A testParserHandlesOriginDot() 0 11 1
A dp_testParserHandlesIncludeDirective() 0 9 1
A testParserHandlesIncludeDirective() 0 18 1
A testIssue89() 0 12 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Badcow DNS Library.
7
 *
8
 * (c) Samuel Williams <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Badcow\DNS\Tests\Parser;
15
16
use Badcow\DNS\Algorithms;
17
use Badcow\DNS\AlignedBuilder;
18
use Badcow\DNS\Classes;
19
use Badcow\DNS\Parser\Comments;
20
use Badcow\DNS\Parser\ParseException;
21
use Badcow\DNS\Parser\Parser;
22
use Badcow\DNS\Parser\TimeFormat;
23
use Badcow\DNS\Parser\ZoneFileFetcherInterface;
24
use Badcow\DNS\Rdata\A;
25
use Badcow\DNS\Rdata\AAAA;
26
use Badcow\DNS\Rdata\APL;
27
use Badcow\DNS\Rdata\CAA;
28
use Badcow\DNS\Rdata\CNAME;
29
use Badcow\DNS\Rdata\Factory;
30
use Badcow\DNS\Rdata\RRSIG;
31
use Badcow\DNS\Rdata\TXT;
32
use Badcow\DNS\ResourceRecord;
33
use Badcow\DNS\Zone;
34
use Badcow\DNS\ZoneBuilder;
35
use PHPUnit\Framework\TestCase;
36
37
class ParserTest extends TestCase
38
{
39
    /**
40
     * Build a test zone.
41
     */
42
    private function getTestZone(): Zone
43
    {
44
        $zone = new Zone('example.com.');
45
        $zone->setDefaultTtl(3600);
46
47
        $soa = new ResourceRecord();
48
        $soa->setName('@');
49
        $soa->setRdata(Factory::SOA(
50
            'example.com.',
51
            'post.example.com.',
52
            2014110501,
53
            3600,
54
            14400,
55
            604800,
56
            3600
57
        ));
58
59
        $ns1 = new ResourceRecord();
60
        $ns1->setName('@');
61
        $ns1->setRdata(Factory::NS('ns1.nameserver.com.'));
62
63
        $ns2 = new ResourceRecord();
64
        $ns2->setName('@');
65
        $ns2->setRdata(Factory::NS('ns2.nameserver.com.'));
66
67
        $a = new ResourceRecord();
68
        $a->setName('sub.domain');
69
        $a->setRdata(Factory::A('192.168.1.42'));
70
        $a->setComment('This is a local ip.');
71
72
        $a6 = new ResourceRecord();
73
        $a6->setName('ipv6.domain');
74
        $a6->setRdata(Factory::AAAA('::1'));
75
        $a6->setComment('This is an IPv6 domain.');
76
77
        $mx1 = new ResourceRecord();
78
        $mx1->setName('@');
79
        $mx1->setRdata(Factory::MX(10, 'mail-gw1.example.net.'));
80
81
        $mx2 = new ResourceRecord();
82
        $mx2->setName('@');
83
        $mx2->setRdata(Factory::MX(20, 'mail-gw2.example.net.'));
84
85
        $mx3 = new ResourceRecord();
86
        $mx3->setName('@');
87
        $mx3->setRdata(Factory::MX(30, 'mail-gw3.example.net.'));
88
89
        $dname = ResourceRecord::create('hq', Factory::Dname('syd.example.com.'));
90
91
        $loc = new ResourceRecord();
92
        $loc->setName('canberra');
93
        $loc->setRdata(Factory::LOC(
94
            -35.3075,   //Lat
95
            149.1244,   //Lon
96
            500,        //Alt
97
            20.12,      //Size
98
            200.3,      //HP
99
            300.1       //VP
100
        ));
101
        $loc->setComment('This is Canberra');
102
103
        $zone->addResourceRecord($soa);
104
        $zone->addResourceRecord($ns1);
105
        $zone->addResourceRecord($ns2);
106
        $zone->addResourceRecord($a);
107
        $zone->addResourceRecord($a6);
108
        $zone->addResourceRecord($dname);
109
        $zone->addResourceRecord($mx1);
110
        $zone->addResourceRecord($mx2);
111
        $zone->addResourceRecord($mx3);
112
        $zone->addResourceRecord($loc);
113
114
        return $zone;
115
    }
116
117
    /**
118
     * Parser creates valid dns object.
119
     *
120
     * @throws ParseException
121
     */
122
    public function testParserCreatesValidDnsObject(): void
123
    {
124
        $zoneBuilder = new AlignedBuilder();
125
        $zone = $zoneBuilder->build($this->getTestZone());
126
127
        $expectation = $this->getTestZone();
128
        foreach ($expectation->getResourceRecords() as $rr) {
129
            $rr->setTtl($rr->getTtl() ?? $expectation->getDefaultTtl());
130
        }
131
132
        $actual = Parser::parse('example.com.', $zone, Comments::END_OF_ENTRY);
133
134
        $this->assertEquals($expectation, $actual);
135
    }
136
137
    /**
138
     * Parser ignores control entries other than TTL.
139
     *
140
     * @throws ParseException|\Exception
141
     */
142
    public function testParserIgnoresControlEntriesOtherThanTtl(): void
143
    {
144
        $file = NormaliserTest::readFile(__DIR__.'/Resources/testCollapseMultilines_sample.txt');
145
        $zone = Parser::parse('example.com.', $file);
146
147
        $this->assertEquals('example.com.', $zone->getName());
148
        $this->assertEquals('::1', self::findRecord('ipv6.domain', $zone)[0]->getRdata()->getAddress());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getAddress() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\A, Badcow\DNS\Rdata\AAAA, Badcow\DNS\Tests\Rdata\a...tests/Rdata/ATest.php$0, Badcow\DNS\Tests\Rdata\a...ts/Rdata/AaaaTest.php$0.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
149
        $this->assertEquals(1337, $zone->getDefaultTtl());
150
    }
151
152
    /**
153
     * Parser can handle convoluted zone record.
154
     *
155
     * @throws ParseException|\Exception
156
     */
157
    public function testParserCanHandleConvolutedZoneRecord(): void
158
    {
159
        $file = NormaliserTest::readFile(__DIR__.'/Resources/testConvolutedZone_sample.txt');
160
        $zone = Parser::parse('example.com.', $file);
161
        $this->assertEquals(3600, $zone->getDefaultTtl());
162
        $this->assertCount(28, $zone->getResourceRecords());
0 ignored issues
show
Documentation introduced by
$zone->getResourceRecords() is of type array<integer,object<Badcow\DNS\ResourceRecord>>, but the function expects a object<Countable>|object...nit\Framework\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
163
164
        $txt = ResourceRecord::create(
165
            'testtxt',
166
            Factory::TXT('v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBg'.
167
                'QDZKI3U+9acu3NfEy0NJHIPydxnPLPpnAJ7k2JdrsLqAK1uouMudHI20pgE8RMldB/TeW'.
168
                'KXYoRidcGCZWXleUzldDTwZAMDQNpdH1uuxym0VhoZpPbI1RXwpgHRTbCk49VqlC'),
169
            600,
170
            Classes::INTERNET
171
        );
172
173
        $txt2 = 'Some text another Some text';
174
175
        $this->assertEquals($txt, self::findRecord($txt->getName(), $zone)[0]);
176
        $this->assertEquals($txt2, self::findRecord('test', $zone)[0]->getRdata()->getText());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getText() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\SPF, Badcow\DNS\Rdata\TXT.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
177
        $this->assertCount(1, self::findRecord('xn----7sbfndkfpirgcajeli2a4pnc.xn----7sbbfcqfo2cfcagacemif0ap5q', $zone));
0 ignored issues
show
Documentation introduced by
self::findRecord('xn----...cagacemif0ap5q', $zone) is of type array<integer,object<Badcow\DNS\ResourceRecord>>, but the function expects a object<Countable>|object...nit\Framework\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
178
        $this->assertCount(4, self::findRecord('testmx', $zone));
0 ignored issues
show
Documentation introduced by
self::findRecord('testmx', $zone) is of type array<integer,object<Badcow\DNS\ResourceRecord>>, but the function expects a object<Countable>|object...nit\Framework\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
179
    }
180
181
    /**
182
     * @throws ParseException
183
     */
184
    public function testCanHandlePolymorphicRdata(): void
185
    {
186
        $string = 'example.com. 7200 IN A6 2001:acad::1337; This is invalid.';
187
        $zone = Parser::parse('example.com.', $string);
188
        $rr = $zone->getResourceRecords()[0];
189
190
        $rdata = $rr->getRdata();
191
192
        $this->assertNotNull($rdata);
193
194
        if (null === $rdata) {
195
            return;
196
        }
197
198
        $this->assertEquals('A6', $rdata->getType());
199
        $this->assertEquals('2001:acad::1337', $rdata->toText());
200
    }
201
202
    /**
203
     * @throws ParseException|\Exception
204
     */
205
    public function testParserCanHandleAplRecords(): void
206
    {
207
        $file = NormaliserTest::readFile(__DIR__.'/Resources/testCollapseMultilines_sample.txt');
208
        $zone = Parser::parse('example.com.', $file);
209
210
        /** @var APL $apl */
211
        $apl = self::findRecord('multicast', $zone)[0]->getRdata();
212
        $this->assertCount(2, $apl->getIncludedAddressRanges());
0 ignored issues
show
Documentation introduced by
$apl->getIncludedAddressRanges() is of type array<integer,object<PhpIP\IPBlock>>, but the function expects a object<Countable>|object...nit\Framework\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
213
        $this->assertCount(2, $apl->getExcludedAddressRanges());
0 ignored issues
show
Documentation introduced by
$apl->getExcludedAddressRanges() is of type array<integer,object<PhpIP\IPBlock>>, but the function expects a object<Countable>|object...nit\Framework\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
214
215
        $this->assertEquals('192.168.0.0/23', (string) $apl->getIncludedAddressRanges()[0]);
216
        $this->assertEquals('2001:acad:1::8/128', (string) $apl->getExcludedAddressRanges()[1]);
217
    }
218
219
    /**
220
     * @throws ParseException
221
     */
222
    public function testParserCanHandleCaaRecords(): void
223
    {
224
        $text = <<<'TXT'
225
$ORIGIN EXAMPLE.COM.
226
$TTL 3600
227
@ 10800 IN CAA 0 issue "letsencrypt.org"
228
TXT;
229
230
        $zone = Parser::parse('example.com.', $text);
231
        $this->assertCount(1, $zone);
232
        /** @var CAA $caa */
233
        $caa = $zone->getResourceRecords()[0]->getRdata();
234
235
        $this->assertEquals('CAA', $caa->getType());
236
        $this->assertEquals(0, $caa->getFlag());
237
        $this->assertEquals('issue', $caa->getTag());
238
        $this->assertEquals('letsencrypt.org', $caa->getValue());
239
    }
240
241
    /**
242
     * @throws ParseException
243
     */
244
    public function testParserCanHandleSshfpRecords(): void
245
    {
246
        $txt = 'host.example. IN SSHFP 2 1 123456789abcdef67890123456789abcdef67890';
247
        $zone = Parser::parse('example.', $txt);
248
249
        $rrs = self::findRecord('host.example.', $zone, 'SSHFP');
250
        $sshfp = $rrs[0]->getRdata();
251
252
        $this->assertEquals(2, $sshfp->getAlgorithm());
253
        $this->assertEquals(1, $sshfp->getFingerprintType());
254
        $this->assertEquals(hex2bin('123456789abcdef67890123456789abcdef67890'), $sshfp->getFingerprint());
255
    }
256
257
    /**
258
     * @throws ParseException
259
     */
260
    public function testParserCanHandleUriRecords(): void
261
    {
262
        $txt = '_ftp._tcp    IN URI 10 1 "ftp://ftp1.example.com/public%20data"';
263
        $zone = Parser::parse('example.com.', $txt);
264
265
        $rrs = self::findRecord('_ftp._tcp', $zone, 'URI');
266
        $uri = $rrs[0]->getRdata();
267
268
        $this->assertEquals(10, $uri->getPriority());
269
        $this->assertEquals(1, $uri->getWeight());
270
        $this->assertEquals('ftp://ftp1.example.com/public%20data', $uri->getTarget());
271
    }
272
273
    /**
274
     * @throws ParseException
275
     */
276
    public function testMalformedAplRecordThrowsException1(): void
277
    {
278
        $zone = 'multicast 3600 IN APL 3:192.168.0.64/30';
279
280
        $this->expectException(ParseException::class);
281
282
        Parser::parse('example.com.', $zone);
283
    }
284
285
    /**
286
     * @throws ParseException
287
     */
288
    public function testUnknownRdataTypeThrowsException(): void
289
    {
290
        $zone = 'resource 3600 IN XX f080:3024:a::1';
291
292
        $this->expectException(ParseException::class);
293
        $this->expectExceptionMessage('Could not parse entry "resource 3600 IN XX f080:3024:a::1".');
294
295
        Parser::parse('acme.com.', $zone);
296
    }
297
298
    /**
299
     * @throws ParseException
300
     */
301
    public function testMalformedAplRecordThrowsException2(): void
302
    {
303
        $zone = 'multicast 3600 IN APL !1-192.168.0.64/30';
304
305
        $this->expectException(ParseException::class);
306
307
        Parser::parse('example.com.', $zone);
308
    }
309
310
    /**
311
     * @throws \Exception|ParseException
312
     */
313
    public function testAmbiguousRecordsParse(): void
314
    {
315
        $file = NormaliserTest::readFile(__DIR__.'/Resources/ambiguous.acme.org.txt');
316
        $zone = Parser::parse('ambiguous.acme.org.', $file);
317
        $mxRecords = self::findRecord('mx', $zone);
318
        $a4Records = self::findRecord('aaaa', $zone);
319
320
        $this->assertCount(3, $mxRecords);
0 ignored issues
show
Documentation introduced by
$mxRecords is of type array<integer,object<Badcow\DNS\ResourceRecord>>, but the function expects a object<Countable>|object...nit\Framework\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
321
        $this->assertCount(2, $a4Records);
0 ignored issues
show
Documentation introduced by
$a4Records is of type array<integer,object<Badcow\DNS\ResourceRecord>>, but the function expects a object<Countable>|object...nit\Framework\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
322
        foreach ($mxRecords as $rr) {
323
            switch ($rr->getType()) {
324
                case A::TYPE:
325
                    $this->assertEquals(900, $rr->getTtl());
326
                    $this->assertEquals('200.100.50.35', $rr->getRdata()->getAddress());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getAddress() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\A, Badcow\DNS\Rdata\AAAA, Badcow\DNS\Tests\Rdata\a...tests/Rdata/ATest.php$0, Badcow\DNS\Tests\Rdata\a...ts/Rdata/AaaaTest.php$0.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
327
                    break;
328
                case CNAME::TYPE:
329
                    $this->assertEquals(3600, $rr->getTtl());
330
                    $this->assertEquals('aaaa', $rr->getRdata()->getTarget());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getTarget() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\CNAME, Badcow\DNS\Rdata\DNAME, Badcow\DNS\Rdata\NS, Badcow\DNS\Rdata\PTR, Badcow\DNS\Rdata\SRV, Badcow\DNS\Rdata\URI.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
331
                    break;
332
                case TXT::TYPE:
333
                    $this->assertEquals(3600, $rr->getTtl());
334
                    $this->assertEquals('Mail Exchange IPv6 Address', $rr->getRdata()->getText());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getText() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\SPF, Badcow\DNS\Rdata\TXT.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
335
                    break;
336
            }
337
        }
338
339
        foreach ($a4Records as $rr) {
340
            switch ($rr->getType()) {
341
                case AAAA::TYPE:
342
                    $this->assertEquals(900, $rr->getTtl());
343
                    $this->assertEquals('2001:acdc:5889::35', $rr->getRdata()->getAddress());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getAddress() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\A, Badcow\DNS\Rdata\AAAA, Badcow\DNS\Tests\Rdata\a...tests/Rdata/ATest.php$0, Badcow\DNS\Tests\Rdata\a...ts/Rdata/AaaaTest.php$0.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
344
                    break;
345
                case TXT::TYPE:
346
                    $this->assertEquals(3600, $rr->getTtl());
347
                    $this->assertEquals('This name is silly.', $rr->getRdata()->getText());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getText() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\SPF, Badcow\DNS\Rdata\TXT.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
348
                    break;
349
            }
350
        }
351
    }
352
353
    /**
354
     * @throws ParseException
355
     */
356
    public function testAmbiguousRecord(): void
357
    {
358
        $record = 'mx cname aaaa';
359
        $zone = Parser::parse('acme.com.', $record);
360
        $mx = $zone->getResourceRecords()[0];
361
362
        $this->assertEquals(CNAME::TYPE, $mx->getType());
363
        $this->assertEquals('mx', $mx->getName());
364
        $this->assertEquals('aaaa', $mx->getRdata()->getTarget());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getTarget() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\CNAME, Badcow\DNS\Rdata\DNAME, Badcow\DNS\Rdata\NS, Badcow\DNS\Rdata\PTR, Badcow\DNS\Rdata\SRV, Badcow\DNS\Rdata\URI.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
365
    }
366
367
    /**
368
     * @throws ParseException
369
     */
370
    public function testUnknownRdataTypesAreParsed(): void
371
    {
372
        $entries = <<<DNS
373
a.example.com.   CLASS32     TYPE731         \# 6 abcd   ef 01 23 45
374
b.example.com.   HS          TYPE62347       \# 0
375
c.example.com.   IN          A               \# 4 0A000001
376
d.example.com.   CLASS1      TYPE1           \# 4 0A 00 00 02
377
DNS;
378
379
        $zone = Parser::parse('example.com.', $entries);
380
        $this->assertCount(4, $zone);
381
382
        $a = self::findRecord('a.example.com.', $zone)[0];
383
        $this->assertEquals(731, $a->getRdata()->getTypeCode());
384
        $this->assertEquals(hex2bin('abcdef012345'), $a->getRdata()->getData());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getData() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\PolymorphicRdata, Badcow\DNS\Rdata\UnknownType.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
385
        $this->assertEquals('CLASS32', $a->getClass());
386
387
        $b = self::findRecord('b.example.com.', $zone)[0];
388
        $this->assertEquals(62347, $b->getRdata()->getTypeCode());
389
        $this->assertEquals(null, $b->getRdata()->getData());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getData() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\PolymorphicRdata, Badcow\DNS\Rdata\UnknownType.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
390
391
        $c = self::findRecord('c.example.com.', $zone)[0];
392
        $this->assertInstanceOf(A::class, $c->getRdata());
393
        $this->assertEquals('10.0.0.1', $c->getRdata()->getAddress());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getAddress() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\A, Badcow\DNS\Rdata\AAAA, Badcow\DNS\Tests\Rdata\a...tests/Rdata/ATest.php$0, Badcow\DNS\Tests\Rdata\a...ts/Rdata/AaaaTest.php$0.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
394
395
        $d = self::findRecord('d.example.com.', $zone)[0];
396
        $this->assertInstanceOf(A::class, $d->getRdata());
397
        $this->assertEquals('10.0.0.2', $d->getRdata()->getAddress());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Badcow\DNS\Rdata\RdataInterface as the method getAddress() does only exist in the following implementations of said interface: Badcow\DNS\Rdata\A, Badcow\DNS\Rdata\AAAA, Badcow\DNS\Tests\Rdata\a...tests/Rdata/ATest.php$0, Badcow\DNS\Tests\Rdata\a...ts/Rdata/AaaaTest.php$0.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
398
    }
399
400
    /**
401
     * @throws ParseException
402
     */
403
    public function testParserRecognisesHumanReadableTimeFormats(): void
404
    {
405
        $record = <<<DNS
406
\$TTL 1h1m3s
407
badcow.co.     1h5m IN SOA   ns.badcow.co. hostmaster.badcow.co. (
408
                             2020070101 ; serial
409
                             3h10s      ; refresh
410
                             59m        ; retry
411
                             4w1d       ; expire
412
                             1h         ; minimum
413
                             )
414
overflow      615000000w IN A     4.3.2.1
415
numeric       12345 IN A     9.9.9.9
416
DNS;
417
        $zone = Parser::parse('badcow.co.', $record);
418
        $this->assertEquals(3663, $zone->getDefaultTtl());
419
        $this->assertCount(3, $zone);
420
421
        $this->assertEquals(3900, $zone[0]->getTtl());
422
        $soa = $zone[0]->getRdata();
423
        $this->assertEquals(10810, $soa->getRefresh());
424
        $this->assertEquals(3540, $soa->getRetry());
425
        $this->assertEquals(2505600, $soa->getExpire());
426
        $this->assertEquals(3600, $soa->getMinimum());
427
428
        $this->assertEquals(0, $zone[1]->getTtl());
429
        $this->assertEquals(12345, $zone[2]->getTtl());
430
431
        // Ensure coverage
432
        $this->assertEquals('1w4d13h46m39s', TimeFormat::toHumanReadable(999999));
433
        $this->assertEquals('1h', TimeFormat::toHumanReadable(3600));
434
    }
435
436
    /**
437
     * @throws ParseException
438
     */
439
    public function testParserRecognisesResourceNameOnRrsigRecords(): void
440
    {
441
        $record = <<<DNS
442
dns.badcow.co. 3600 IN SOA   ns.badcow.co. hostmaster.badcow.co. (
443
                             2020010101 ; serial
444
                             10800      ; refresh (3 hours)
445
                             3600       ; retry (1 hour)
446
                             2419200    ; expire (4 weeks)
447
                             3600       ; minimum (1 hour)
448
                             )
449
               3600    RRSIG A 4 2 86400 (
450
                             20050322173103 20030220173103 2642 example.com.
451
                             oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip
452
                             8WTrPYGv07h108dUKGMeDPKijVCHX3DD
453
                             Kdfb+v6oB9wfuh3DTJXUAfI/M0zmO/zz
454
                             8bW0Rznl8O3tGNazPwQKkRN20XPXV6nw
455
                             wfoXmJQbsLNrLfkGJ5D6fwFm8nN+6pBz
456
                             eDQfsS3Ap3o= )
457
DNS;
458
459
        $expectedSignature = base64_decode('oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTrPYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6oB9wfuh3DTJXUAfI/'.
460
            'M0zmO/zz8bW0Rznl8O3tGNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkGJ5D6fwFm8nN+6pBzeDQfsS3Ap3o=');
461
        $expectedExpiration = \DateTime::createFromFormat(RRSIG::TIME_FORMAT, '20050322173103');
462
        $expectedInception = \DateTime::createFromFormat(RRSIG::TIME_FORMAT, '20030220173103');
463
464
        $zone = Parser::parse('badcow.co.', $record);
465
466
        $this->assertCount(2, $zone);
467
        /** @var ResourceRecord $rr */
468
        $rr = $zone[1];
469
470
        $this->assertEquals('dns.badcow.co.', $rr->getName());
471
        $this->assertEquals(3600, $rr->getTtl());
472
        /** @var RRSIG $rrsig */
473
        $rrsig = $rr->getRdata();
474
        $this->assertInstanceOf(RRSIG::class, $rrsig);
475
        $this->assertEquals('A', $rrsig->getTypeCovered());
476
        $this->assertEquals(Algorithms::ECC, $rrsig->getAlgorithm());
477
        $this->assertEquals(2, $rrsig->getLabels());
478
        $this->assertEquals(86400, $rrsig->getOriginalTtl());
479
        $this->assertEquals($expectedExpiration, $rrsig->getSignatureExpiration());
480
        $this->assertEquals($expectedInception, $rrsig->getSignatureInception());
481
        $this->assertEquals(2642, $rrsig->getKeyTag());
482
        $this->assertEquals('example.com.', $rrsig->getSignersName());
483
        $this->assertEquals($expectedSignature, $rrsig->getSignature());
484
    }
485
486
    /**
487
     * Tests if a control entry on a zone file will overwrite the initial parameter in Parser::parse().
488
     *
489
     * @throws \Exception
490
     */
491
    public function testParserDoesNotOverwritesZoneNameIfOriginControlEntryIsDifferent(): void
492
    {
493
        $file = NormaliserTest::readFile(__DIR__.'/Resources/testCollapseMultilines_sample.txt');
494
        $zone = Parser::parse('test.com.', $file);
495
496
        $this->assertEquals('test.com.', $zone->getName());
497
    }
498
499
    /**
500
     * Find all records in a Zone named $name.
501
     *
502
     * @return ResourceRecord[]
503
     */
504
    public static function findRecord(?string $name, Zone $zone, ?string $type = 'ANY'): array
505
    {
506
        $records = [];
507
508
        foreach ($zone->getResourceRecords() as $resourceRecord) {
509
            if ($name === $resourceRecord->getName() && ('ANY' === $type || $type === $resourceRecord->getType())) {
510
                $records[] = $resourceRecord;
511
            }
512
        }
513
514
        return $records;
515
    }
516
517
    /**
518
     * Parser handles multiple $ORIGINS.
519
     *
520
     * @throws ParseException|\Exception
521
     */
522
    public function testParserHandlesMultipleOrigins(): void
523
    {
524
        $file = NormaliserTest::readFile(__DIR__.'/Resources/multipleOrigins.txt');
525
        $expectation = NormaliserTest::readFile(__DIR__.'/Resources/multipleOrigins_expectation.txt');
526
        $zone = Parser::parse('mydomain.biz.', $file);
527
528
        $this->assertEquals('mydomain.biz.', $zone->getName());
529
        $this->assertEquals(3600, $zone->getDefaultTtl());
530
531
        $this->assertEquals($expectation, ZoneBuilder::build($zone));
532
    }
533
534
    /**
535
     * Parser handles $ORIGIN . correctly.
536
     *
537
     * @throws ParseException|\Exception
538
     */
539
    public function testParserHandlesOriginDot(): void
540
    {
541
        $file = NormaliserTest::readFile(__DIR__.'/Resources/testOriginDot_sample.txt');
542
        $expectation = NormaliserTest::readfile(__DIR__.'/Resources/testOriginDot_expectation.txt');
543
544
        $zone = Parser::parse('otherdomain.biz.', $file);
545
        $this->assertEquals('otherdomain.biz.', $zone->getName());
546
547
        ZoneBuilder::fillOutZone($zone);
548
        $this->assertEquals($expectation, ZoneBuilder::build($zone));
549
    }
550
551
    public function dp_testParserHandlesIncludeDirective(): array
552
    {
553
        $baseDir = __DIR__.'/Resources/IncludeControlEntryTests/';
554
555
        return [
556
            ['mydomain.biz.', 3600, $baseDir.'mydomain.biz.db', $baseDir.'mydomain.biz_expectation.db', Comments::ALL],
557
            ['testdomain.geek.', 7200, $baseDir.'testdomain.geek.db', $baseDir.'testdomain.geek_expectation.db', Comments::NONE],
558
        ];
559
    }
560
561
    /**
562
     * Parser imports files specified by the $INCLUDE directive.
563
     *
564
     * @dataProvider dp_testParserHandlesIncludeDirective
565
     *
566
     * @throws ParseException|\Exception
567
     */
568
    public function testParserHandlesIncludeDirective(string $zoneName, int $ttl, string $zoneFilePath, string $expectationPath, int $commentOptions): void
569
    {
570
        $zoneFetcher = new class() implements ZoneFileFetcherInterface {
571
            public function fetch(string $path): string
572
            {
573
                return file_get_contents(__DIR__.'/Resources/IncludeControlEntryTests/'.$path);
574
            }
575
        };
576
577
        $file = NormaliserTest::readFile($zoneFilePath);
578
        $expectation = NormaliserTest::readFile($expectationPath);
579
        $zone = (new Parser([], $zoneFetcher))->makeZone($zoneName, $file, $commentOptions);
580
581
        $this->assertEquals($zoneName, $zone->getName());
582
        $this->assertEquals($ttl, $zone->getDefaultTtl());
583
584
        $this->assertEquals($expectation, ZoneBuilder::build($zone));
585
    }
586
587
    public function testIssue89(): void
588
    {
589
        $zone = Parser::parse('tld.', "\$ORIGIN tld.\nzone.tld. 900 IN TXT 3600");
590
591
        $this->assertCount(1, $zone);
592
        $rr = $zone[0];
593
594
        $this->assertEquals('zone.tld.', $rr->getName());
595
        $this->assertEquals(900, $rr->getTtl());
596
        $this->assertEquals(Classes::INTERNET, $rr->getClass());
597
        $this->assertEquals('3600', $rr->getRdata()->getText());
598
    }
599
}
600