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); } } }