From 98a3ba5b1585a7eec4d342ef6e8fc0a90ec82be1 Mon Sep 17 00:00:00 2001 From: Zafer Balkan Date: Tue, 3 Feb 2026 15:12:03 +0200 Subject: [PATCH 1/5] Added RFC 3403-compliant guard clauses to DnsNAPTRRecordData constructor Signed-off-by: Zafer Balkan --- .../Dns/ResourceRecords/DnsNAPTRRecordData.cs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs b/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs index 5f6bc4b3..154958ed 100644 --- a/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs +++ b/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs @@ -22,6 +22,7 @@ You should have received a copy of the GNU General Public License using System.IO; using System.Text; using System.Text.Json; +using System.Text.RegularExpressions; using System.Threading.Tasks; using TechnitiumLibrary.IO; @@ -46,6 +47,75 @@ public class DnsNAPTRRecordData : DnsResourceRecordData public DnsNAPTRRecordData(ushort order, ushort preference, string flags, string services, string regexp, string replacement) { + ArgumentNullException.ThrowIfNull(flags); + + ArgumentNullException.ThrowIfNull(services); + + ArgumentNullException.ThrowIfNull(regexp); + + ArgumentNullException.ThrowIfNull(replacement); + + // RFC 3403: REGEXP and REPLACEMENT are mutually exclusive + // If both are present (non-empty), the record is in error and MUST be rejected or ignored. + if (regexp.Length > 0 && replacement.Length > 0) + throw new ArgumentException( + "REGEXP and REPLACEMENT are mutually exclusive per RFC 3403."); + + // RFC 3403: FLAGS validation + // Flags are single characters from A–Z and 0–9, case-insensitive. + for (int i = 0; i < flags.Length; i++) + { + char c = flags[i]; + if (!(char.IsAsciiLetter(c) || char.IsDigit(c))) + throw new ArgumentException( + $"Invalid NAPTR flag '{c}'. Allowed set is A–Z and 0–9.", + nameof(flags)); + } + + // RFC 3403: SERVICES is a DNS + // RFC intentionally does NOT define semantics here; only basic sanity checks. + // Enforce non-control UTF-16 chars; deeper validation is application-specific. + for (int i = 0; i < services.Length; i++) + { + if (char.IsControl(services[i])) + throw new ArgumentException( + "SERVICES contains control characters, which are not permitted.", + nameof(services)); + } + + // RFC 3403: REPLACEMENT must be a fully qualified domain name + if (replacement.Length > 0) + { + // Must end with a root label (trailing dot) + if (!replacement.EndsWith(".", StringComparison.Ordinal)) + throw new ArgumentException( + "REPLACEMENT must be a fully qualified domain name ending with a dot.", + nameof(replacement)); + + // No name compression, no empty labels except the root + if (replacement.Contains("..", StringComparison.Ordinal)) + throw new ArgumentException( + "REPLACEMENT contains empty DNS labels.", + nameof(replacement)); + } + + // RFC 3403: REGEXP sanity + // The RFC requires POSIX ERE semantics but does not mandate compile-time validation. + // Enforce only that it is non-empty when used. + if (regexp.Length == 0 && replacement.Length == 0) + throw new ArgumentException( + "Either REGEXP or REPLACEMENT must be specified per RFC 3403."); + + // OPTIONAL: Validate regex, not defined in RFC. + // It is a NICE-TO-HAVE, not a MUST. + if (!IsValidRegex(regexp)) + { + throw new ArgumentException( + "REGEXP is not a valid regular expression.", + nameof(regexp)); + } + + // DNS constraints if (DnsClient.IsDomainNameUnicode(replacement)) replacement = DnsClient.ConvertDomainNameToAscii(replacement); @@ -84,6 +154,23 @@ private void Serialize() } } + private bool IsValidRegex(string pattern) + { + if (pattern is null) return false; + try + { + _ = new Regex( + pattern, + RegexOptions.None, + TimeSpan.FromMilliseconds(100)); + return true; + } + catch (ArgumentException) + { + return false; + } + } + #endregion #region protected From 8bdc964e0b46f5c4ab52018c3d51c8ad8c8da91a Mon Sep 17 00:00:00 2001 From: Zafer Balkan Date: Tue, 3 Feb 2026 19:13:49 +0200 Subject: [PATCH 2/5] Added copyright --- TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs b/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs index 154958ed..fce1803a 100644 --- a/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs +++ b/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs @@ -1,6 +1,7 @@ /* Technitium Library Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2026 Zafer Balkan (zafer@zaferbalkan.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From 84661ed58bc63342147f33c9a5ecaca2f1fbad42 Mon Sep 17 00:00:00 2001 From: Zafer Balkan Date: Mon, 9 Feb 2026 11:29:22 +0200 Subject: [PATCH 3/5] Revert "Added copyright" This reverts commit 8bdc964e0b46f5c4ab52018c3d51c8ad8c8da91a. --- TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs b/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs index fce1803a..154958ed 100644 --- a/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs +++ b/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs @@ -1,7 +1,6 @@ /* Technitium Library Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com) -Copyright (C) 2026 Zafer Balkan (zafer@zaferbalkan.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From 0d5a784499de89587d4419a1fa2f8739bd7d7c5d Mon Sep 17 00:00:00 2001 From: Zafer Balkan Date: Mon, 9 Feb 2026 14:21:40 +0200 Subject: [PATCH 4/5] Removed the trailing dot check. --- .../Dns/ResourceRecords/DnsNAPTRRecordData.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs b/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs index 154958ed..a6c9e560 100644 --- a/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs +++ b/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs @@ -66,7 +66,7 @@ public DnsNAPTRRecordData(ushort order, ushort preference, string flags, string for (int i = 0; i < flags.Length; i++) { char c = flags[i]; - if (!(char.IsAsciiLetter(c) || char.IsDigit(c))) + if (!(char.IsAsciiLetter(c) || char.IsAsciiDigit(c))) throw new ArgumentException( $"Invalid NAPTR flag '{c}'. Allowed set is A–Z and 0–9.", nameof(flags)); @@ -87,10 +87,8 @@ public DnsNAPTRRecordData(ushort order, ushort preference, string flags, string if (replacement.Length > 0) { // Must end with a root label (trailing dot) - if (!replacement.EndsWith(".", StringComparison.Ordinal)) - throw new ArgumentException( - "REPLACEMENT must be a fully qualified domain name ending with a dot.", - nameof(replacement)); + // But zoneFile.PopDomainAsync() removes the trailing dot. + // Therefore the check is useless. // No name compression, no empty labels except the root if (replacement.Contains("..", StringComparison.Ordinal)) From 6dcaff3993650003fc5b6a8bdbbb07fb14389b76 Mon Sep 17 00:00:00 2001 From: Zafer Balkan Date: Mon, 9 Feb 2026 14:23:20 +0200 Subject: [PATCH 5/5] Removed non-RFC-specified regex validation --- .../Dns/ResourceRecords/DnsNAPTRRecordData.cs | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs b/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs index a6c9e560..b6b1043c 100644 --- a/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs +++ b/TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs @@ -22,7 +22,6 @@ You should have received a copy of the GNU General Public License using System.IO; using System.Text; using System.Text.Json; -using System.Text.RegularExpressions; using System.Threading.Tasks; using TechnitiumLibrary.IO; @@ -104,15 +103,6 @@ public DnsNAPTRRecordData(ushort order, ushort preference, string flags, string throw new ArgumentException( "Either REGEXP or REPLACEMENT must be specified per RFC 3403."); - // OPTIONAL: Validate regex, not defined in RFC. - // It is a NICE-TO-HAVE, not a MUST. - if (!IsValidRegex(regexp)) - { - throw new ArgumentException( - "REGEXP is not a valid regular expression.", - nameof(regexp)); - } - // DNS constraints if (DnsClient.IsDomainNameUnicode(replacement)) replacement = DnsClient.ConvertDomainNameToAscii(replacement); @@ -152,23 +142,6 @@ private void Serialize() } } - private bool IsValidRegex(string pattern) - { - if (pattern is null) return false; - try - { - _ = new Regex( - pattern, - RegexOptions.None, - TimeSpan.FromMilliseconds(100)); - return true; - } - catch (ArgumentException) - { - return false; - } - } - #endregion #region protected