From ea87bbb70c9e559233cbcc0cc81a95f840e511ca Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:17:42 +0000 Subject: [PATCH] fix: lowercase questions --- src/DNS/Message/Question.php | 5 ++- src/DNS/Message/Record.php | 15 +++++++++ tests/unit/DNS/Message/QuestionTest.php | 14 +++++++++ tests/unit/DNS/Zone/FileTest.php | 41 +++++++++++++++++++++++++ tests/unit/DNS/ZoneTest.php | 19 ++++++++++++ 5 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/DNS/Message/Question.php b/src/DNS/Message/Question.php index b167b06..4c6c440 100644 --- a/src/DNS/Message/Question.php +++ b/src/DNS/Message/Question.php @@ -6,11 +6,14 @@ final readonly class Question { + public string $name; + public function __construct( - public string $name, + string $name, public int $type, public int $class = Record::CLASS_IN ) { + $this->name = strtolower($name); } /** diff --git a/src/DNS/Message/Record.php b/src/DNS/Message/Record.php index 44137e9..3bab79a 100644 --- a/src/DNS/Message/Record.php +++ b/src/DNS/Message/Record.php @@ -4,6 +4,9 @@ use Utopia\DNS\Exception\DecodingException; +/** + * A DNS record. + */ final readonly class Record { public string $name; @@ -95,6 +98,18 @@ self::TYPE_CAA => 'CAA', ]; + /** + * Creates a DNS record. + * + * @param string $name Domain names are absolute, lowercase and without a trailing dot. + * @param int $type Record type. One of `Record::TYPE_*` constants. + * @param int $class Record class. One of `Record::CLASS_*` constants. Defaults to `Record::CLASS_IN`. + * @param int $ttl Time to live in seconds. Defaults to 0. + * @param string $rdata Record data. + * @param int|null $priority Priority. Used for MX and SRV records. + * @param int|null $weight Weight. Used for SRV records. + * @param int|null $port Port. Used for SRV records. + */ public function __construct( string $name, public int $type, diff --git a/tests/unit/DNS/Message/QuestionTest.php b/tests/unit/DNS/Message/QuestionTest.php index e260b60..9f100bb 100644 --- a/tests/unit/DNS/Message/QuestionTest.php +++ b/tests/unit/DNS/Message/QuestionTest.php @@ -8,6 +8,20 @@ final class QuestionTest extends TestCase { + public function testConstructorSetsName(): void + { + $question = new Question('www.example.com', Record::TYPE_A, Record::CLASS_IN); + + $this->assertSame('www.example.com', $question->name); + } + + public function testConstructorSetsNameCaseInsensitive(): void + { + $question = new Question('WWW.EXAMPLE.COM', Record::TYPE_A, Record::CLASS_IN); + + $this->assertSame('www.example.com', $question->name); + } + public function testEncodeProducesExactBytes(): void { $question = new Question('www.example.com', Record::TYPE_A, Record::CLASS_IN); diff --git a/tests/unit/DNS/Zone/FileTest.php b/tests/unit/DNS/Zone/FileTest.php index 7a01e9b..58ab409 100644 --- a/tests/unit/DNS/Zone/FileTest.php +++ b/tests/unit/DNS/Zone/FileTest.php @@ -525,6 +525,47 @@ public function testImportExportRoundTripForAaaa(): void $this->assertSame($zone->records[0]->rdata, $roundTrip->records[0]->rdata); } + public function testCanExportZoneWithTemplateRecords(): void + { + $soa = new Record( + 'example.com', + Record::TYPE_SOA, + ttl: 3600, + rdata: 'ns1.example.com hostmaster.example.com 1 7200 3600 1209600 300' + ); + $records = [ + new Record('api.example.com', Record::TYPE_A, ttl: 120, rdata: 'a.a.a.a'), + new Record('api.example.com', Record::TYPE_A, ttl: 120, rdata: 'b:b::b:b:b'), + ]; + + $zone = new Zone('example.com', $records, $soa); + $this->assertInstanceOf(Zone::class, $zone); + + $contents = File::export($zone); + + $this->assertStringContainsString('a.a.a.a', $contents); + $this->assertStringContainsString('b:b::b:b:b', $contents); + } + + public function testCanImportZoneWithTemplateRecords(): void + { + $contents = sprintf( + <<<'ZONE' +$ORIGIN example.com. +%s +www 600 IN AAAA b:b::b:b:b +ZONE, + self::DEFAULT_SOA + ); + + $zone = File::import($contents); + + $this->assertInstanceOf(Zone::class, $zone); + $this->assertCount(1, $zone->records); + $this->assertSame('www.example.com', $zone->records[0]->name); + $this->assertSame('b:b::b:b:b', $zone->records[0]->rdata); + } + public function testImportExportRoundTripForCaa(): void { $contents = sprintf( diff --git a/tests/unit/DNS/ZoneTest.php b/tests/unit/DNS/ZoneTest.php index d859943..99216ea 100644 --- a/tests/unit/DNS/ZoneTest.php +++ b/tests/unit/DNS/ZoneTest.php @@ -106,4 +106,23 @@ public function testConstructorAcceptsNestedWildcardRecord(): void $this->assertInstanceOf(Zone::class, $zone); $this->assertCount(2, $zone->records); } + + public function testConstructorAcceptsTemplateRecords(): void + { + $soa = new Record( + 'example.com', + Record::TYPE_SOA, + ttl: 3600, + rdata: 'ns1.example.com hostmaster.example.com 1 7200 3600 1209600 300' + ); + $records = [ + new Record('api.example.com', Record::TYPE_A, ttl: 120, rdata: 'a.a.a.a'), + new Record('api.example.com', Record::TYPE_AAAA, ttl: 120, rdata: 'b:b::b:b:b'), + ]; + + $zone = new Zone('example.com', $records, $soa); + + $this->assertInstanceOf(Zone::class, $zone); + $this->assertCount(2, $zone->records); + } }