diff --git a/src/ObjectValidation CountryValidator/README.md b/src/ObjectValidation CountryValidator/README.md index a808e08..7ad4418 100644 --- a/src/ObjectValidation CountryValidator/README.md +++ b/src/ObjectValidation CountryValidator/README.md @@ -24,6 +24,7 @@ enumerable lengths - XRechnung routing validation - European VAT ID validation - XML validation +- Hostname or IP address validation - Conditional value requirements - Enumeration value validation - Validation references @@ -81,6 +82,7 @@ The **ObjectValidation-CountryValidator** extension is licensed using the | XRechnung routing validation | `XRechnungRouteAttribute` | | European VAT ID validation | `EuVatIdAttribute` | | XML validation | `XmlAttribute` | +| Hostname or IP address validation | `HostAttribute` | | Conditional value requirement | `RequiredIfAttribute` | | Allowed/denied values | `AllowedValuesAttribute`, `DeniedValuesAttribute` | | Enumeration value | (none - using the type) | @@ -298,6 +300,7 @@ These item validation adapters exist: | `CompareAttribute` | `ItemCompareAttribute` | | `CreditCardAttribute` | `ItemCreditCardAttribute` | | `EmailAddressAttribute` | `ItemEmailAddressAttribute` | +| `HostAttribute` | `ItemHostAttribute` | | `MaxLengthAttribute` | `ItemMaxLengthAttribute` | | `MinLengthAttribute` | `ItemMinLengthAttribute` | | `NoValidationAttribute` | `ItemNoValidationAttribute` | diff --git a/src/ObjectValidation/AbaValidation.cs b/src/ObjectValidation/AbaValidation.cs index 93535ab..f9eca72 100644 --- a/src/ObjectValidation/AbaValidation.cs +++ b/src/ObjectValidation/AbaValidation.cs @@ -93,25 +93,25 @@ public static AbaFormats GetAbaFormat(string aba) /// /// MICR format normalizing regular expression /// - [GeneratedRegex(@"[^\d]", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"[^\d]", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex NormalizingMicr_Generated(); /// /// Fraction format normalizing regular expression /// - [GeneratedRegex(@"[^\d|\-|/]", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"[^\d|\-|/]", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex NormalizingFraction_Generated(); /// /// MICR syntax validating regular expression ($1 is the FED routing symbol, $2 the ABA institution identifier, and $3 the check digit) /// - [GeneratedRegex(@"^(\d{4})(\d{4})(\d)$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(\d{4})(\d{4})(\d)$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex MicrSyntax_Generated(); /// /// Fraction syntax validating regular expression ($1 is the ABA prefix, $2 the ABA institution identifier, and $3 the FED routing symbol) /// - [GeneratedRegex(@"^(\d{1,2})\-(\d{4})/?(\d{4})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(\d{1,2})\-(\d{4})/?(\d{4})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex FractionSyntax_Generated(); } } diff --git a/src/ObjectValidation/CountryCodes.cs b/src/ObjectValidation/CountryCodes.cs index 2280267..3ce1542 100644 --- a/src/ObjectValidation/CountryCodes.cs +++ b/src/ObjectValidation/CountryCodes.cs @@ -1,6 +1,4 @@ -using System.Collections.Frozen; - -namespace wan24.ObjectValidation +namespace wan24.ObjectValidation { /// /// Country ISO 3166-1 alpha-2 codes diff --git a/src/ObjectValidation/CurrencyCodes.cs b/src/ObjectValidation/CurrencyCodes.cs index 82c2b57..e3559b9 100644 --- a/src/ObjectValidation/CurrencyCodes.cs +++ b/src/ObjectValidation/CurrencyCodes.cs @@ -1,6 +1,4 @@ -using System.Collections.Frozen; - -namespace wan24.ObjectValidation +namespace wan24.ObjectValidation { /// /// Currency ISO 4217 codes diff --git a/src/ObjectValidation/EuVatId.cs b/src/ObjectValidation/EuVatId.cs index 71842d7..6a1b2ae 100644 --- a/src/ObjectValidation/EuVatId.cs +++ b/src/ObjectValidation/EuVatId.cs @@ -65,175 +65,175 @@ public static partial class EuVatId /// /// VAT ID syntax regular expression for AT /// - [GeneratedRegex(@"^(ATU)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(ATU)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex AT_Generated(); /// /// VAT ID syntax regular expression for BE /// - [GeneratedRegex(@"^(BE)(\d{10})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(BE)(\d{10})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex BE_Generated(); /// /// VAT ID syntax regular expression for BG /// - [GeneratedRegex(@"^(BG)(\d{9,10})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(BG)(\d{9,10})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex BG_Generated(); /// /// VAT ID syntax regular expression for CY /// - [GeneratedRegex(@"^(CY)(\d{8}[A-Z])$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(CY)(\d{8}[A-Z])$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex CY_Generated(); /// /// VAT ID syntax regular expression for CZ /// - [GeneratedRegex(@"^(CZ)(\d{8,10})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(CZ)(\d{8,10})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex CZ_Generated(); /// /// VAT ID syntax regular expression for DE /// - [GeneratedRegex(@"^(DE)(\d{9})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(DE)(\d{9})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex DE_Generated(); /// /// VAT ID syntax regular expression for DK /// - [GeneratedRegex(@"^(DK)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(DK)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex DK_Generated(); /// /// VAT ID syntax regular expression for EE /// - [GeneratedRegex(@"^(EE)(\d{9})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(EE)(\d{9})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex EE_Generated(); /// /// VAT ID syntax regular expression for ES /// - [GeneratedRegex(@"^(ES)([A-Z|\d]\d{7}[A-Z|\d])$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(ES)([A-Z|\d]\d{7}[A-Z|\d])$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex ES_Generated(); /// /// VAT ID syntax regular expression for FI /// - [GeneratedRegex(@"^(FI)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(FI)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex FI_Generated(); /// /// VAT ID syntax regular expression for FR /// - [GeneratedRegex(@"^(FR)([A-Z|\d]{2}\d{9})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(FR)([A-Z|\d]{2}\d{9})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex FR_Generated(); /// /// VAT ID syntax regular expression for GR /// - [GeneratedRegex(@"^(GR)(\d{9})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(GR)(\d{9})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex GR_Generated(); /// /// VAT ID syntax regular expression for HR /// - [GeneratedRegex(@"^(HR)(\d{11})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(HR)(\d{11})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex HR_Generated(); /// /// VAT ID syntax regular expression for HU /// - [GeneratedRegex(@"^(HU)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(HU)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex HU_Generated(); /// /// VAT ID syntax regular expression for IE /// - [GeneratedRegex(@"^(IE)((\d[A-Z|\d]\d{5}[A-Z])|(\d{7}[A-W][A-I]))$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(IE)((\d[A-Z|\d]\d{5}[A-Z])|(\d{7}[A-W][A-I]))$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex IE_Generated(); /// /// VAT ID syntax regular expression for IT /// - [GeneratedRegex(@"^(IT)(\d{11})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(IT)(\d{11})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex IT_Generated(); /// /// VAT ID syntax regular expression for LT /// - [GeneratedRegex(@"^(LT)(\d{9}|\d{12})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(LT)(\d{9}|\d{12})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex LT_Generated(); /// /// VAT ID syntax regular expression for LU /// - [GeneratedRegex(@"^(LU)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(LU)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex LU_Generated(); /// /// VAT ID syntax regular expression for LV /// - [GeneratedRegex(@"^(LV)(\d{11})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(LV)(\d{11})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex LV_Generated(); /// /// VAT ID syntax regular expression for MT /// - [GeneratedRegex(@"^(MT)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(MT)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex MT_Generated(); /// /// VAT ID syntax regular expression for NL /// - [GeneratedRegex(@"^(NL)([\d|A-Z|\+|\*]{12})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(NL)([\d|A-Z|\+|\*]{12})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex NL_Generated(); /// /// VAT ID syntax regular expression for PL /// - [GeneratedRegex(@"^(PL)(\d{10})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(PL)(\d{10})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex PL_Generated(); /// /// VAT ID syntax regular expression for PT /// - [GeneratedRegex(@"^(PT)(\d{9})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(PT)(\d{9})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex PT_Generated(); /// /// VAT ID syntax regular expression for RO /// - [GeneratedRegex(@"^(RO)([1-9]\d{0,9})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(RO)([1-9]\d{0,9})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex RO_Generated(); /// /// VAT ID syntax regular expression for SE /// - [GeneratedRegex(@"^(SE)(\d{10}01)$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(SE)(\d{10}01)$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex SE_Generated(); /// /// VAT ID syntax regular expression for SI /// - [GeneratedRegex(@"^(SI)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(SI)(\d{8})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex SI_Generated(); /// /// VAT ID syntax regular expression for SK /// - [GeneratedRegex(@"^(SK)(\d{10})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(SK)(\d{10})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex SK_Generated(); /// /// VAT ID syntax regular expression for XI /// - [GeneratedRegex(@"^(XI)(\d{9}|\d{12}|(GD\d{3})|(HA\d{3}))$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^(XI)(\d{9}|\d{12}|(GD\d{3})|(HA\d{3}))$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex XI_Generated(); /// /// Normalizing regular expression /// - [GeneratedRegex(@"[^\d|A-Z]", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"[^\d|A-Z]", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex Normalizing_Generated(); } } diff --git a/src/ObjectValidation/HostAttribute.cs b/src/ObjectValidation/HostAttribute.cs index 21cf72a..f64ba98 100644 --- a/src/ObjectValidation/HostAttribute.cs +++ b/src/ObjectValidation/HostAttribute.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Diagnostics.Contracts; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; @@ -34,6 +35,36 @@ public class HostAttribute() : ValidationAttributeBase() /// public int PingTimeout { get; set; } = 300; + /// + /// Use a cache for results? + /// + public bool UseCache { get; set; } = true; + + /// + /// Check if a hostname/IP exists using a cache (may throw; for implementing a cache, this method needs to be overridden) + /// + /// Hostname + /// IP address + /// IP status + /// If exists + protected virtual bool CheckIfExistsCache(string? hostName, IPAddress? ip, out IPStatus status) + { + if (hostName is not null) + { + IPHostEntry hostEntry = Dns.GetHostEntry(hostName); + status = IPStatus.Unknown; + return hostEntry.AddressList.Length > 0 || hostEntry.Aliases.Length > 0; + } + Contract.Assert(ip is not null); + using Ping ping = new(); + PingReply pong = ping.Send(ip, PingTimeout, RandomNumberGenerator.GetBytes(count: 32), new() + { + DontFragment = true + }); + status = pong.Status; + return pong.Status == IPStatus.Success; + } + /// protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { @@ -47,15 +78,16 @@ public class HostAttribute() : ValidationAttributeBase() if (!CheckIfExists) return null; try { - Dns.GetHostEntry(str); - return null; + return CheckIfExistsCache(str, ip: null, out _) + ? null + : this.CreateValidationResult($"Hostname DNS lookup failed", validationContext); } catch (Exception ex) { return this.CreateValidationResult($"Hostname DNS lookup failed: {ex.Message ?? ex.GetType().ToString()}", validationContext); } case UriHostNameType.IPv4: - if(!IPAddress.TryParse(str, out ip)) return this.CreateValidationResult($"Host IPv4 address parsing failed", validationContext); + if (!IPAddress.TryParse(str, out ip)) return this.CreateValidationResult($"Host IPv4 address parsing failed", validationContext); if (ip.AddressFamily != AddressFamily.InterNetwork) return this.CreateValidationResult($"Detected host IPv4 address parsed to IPv6", validationContext); break; case UriHostNameType.IPv6: @@ -66,18 +98,13 @@ public class HostAttribute() : ValidationAttributeBase() return this.CreateValidationResult($"Hostname or IP address value invalid ({type})", validationContext); } if (!CheckIfExists) return null; - using Ping ping = new(); try { - PingReply pong = ping.Send(ip, PingTimeout, RandomNumberGenerator.GetBytes(count: 32), new() - { - DontFragment = true - }); - return pong.Status == IPStatus.Success + return CheckIfExistsCache(hostName: null, ip, out IPStatus status) ? null - : this.CreateValidationResult($"Ping to {ip} failed: {pong.Status}", validationContext); + : this.CreateValidationResult($"Ping to {ip} failed: {status}", validationContext); } - catch(Exception ex) + catch (Exception ex) { return this.CreateValidationResult($"Ping to {ip} failed exceptional: {ex.Message ?? ex.GetType().ToString()}", validationContext); } diff --git a/src/ObjectValidation/LuhnChecksum.cs b/src/ObjectValidation/LuhnChecksum.cs index dbae836..7e79bab 100644 --- a/src/ObjectValidation/LuhnChecksum.cs +++ b/src/ObjectValidation/LuhnChecksum.cs @@ -30,7 +30,7 @@ public static bool Validate(string value) /// /// Normalizing regular expression /// - [GeneratedRegex(@"[^\d]", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"[^\d]", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex Normalizing_Generated(); } } diff --git a/src/ObjectValidation/ObjectValidation.csproj b/src/ObjectValidation/ObjectValidation.csproj index f174134..7795223 100644 --- a/src/ObjectValidation/ObjectValidation.csproj +++ b/src/ObjectValidation/ObjectValidation.csproj @@ -22,7 +22,7 @@ LICENSE True README.md - 2.7.0 + 2.8.0 embedded true Debug;Release;Trunk diff --git a/src/ObjectValidation/SwiftValidation.cs b/src/ObjectValidation/SwiftValidation.cs index 3cc8e72..d5c64bc 100644 --- a/src/ObjectValidation/SwiftValidation.cs +++ b/src/ObjectValidation/SwiftValidation.cs @@ -79,25 +79,25 @@ public static bool ValidateIban(string iban) /// /// Normalizing regular expression /// - [GeneratedRegex(@"[\d|A-Z]", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"[\d|A-Z]", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex Normalizing_Generated(); /// /// IBAN syntax regular expression ($1 is the country, $2 the checksum, $3 the bank ID and $4 the account ID) /// - [GeneratedRegex(@"^([A-Z]{2})(\d{2})(\d{8})(\d{10})$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^([A-Z]{2})(\d{2})(\d{8})(\d{10})$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex IbanSyntax_Generated(); /// /// IBAN checksum calculation regular expression /// - [GeneratedRegex(@"[A-Z]", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"[A-Z]", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex IbanChecksum_Generated(); /// /// BIC syntax regular expression /// - [GeneratedRegex(@"^[A-Z|\d]{4}[A-Z]{2}[A-Z|\d]{2}([A-Z|\d]{3})?$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^[A-Z|\d]{4}[A-Z]{2}[A-Z|\d]{2}([A-Z|\d]{3})?$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex BicSyntax_Generated(); } } diff --git a/src/ObjectValidation/XRechnungRouting.cs b/src/ObjectValidation/XRechnungRouting.cs index 2e12665..51c977d 100644 --- a/src/ObjectValidation/XRechnungRouting.cs +++ b/src/ObjectValidation/XRechnungRouting.cs @@ -48,25 +48,25 @@ public static bool Validate(string route) /// /// Normalizing regular expression /// - [GeneratedRegex(@"[^\d|A-Z|-]", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"[^\d|A-Z|-]", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex Normalizing_Generated(); /// /// Syntax regular expression /// - [GeneratedRegex(@"^\d{2}(\d(\d{2}(\d{3})?)?)?-[A-Z|\d]{1,30}-\d{2}$", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"^\d{2}(\d(\d{2}(\d{3})?)?)?-[A-Z|\d]{1,30}-\d{2}$", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex Syntax_Generated(); /// /// Checksum calculation regular expression /// - [GeneratedRegex(@"[A-Z]", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"[A-Z]", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex ChecksumCalculation_Generated(); /// /// Checksum normalizing regular expression /// - [GeneratedRegex(@"[^\d|A-Z]", RegexOptions.Compiled | RegexOptions.Singleline)] + [GeneratedRegex(@"[^\d|A-Z]", RegexOptions.Compiled | RegexOptions.Singleline, 3000)] private static partial Regex ChecksumNormalizing_Generated(); } }