using System;
using System.Globalization;
namespace DnsClient
{
///
/// The type is used to normalize and validate domain names and labels.
///
public class DnsString
{
///
/// The ACE prefix indicates that the domain name label contains not normally supported characters and that the label has been endoded.
///
public const string ACEPrefix = "xn--";
///
/// The maximum lenght in bytes for one label.
///
public const int LabelMaxLength = 63;
///
/// The maximum supported total length in bytes for a domain nanme. The calculation of the actual
/// bytes this consumes includes all bytes used for to encode it as octet string.
///
public const int QueryMaxLength = 255;
///
/// The root label ".".
///
public static readonly DnsString RootLabel = new DnsString(".", ".");
internal static readonly IdnMapping IDN = new IdnMapping();
private const char Dot = '.';
private const string DotStr = ".";
///
/// Gets the orginal value.
///
public string Original { get; }
///
/// Gets the validated and eventually modified value.
///
public string Value { get; }
internal DnsString(string original, string value)
{
Original = original;
Value = value;
}
///
/// Performs an implicit conversion from to .
///
/// The name.
///
/// The result of the conversion.
///
public static implicit operator string(DnsString name) => name?.Value;
///
public override string ToString()
{
return Value;
}
///
public override int GetHashCode()
{
return Value.GetHashCode();
}
///
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
return obj.ToString().Equals(Value);
}
// removed as this is actually the wrong label representation (also, doesn't work if there are escaped \. in the value
///////
/////// Returns labels representation of the .
///////
////public IReadOnlyList Labels
////{
//// get
//// {
//// return Value.Split('.').Reverse().Select(p => p + DotStr).ToArray();
//// }
////}
///
/// Parses the given and validates all labels.
///
///
/// An empty string will be interpreted as root label.
///
/// A domain name.
/// The representing the given .
/// If is null.
public static DnsString Parse(string query)
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
int charCount = 0;
int labelCharCount = 0;
int labelsCount = 0;
if (query.Length > 1 && query[0] == Dot)
{
throw new ArgumentException($"'{query}' is not a legal name, found leading root label.", nameof(query));
}
if (query.Length == 0 || (query.Length == 1 && query.Equals(DotStr)))
{
return RootLabel;
}
for (int index = 0; index < query.Length; index++)
{
var c = query[index];
if (c == Dot)
{
if (labelCharCount > LabelMaxLength)
{
throw new ArgumentException($"Label '{labelsCount + 1}' is longer than {LabelMaxLength} bytes.", nameof(query));
}
labelsCount++;
labelCharCount = 0;
}
else
{
labelCharCount++;
charCount++;
if (!(c == '-' || c == '_' ||
c >= 'a' && c <= 'z' ||
c >= 'A' && c <= 'Z' ||
c >= '0' && c <= '9'))
{
try
{
var result = IDN.GetAscii(query);
if (result[result.Length - 1] != Dot)
{
result += Dot;
}
return new DnsString(query, result);
}
catch (Exception ex)
{
throw new ArgumentException($"'{query}' is not a valid hostname.", nameof(query), ex);
}
}
}
}
// check rest
if (labelCharCount > 0)
{
labelsCount++;
// check again label max length
if (labelCharCount > LabelMaxLength)
{
throw new ArgumentException($"Label '{labelsCount}' is longer than {LabelMaxLength} bytes.", nameof(query));
}
}
// octets length length bit per label + 2(start +end)
if (charCount + labelsCount + 1 > QueryMaxLength)
{
throw new ArgumentException($"Octet length of '{query}' exceeds maximum of {QueryMaxLength} bytes.", nameof(query));
}
if (query[query.Length - 1] != Dot)
{
return new DnsString(query, query + Dot);
}
return new DnsString(query, query);
}
///
/// Transforms names with the to the unicode variant and adds a trailing '.' at the end if not present.
/// The original value will be kept in this instance in case it is needed.
///
///
/// The method does not parse the domain name unless it contains a .
///
/// The value to check.
/// The representation.
public static DnsString FromResponseQueryString(string query)
{
if (query.Length == 0 || query[query.Length - 1] != Dot)
{
query += DotStr;
}
if (query.Contains(ACEPrefix))
{
var unicode = IDN.GetUnicode(query);
return new DnsString(unicode, query);
}
return new DnsString(query, query);
}
}
}