mirror of
https://github.com/netchx/netch.git
synced 2026-03-20 18:19:44 +08:00
done
This commit is contained in:
217
Netch/3rd/DnsClient.NET/DnsString.cs
Normal file
217
Netch/3rd/DnsClient.NET/DnsString.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace DnsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="DnsString"/> type is used to normalize and validate domain names and labels.
|
||||
/// </summary>
|
||||
public class DnsString
|
||||
{
|
||||
/// <summary>
|
||||
/// The ACE prefix indicates that the domain name label contains not normally supported characters and that the label has been endoded.
|
||||
/// </summary>
|
||||
public const string ACEPrefix = "xn--";
|
||||
|
||||
/// <summary>
|
||||
/// The maximum lenght in bytes for one label.
|
||||
/// </summary>
|
||||
public const int LabelMaxLength = 63;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum supported total length in bytes for a domain nanme. The calculation of the actual
|
||||
/// bytes this <see cref="DnsString"/> consumes includes all bytes used for to encode it as octet string.
|
||||
/// </summary>
|
||||
public const int QueryMaxLength = 255;
|
||||
|
||||
/// <summary>
|
||||
/// The root label ".".
|
||||
/// </summary>
|
||||
public static readonly DnsString RootLabel = new DnsString(".", ".");
|
||||
|
||||
internal static readonly IdnMapping IDN = new IdnMapping();
|
||||
private const char Dot = '.';
|
||||
private const string DotStr = ".";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the orginal value.
|
||||
/// </summary>
|
||||
public string Original { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the validated and eventually modified value.
|
||||
/// </summary>
|
||||
public string Value { get; }
|
||||
|
||||
internal DnsString(string original, string value)
|
||||
{
|
||||
Original = original;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an implicit conversion from <see cref="DnsString"/> to <see cref="System.String"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>
|
||||
/// The result of the conversion.
|
||||
/// </returns>
|
||||
public static implicit operator string(DnsString name) => name?.Value;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
/////// <summary>
|
||||
/////// Returns labels representation of the <see cref="Value"/>.
|
||||
/////// </summary>
|
||||
////public IReadOnlyList<string> Labels
|
||||
////{
|
||||
//// get
|
||||
//// {
|
||||
//// return Value.Split('.').Reverse().Select(p => p + DotStr).ToArray();
|
||||
//// }
|
||||
////}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the given <paramref name="query"/> and validates all labels.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An empty string will be interpreted as root label.
|
||||
/// </remarks>
|
||||
/// <param name="query">A domain name.</param>
|
||||
/// <returns>The <see cref="DnsString"/> representing the given <paramref name="query"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException">If <paramref name="query"/> is null.</exception>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms names with the <see cref="ACEPrefix"/> 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The method does not parse the domain name unless it contains a <see cref="ACEPrefix"/>.
|
||||
/// </remarks>
|
||||
/// <param name="query">The value to check.</param>
|
||||
/// <returns>The <see cref="DnsString"/> representation.</returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user