252 lines
9.0 KiB
C#
252 lines
9.0 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Mail;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using AMWD.Common.Extensions;
|
|
|
|
namespace System
|
|
{
|
|
/// <summary>
|
|
/// String extensions.
|
|
/// </summary>
|
|
#if NET8_0_OR_GREATER
|
|
public static partial class StringExtensions
|
|
#else
|
|
|
|
public static class StringExtensions
|
|
#endif
|
|
{
|
|
/// <summary>
|
|
/// Converts a hex string into a byte array.
|
|
/// </summary>
|
|
/// <param name="hexString">The hex encoded string.</param>
|
|
/// <param name="delimiter">A delimiter between the bytes (e.g. for MAC address).</param>
|
|
/// <returns>The bytes.</returns>
|
|
public static IEnumerable<byte> HexToBytes(this string hexString, string delimiter = "")
|
|
{
|
|
if (string.IsNullOrWhiteSpace(hexString))
|
|
yield break;
|
|
|
|
string str = string.IsNullOrEmpty(delimiter) ? hexString : hexString.Replace(delimiter, "");
|
|
if (str.Length % 2 == 1)
|
|
yield break;
|
|
|
|
#if NET8_0_OR_GREATER
|
|
if (InvalidHexCharRegex().IsMatch(str))
|
|
yield break;
|
|
#else
|
|
if (Regex.IsMatch(str, "[^0-9a-fA-F]"))
|
|
yield break;
|
|
#endif
|
|
|
|
for (int i = 0; i < str.Length; i += 2)
|
|
yield return Convert.ToByte(str.Substring(i, 2), 16);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a byte collection into a hex string.
|
|
/// </summary>
|
|
/// <param name="bytes">The bytes.</param>
|
|
/// <param name="delimiter">A delimiter to set between the bytes (e.g. for MAC address).</param>
|
|
/// <returns>The hex encoded string.</returns>
|
|
public static string BytesToHex(this IEnumerable<byte> bytes, string delimiter = "")
|
|
{
|
|
if (bytes?.Any() != true)
|
|
return null;
|
|
|
|
return string.Join(delimiter, bytes.Select(b => b.ToString("x2")));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encodes a string to the hexadecimal system (base 16).
|
|
/// </summary>
|
|
/// <param name="str">The string to encode hexadecimal.</param>
|
|
/// <param name="encoding">The text encoding to use (default: <see cref="Encoding.Default"/>)</param>
|
|
/// <returns></returns>
|
|
public static string HexEncode(this string str, Encoding encoding = null)
|
|
{
|
|
if (string.IsNullOrEmpty(str))
|
|
return str;
|
|
|
|
return (encoding ?? Encoding.Default).GetBytes(str).BytesToHex();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decodes a string from the hexadecimal system (base 16).
|
|
/// </summary>
|
|
/// <param name="str">The hexadecimal encoded string to decode.</param>
|
|
/// <param name="encoding">The text encoding to use (default: <see cref="Encoding.Default"/>)</param>
|
|
/// <returns></returns>
|
|
public static string HexDecode(this string str, Encoding encoding = null)
|
|
{
|
|
if (string.IsNullOrEmpty(str))
|
|
return str;
|
|
|
|
return (encoding ?? Encoding.Default).GetString(str.HexToBytes().ToArray());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encodes a string to base64.
|
|
/// </summary>
|
|
/// <param name="str">The string to encode with base64.</param>
|
|
/// <param name="encoding">The text encoding to use (default: <see cref="Encoding.Default"/>)</param>
|
|
/// <returns></returns>
|
|
public static string Base64Encode(this string str, Encoding encoding = null)
|
|
=> Convert.ToBase64String((encoding ?? Encoding.Default).GetBytes(str));
|
|
|
|
/// <summary>
|
|
/// Decodes a string from base64.
|
|
/// </summary>
|
|
/// <param name="str">The base64 encoded string to decode.</param>
|
|
/// <param name="encoding">The text encoding to use (default: <see cref="Encoding.Default"/>)</param>
|
|
/// <returns></returns>
|
|
public static string Base64Decode(this string str, Encoding encoding = null)
|
|
=> (encoding ?? Encoding.Default).GetString(Convert.FromBase64String(str));
|
|
|
|
/// <summary>
|
|
/// Replaces the search substring with the replacement when it was found at the beginning of the string.
|
|
/// </summary>
|
|
/// <param name="str">The string.</param>
|
|
/// <param name="search">The searched substring.</param>
|
|
/// <param name="replacement">The replacement.</param>
|
|
/// <returns></returns>
|
|
public static string ReplaceStart(this string str, string search, string replacement)
|
|
{
|
|
if (str.StartsWith(search))
|
|
return replacement + str.Substring(search.Length);
|
|
|
|
return str;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces the search substring with the replacement when it was found at the end of the string.
|
|
/// </summary>
|
|
/// <param name="str">The string.</param>
|
|
/// <param name="search">The searched substring.</param>
|
|
/// <param name="replacement">The replacement.</param>
|
|
/// <returns></returns>
|
|
public static string ReplaceEnd(this string str, string search, string replacement)
|
|
{
|
|
if (str.EndsWith(search))
|
|
return str.Substring(0, str.Length - search.Length) + replacement;
|
|
|
|
return str;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a <see cref="string"/> to a <see cref="decimal"/> using culture "de" or invariant.
|
|
/// </summary>
|
|
/// <param name="decString">The string to parse.</param>
|
|
/// <returns></returns>
|
|
public static decimal ParseDecimal(this string decString)
|
|
{
|
|
int dotIndex = decString.LastIndexOf('.');
|
|
int commaIndex = decString.LastIndexOf(',');
|
|
|
|
var culture = dotIndex < commaIndex ? new CultureInfo("de-DE") : CultureInfo.InvariantCulture;
|
|
|
|
return decimal.Parse(decString, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether the given <see cref="string"/> is a valid <see cref="MailAddress"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// You can enhance the check by requesting the MX record of the domain.
|
|
/// </remarks>
|
|
/// <param name="email">The email address as string to validate.</param>
|
|
/// <param name="checkForDnsRecord">A value indicating whether to resolve a MX record (Google DNS is used).</param>
|
|
/// <returns><c>true</c> when the email address is valid, other wise <c>false</c>.</returns>
|
|
public static bool IsValidEmailAddress(this string email, bool checkForDnsRecord = false)
|
|
=> email.IsValidEmailAddress(checkForDnsRecord ? new[] { new IPEndPoint(IPAddress.Parse("8.8.8.8"), 53) } : null);
|
|
|
|
/// <summary>
|
|
/// Checks whether the given <see cref="string"/> is a valid <see cref="MailAddress"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The check is enhanced by a request for MX records on the defined <paramref name="nameservers"/>.
|
|
/// <br/>
|
|
/// The DNS resolution is only used when the DNS NuGet package is available.
|
|
/// See: https://www.nuget.org/packages/DNS/7.0.0
|
|
/// </remarks>
|
|
/// <param name="emailAddress">The email address as string to validate.</param>
|
|
/// <param name="nameservers">A list of <see cref="IPEndPoint"/>s of nameservers.</param>
|
|
/// <returns><c>true</c> when the email address is valid, other wise <c>false</c>.</returns>
|
|
public static bool IsValidEmailAddress(this string emailAddress, IEnumerable<IPEndPoint> nameservers)
|
|
{
|
|
try
|
|
{
|
|
var mailAddress = new MailAddress(emailAddress);
|
|
bool isValid = mailAddress.Address == emailAddress;
|
|
|
|
if (isValid && nameservers?.Any() == true)
|
|
{
|
|
var dnsClientType = Type.GetType("DNS.Client.DnsClient, DNS") ?? throw new DllNotFoundException("The DNS NuGet package is required: https://www.nuget.org/packages/DNS/7.0.0");
|
|
var recordTypeType = Type.GetType("DNS.Protocol.RecordType, DNS");
|
|
var resolveMethodInfo = dnsClientType.GetMethod("Resolve", [typeof(string), recordTypeType, typeof(CancellationToken)]);
|
|
|
|
bool exists = false;
|
|
foreach (var nameserver in nameservers)
|
|
{
|
|
object dnsClient = Activator.CreateInstance(dnsClientType, [nameserver]);
|
|
|
|
var waitTask = Task.Run(async () => await resolveMethodInfo.InvokeAsync<object>(dnsClient, [mailAddress.Host, 15, CancellationToken.None])); // 15 = MX Record
|
|
waitTask.Wait();
|
|
|
|
object response = waitTask.Result;
|
|
waitTask.Dispose();
|
|
|
|
int responseCode = (int)response.GetType().GetProperty("ResponseCode").GetValue(response);
|
|
if (responseCode != 0)
|
|
continue;
|
|
|
|
object list = response.GetType().GetProperty("AnswerRecords").GetValue(response);
|
|
foreach (object item in (list as IEnumerable))
|
|
{
|
|
int type = (int)item.GetType().GetProperty("Type").GetValue(item);
|
|
if (type == 15)
|
|
{
|
|
exists = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (exists)
|
|
break;
|
|
}
|
|
|
|
isValid &= exists;
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends a copy of the specified string followed by the specified line terminator
|
|
/// to the end of the current <see cref="StringBuilder"/> object.
|
|
/// </summary>
|
|
/// <param name="sb">The <see cref="StringBuilder"/> object.</param>
|
|
/// <param name="value">The string to append.</param>
|
|
/// <param name="newLine">The line terminator.</param>
|
|
/// <returns></returns>
|
|
public static StringBuilder AppendLine(this StringBuilder sb, string value, string newLine)
|
|
=> sb.Append(value).Append(newLine);
|
|
|
|
#if NET8_0_OR_GREATER
|
|
[GeneratedRegex("[^0-9a-fA-F]")]
|
|
private static partial Regex InvalidHexCharRegex();
|
|
#endif
|
|
}
|
|
}
|