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()); |
|
|
|
|
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()); |
|
|
|
|
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()); |
|
|
|
|
177
|
|
|
$this->assertCount(1, self::findRecord('xn----7sbfndkfpirgcajeli2a4pnc.xn----7sbbfcqfo2cfcagacemif0ap5q', $zone)); |
|
|
|
|
178
|
|
|
$this->assertCount(4, self::findRecord('testmx', $zone)); |
|
|
|
|
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()); |
|
|
|
|
213
|
|
|
$this->assertCount(2, $apl->getExcludedAddressRanges()); |
|
|
|
|
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); |
|
|
|
|
321
|
|
|
$this->assertCount(2, $a4Records); |
|
|
|
|
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()); |
|
|
|
|
327
|
|
|
break; |
328
|
|
|
case CNAME::TYPE: |
329
|
|
|
$this->assertEquals(3600, $rr->getTtl()); |
330
|
|
|
$this->assertEquals('aaaa', $rr->getRdata()->getTarget()); |
|
|
|
|
331
|
|
|
break; |
332
|
|
|
case TXT::TYPE: |
333
|
|
|
$this->assertEquals(3600, $rr->getTtl()); |
334
|
|
|
$this->assertEquals('Mail Exchange IPv6 Address', $rr->getRdata()->getText()); |
|
|
|
|
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()); |
|
|
|
|
344
|
|
|
break; |
345
|
|
|
case TXT::TYPE: |
346
|
|
|
$this->assertEquals(3600, $rr->getTtl()); |
347
|
|
|
$this->assertEquals('This name is silly.', $rr->getRdata()->getText()); |
|
|
|
|
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()); |
|
|
|
|
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()); |
|
|
|
|
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()); |
|
|
|
|
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()); |
|
|
|
|
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()); |
|
|
|
|
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
|
|
|
|
Let’s take a look at an example:
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
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the interface: