Skip to content

Commit 98a3ba5

Browse files
committed
Added RFC 3403-compliant guard clauses to DnsNAPTRRecordData constructor
Signed-off-by: Zafer Balkan <zafer@zaferbalkan.com>
1 parent 90ebeae commit 98a3ba5

File tree

1 file changed

+87
-0
lines changed

1 file changed

+87
-0
lines changed

TechnitiumLibrary.Net/Dns/ResourceRecords/DnsNAPTRRecordData.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ You should have received a copy of the GNU General Public License
2222
using System.IO;
2323
using System.Text;
2424
using System.Text.Json;
25+
using System.Text.RegularExpressions;
2526
using System.Threading.Tasks;
2627
using TechnitiumLibrary.IO;
2728

@@ -46,6 +47,75 @@ public class DnsNAPTRRecordData : DnsResourceRecordData
4647

4748
public DnsNAPTRRecordData(ushort order, ushort preference, string flags, string services, string regexp, string replacement)
4849
{
50+
ArgumentNullException.ThrowIfNull(flags);
51+
52+
ArgumentNullException.ThrowIfNull(services);
53+
54+
ArgumentNullException.ThrowIfNull(regexp);
55+
56+
ArgumentNullException.ThrowIfNull(replacement);
57+
58+
// RFC 3403: REGEXP and REPLACEMENT are mutually exclusive
59+
// If both are present (non-empty), the record is in error and MUST be rejected or ignored.
60+
if (regexp.Length > 0 && replacement.Length > 0)
61+
throw new ArgumentException(
62+
"REGEXP and REPLACEMENT are mutually exclusive per RFC 3403.");
63+
64+
// RFC 3403: FLAGS validation
65+
// Flags are single characters from A–Z and 0–9, case-insensitive.
66+
for (int i = 0; i < flags.Length; i++)
67+
{
68+
char c = flags[i];
69+
if (!(char.IsAsciiLetter(c) || char.IsDigit(c)))
70+
throw new ArgumentException(
71+
$"Invalid NAPTR flag '{c}'. Allowed set is A–Z and 0–9.",
72+
nameof(flags));
73+
}
74+
75+
// RFC 3403: SERVICES is a DNS <character-string>
76+
// RFC intentionally does NOT define semantics here; only basic sanity checks.
77+
// Enforce non-control UTF-16 chars; deeper validation is application-specific.
78+
for (int i = 0; i < services.Length; i++)
79+
{
80+
if (char.IsControl(services[i]))
81+
throw new ArgumentException(
82+
"SERVICES contains control characters, which are not permitted.",
83+
nameof(services));
84+
}
85+
86+
// RFC 3403: REPLACEMENT must be a fully qualified domain name
87+
if (replacement.Length > 0)
88+
{
89+
// Must end with a root label (trailing dot)
90+
if (!replacement.EndsWith(".", StringComparison.Ordinal))
91+
throw new ArgumentException(
92+
"REPLACEMENT must be a fully qualified domain name ending with a dot.",
93+
nameof(replacement));
94+
95+
// No name compression, no empty labels except the root
96+
if (replacement.Contains("..", StringComparison.Ordinal))
97+
throw new ArgumentException(
98+
"REPLACEMENT contains empty DNS labels.",
99+
nameof(replacement));
100+
}
101+
102+
// RFC 3403: REGEXP sanity
103+
// The RFC requires POSIX ERE semantics but does not mandate compile-time validation.
104+
// Enforce only that it is non-empty when used.
105+
if (regexp.Length == 0 && replacement.Length == 0)
106+
throw new ArgumentException(
107+
"Either REGEXP or REPLACEMENT must be specified per RFC 3403.");
108+
109+
// OPTIONAL: Validate regex, not defined in RFC.
110+
// It is a NICE-TO-HAVE, not a MUST.
111+
if (!IsValidRegex(regexp))
112+
{
113+
throw new ArgumentException(
114+
"REGEXP is not a valid regular expression.",
115+
nameof(regexp));
116+
}
117+
118+
// DNS <character-string> constraints
49119
if (DnsClient.IsDomainNameUnicode(replacement))
50120
replacement = DnsClient.ConvertDomainNameToAscii(replacement);
51121

@@ -84,6 +154,23 @@ private void Serialize()
84154
}
85155
}
86156

157+
private bool IsValidRegex(string pattern)
158+
{
159+
if (pattern is null) return false;
160+
try
161+
{
162+
_ = new Regex(
163+
pattern,
164+
RegexOptions.None,
165+
TimeSpan.FromMilliseconds(100));
166+
return true;
167+
}
168+
catch (ArgumentException)
169+
{
170+
return false;
171+
}
172+
}
173+
87174
#endregion
88175

89176
#region protected

0 commit comments

Comments
 (0)