diff --git a/src/Ae.Dns.Protocol/Records/DnsResourceRecord.cs b/src/Ae.Dns.Protocol/Records/DnsResourceRecord.cs
index a4cacd5..015da2d 100644
--- a/src/Ae.Dns.Protocol/Records/DnsResourceRecord.cs
+++ b/src/Ae.Dns.Protocol/Records/DnsResourceRecord.cs
@@ -129,14 +129,42 @@ public string ToZone(IDnsZone zone)
///
public void FromZone(IDnsZone zone, string input)
{
- var parts = input.Split(null, 5);
+ var parts = input.Split(Array.Empty(), StringSplitOptions.RemoveEmptyEntries);
- Host = zone.FromFormattedHost(parts[0]);
- TimeToLive = uint.Parse(parts[1]);
- Class = (DnsQueryClass)Enum.Parse(typeof(DnsQueryClass), parts[2]);
- Type = (DnsQueryType)Enum.Parse(typeof(DnsQueryType), parts[3]);
+ var index = 0;
+
+ if (char.IsWhiteSpace(input.First()))
+ {
+ Host = zone.Records.Last().Host;
+ }
+ else
+ {
+ Host = zone.FromFormattedHost(parts[index++]);
+ }
+
+ if (uint.TryParse(parts[index], out var ttl))
+ {
+ TimeToLive = ttl;
+ index++;
+ }
+ else
+ {
+ TimeToLive = (uint)zone.DefaultTtl.TotalSeconds;
+ }
+
+ if (Enum.TryParse(parts[index], out var cl))
+ {
+ Class = cl;
+ index++;
+ }
+ else
+ {
+ Class = zone.Records.Last().Class;
+ }
+
+ Type = (DnsQueryType)Enum.Parse(typeof(DnsQueryType), parts[index++]);
Resource = CreateResourceRecord(Type);
- Resource.FromZone(zone, parts[4]);
+ Resource.FromZone(zone, string.Join(" ", parts.Skip(index)));
}
}
}
diff --git a/src/Ae.Dns.Protocol/Records/DnsSoaResource.cs b/src/Ae.Dns.Protocol/Records/DnsSoaResource.cs
index df67a21..7fb4b3f 100644
--- a/src/Ae.Dns.Protocol/Records/DnsSoaResource.cs
+++ b/src/Ae.Dns.Protocol/Records/DnsSoaResource.cs
@@ -102,7 +102,7 @@ public void FromZone(IDnsZone zone, string input)
MName = parts[0].Trim('.');
RName = parts[1].Trim('.');
- var parts1 = parts[2].Trim(new[] { '(', ')' }).Split(null);
+ var parts1 = parts[2].Trim(new[] { '(', ')' }).Split(Array.Empty(), StringSplitOptions.RemoveEmptyEntries);
Serial = uint.Parse(parts1[0]);
Refresh = TimeSpan.FromSeconds(int.Parse(parts1[1]));
Retry = TimeSpan.FromSeconds(int.Parse(parts1[2]));
diff --git a/src/Ae.Dns.Protocol/Records/DnsTextResource.cs b/src/Ae.Dns.Protocol/Records/DnsTextResource.cs
index 7d64b5a..d9e7aa9 100644
--- a/src/Ae.Dns.Protocol/Records/DnsTextResource.cs
+++ b/src/Ae.Dns.Protocol/Records/DnsTextResource.cs
@@ -1,5 +1,4 @@
using Ae.Dns.Protocol.Zone;
-using System;
using System.Linq;
namespace Ae.Dns.Protocol.Records
diff --git a/src/Ae.Dns.Protocol/Zone/DnsZone.cs b/src/Ae.Dns.Protocol/Zone/DnsZone.cs
index ca0a785..10166a4 100644
--- a/src/Ae.Dns.Protocol/Zone/DnsZone.cs
+++ b/src/Ae.Dns.Protocol/Zone/DnsZone.cs
@@ -1,5 +1,4 @@
-using Ae.Dns.Protocol.Enums;
-using Ae.Dns.Protocol.Records;
+using Ae.Dns.Protocol.Records;
using System;
using System.Collections.Generic;
using System.IO;
@@ -19,6 +18,22 @@ public sealed class DnsZone : IDnsZone
private readonly List _records = new List();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
+ ///
+ /// Construct a new with no records.
+ ///
+ public DnsZone()
+ {
+ }
+
+ ///
+ /// Construct a new with the specified records.
+ ///
+ ///
+ public DnsZone(IEnumerable records)
+ {
+ _records = records.ToList();
+ }
+
///
public IReadOnlyList Records => _records;
@@ -68,9 +83,35 @@ public void DeserializeZone(string zone)
var reader = new StringReader(zone);
+ string? spillage = null;
string? line;
+ bool spillover = false;
while ((line = reader.ReadLine()) != null)
{
+ line = line.Split(';')[0];
+
+ if (line.Contains("(") && !line.Contains(")"))
+ {
+ spillover = true;
+ }
+
+ if (line.Contains(")"))
+ {
+ line = spillage + line;
+ spillover = false;
+ }
+
+ if (spillover)
+ {
+ spillage += line;
+ continue;
+ }
+
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
+
if (line.StartsWith("$ORIGIN"))
{
Origin = line.Substring("$ORIGIN".Length).Trim().Trim('.');
diff --git a/tests/Ae.Dns.Tests/Ae.Dns.Tests.csproj b/tests/Ae.Dns.Tests/Ae.Dns.Tests.csproj
index 6bb66a4..9b70431 100644
--- a/tests/Ae.Dns.Tests/Ae.Dns.Tests.csproj
+++ b/tests/Ae.Dns.Tests/Ae.Dns.Tests.csproj
@@ -50,4 +50,10 @@
+
+
+ PreserveNewest
+
+
+
\ No newline at end of file
diff --git a/tests/Ae.Dns.Tests/Zone/DnsZoneTests.cs b/tests/Ae.Dns.Tests/Zone/DnsZoneTests.cs
index cd34830..95a58d9 100644
--- a/tests/Ae.Dns.Tests/Zone/DnsZoneTests.cs
+++ b/tests/Ae.Dns.Tests/Zone/DnsZoneTests.cs
@@ -2,6 +2,8 @@
using Ae.Dns.Protocol.Records;
using Ae.Dns.Protocol.Zone;
using System;
+using System.IO;
+using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Xunit;
@@ -11,10 +13,9 @@ namespace Ae.Dns.Tests.Zone
[Obsolete]
public sealed class DnsZoneTests
{
- [Fact]
- public async Task TestRoundTripZone()
+ private IDnsZone _wikipediaExampleZone = new DnsZone(new[]
{
- var zone = await RoundTripRecords(new DnsResourceRecord
+ new DnsResourceRecord
{
Class = DnsQueryClass.IN,
Type = DnsQueryType.SOA,
@@ -129,7 +130,17 @@ public async Task TestRoundTripZone()
Host = "mail3.example.com",
TimeToLive = 3600,
Resource = new DnsIpAddressResource { IPAddress = IPAddress.Parse("192.0.2.5") }
- });
+ }
+ })
+ {
+ Origin = "example.com",
+ DefaultTtl = TimeSpan.FromHours(1)
+ };
+
+ [Fact]
+ public async Task TestRoundTripZone()
+ {
+ var zone = await RoundTripRecords(_wikipediaExampleZone.Records.ToArray());
Assert.NotNull(zone);
}
@@ -163,6 +174,26 @@ public async Task TestTxtResource()
Assert.NotNull(zone);
}
+ [Fact]
+ public async Task TestLoadZone1()
+ {
+ var zone = new DnsZone();
+ zone.DeserializeZone(await File.ReadAllTextAsync("Zone/test1.zone"));
+
+ Assert.Equal(zone.Records, _wikipediaExampleZone.Records);
+
+ var serialized = await RoundTripRecords(zone.Records.ToArray());
+
+ Assert.NotNull(serialized);
+ }
+
+ [Fact]
+ public async Task TestLoadZone2()
+ {
+ var zone = new DnsZone();
+ zone.DeserializeZone(await File.ReadAllTextAsync("Zone/test2.zone"));
+ }
+
public async Task RoundTripRecords(params DnsResourceRecord[] records)
{
var originalZone = new DnsZone
diff --git a/tests/Ae.Dns.Tests/Zone/test1.zone b/tests/Ae.Dns.Tests/Zone/test1.zone
new file mode 100644
index 0000000..c2db598
--- /dev/null
+++ b/tests/Ae.Dns.Tests/Zone/test1.zone
@@ -0,0 +1,17 @@
+$ORIGIN example.com. ; designates the start of this zone file in the namespace
+$TTL 3600 ; default expiration time (in seconds) of all RRs without their own TTL value
+example.com. IN SOA ns.example.com. username.example.com. ( 2020091025 7200 3600 1209600 3600 )
+example.com. IN NS ns ; ns.example.com is a nameserver for example.com
+example.com. IN NS ns.somewhere.example. ; ns.somewhere.example is a backup nameserver for example.com
+example.com. IN MX 10 mail.example.com. ; mail.example.com is the mailserver for example.com
+@ IN MX 20 mail2.example.com. ; equivalent to above line, "@" represents zone origin
+@ IN MX 50 mail3 ; equivalent to above line, but using a relative host name
+example.com. IN A 192.0.2.1 ; IPv4 address for example.com
+ IN AAAA 2001:db8:10::1 ; IPv6 address for example.com
+ns IN A 192.0.2.2 ; IPv4 address for ns.example.com
+ IN AAAA 2001:db8:10::2 ; IPv6 address for ns.example.com
+www IN CNAME example.com. ; www.example.com is an alias for example.com
+wwwtest IN CNAME www ; wwwtest.example.com is another alias for www.example.com
+mail IN A 192.0.2.3 ; IPv4 address for mail.example.com
+mail2 IN A 192.0.2.4 ; IPv4 address for mail2.example.com
+mail3 IN A 192.0.2.5 ; IPv4 address for mail3.example.com
\ No newline at end of file
diff --git a/tests/Ae.Dns.Tests/Zone/test2.zone b/tests/Ae.Dns.Tests/Zone/test2.zone
new file mode 100644
index 0000000..cc0309f
--- /dev/null
+++ b/tests/Ae.Dns.Tests/Zone/test2.zone
@@ -0,0 +1,11 @@
+$ORIGIN localhost.
+@ 86400 IN SOA @ root (
+ 1999010100 ; serial
+ 10800 ; refresh (3 hours)
+ 900 ; retry (15 minutes)
+ 604800 ; expire (1 week)
+ 86400 ; minimum (1 day)
+ )
+@ 86400 IN NS @
+@ 86400 IN A 127.0.0.1
+@ 86400 IN AAAA ::1
\ No newline at end of file