Issues (166)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

tests/Parser/ParserTest.php (19 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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
$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
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
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
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
$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
$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
$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
$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
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
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
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
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
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
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
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
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
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
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