You've already forked linux-packaging-mono
Imported Upstream version 4.3.2.467
Former-commit-id: 9c2cb47f45fa221e661ab616387c9cda183f283d
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
// <copyright>
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
namespace System
|
||||
{
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
internal static partial class AppContextDefaultValues
|
||||
{
|
||||
static partial void PopulateDefaultValuesPartial(string platformIdentifier, string profile, int version)
|
||||
{
|
||||
// When defining a new switch you should add it to the last known version.
|
||||
// For instance, if you are adding a switch in .NET 4.6 (the release after 4.5.2) you should defined your switch
|
||||
// like this:
|
||||
// if (version <= 40502) ...
|
||||
// This ensures that all previous versions of that platform (up-to 4.5.2) will get the old behavior by default
|
||||
// NOTE: When adding a default value for a switch please make sure that the default value is added to ALL of the existing platforms!
|
||||
// NOTE: When adding a new if statement for the version please ensure that ALL previous switches are enabled (ie. don't use else if)
|
||||
switch (platformIdentifier)
|
||||
{
|
||||
case ".NETCore":
|
||||
case ".NETFramework":
|
||||
{
|
||||
if (version <= 40600)
|
||||
{
|
||||
LocalAppContextSwitches.SetDefaultsLessOrEqual_46();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
external/referencesource/System.ComponentModel.DataAnnotations/DataAnnotations/AppSettings.cs
vendored
Normal file
52
external/referencesource/System.ComponentModel.DataAnnotations/DataAnnotations/AppSettings.cs
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="AppSettings.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// AppSettings.cs
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Configuration;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace System.ComponentModel.DataAnnotations {
|
||||
internal static class AppSettings {
|
||||
#if MONO
|
||||
internal static readonly bool DisableRegEx = false;
|
||||
#else
|
||||
private static volatile bool _settingsInitialized = false;
|
||||
private static object _appSettingsLock = new object();
|
||||
private static void EnsureSettingsLoaded() {
|
||||
if (!_settingsInitialized) {
|
||||
lock (_appSettingsLock) {
|
||||
if (!_settingsInitialized) {
|
||||
NameValueCollection settings = null;
|
||||
|
||||
try {
|
||||
settings = ConfigurationManager.AppSettings;
|
||||
}
|
||||
catch (ConfigurationErrorsException) { }
|
||||
finally {
|
||||
if (settings == null || !Boolean.TryParse(settings["dataAnnotations:dataTypeAttribute:disableRegEx"], out _disableRegEx))
|
||||
_disableRegEx = false;
|
||||
|
||||
_settingsInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool _disableRegEx;
|
||||
internal static bool DisableRegEx {
|
||||
get {
|
||||
EnsureSettingsLoaded();
|
||||
return _disableRegEx;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -9,7 +9,10 @@
|
||||
public sealed class CreditCardAttribute : DataTypeAttribute {
|
||||
public CreditCardAttribute()
|
||||
: base(DataType.CreditCard) {
|
||||
ErrorMessage = DataAnnotationsResources.CreditCardAttribute_Invalid;
|
||||
|
||||
// DevDiv 468241: set DefaultErrorMessage not ErrorMessage, allowing user to set
|
||||
// ErrorMessageResourceType and ErrorMessageResourceName to use localized messages.
|
||||
DefaultErrorMessage = DataAnnotationsResources.CreditCardAttribute_Invalid;
|
||||
}
|
||||
|
||||
public override bool IsValid(object value) {
|
||||
|
@@ -8,11 +8,14 @@
|
||||
|
||||
// This attribute provides server-side email validation equivalent to jquery validate,
|
||||
// and therefore shares the same regular expression. See unit tests for examples.
|
||||
private static Regex _regex = new Regex(@"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
|
||||
private static Regex _regex = CreateRegEx();
|
||||
|
||||
public EmailAddressAttribute()
|
||||
: base(DataType.EmailAddress) {
|
||||
ErrorMessage = DataAnnotationsResources.EmailAddressAttribute_Invalid;
|
||||
|
||||
// DevDiv 468241: set DefaultErrorMessage not ErrorMessage, allowing user to set
|
||||
// ErrorMessageResourceType and ErrorMessageResourceName to use localized messages.
|
||||
DefaultErrorMessage = DataAnnotationsResources.EmailAddressAttribute_Invalid;
|
||||
}
|
||||
|
||||
public override bool IsValid(object value) {
|
||||
@@ -21,7 +24,51 @@
|
||||
}
|
||||
|
||||
string valueAsString = value as string;
|
||||
return valueAsString != null && _regex.Match(valueAsString).Length > 0;
|
||||
|
||||
// Use RegEx implementation if it has been created, otherwise use a non RegEx version.
|
||||
if (_regex != null) {
|
||||
return valueAsString != null && _regex.Match(valueAsString).Length > 0;
|
||||
}
|
||||
else {
|
||||
int atCount = 0;
|
||||
|
||||
foreach (char c in valueAsString) {
|
||||
if (c == '@') {
|
||||
atCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return (valueAsString != null
|
||||
&& atCount == 1
|
||||
&& valueAsString[0] != '@'
|
||||
&& valueAsString[valueAsString.Length - 1] != '@');
|
||||
}
|
||||
}
|
||||
|
||||
private static Regex CreateRegEx() {
|
||||
// We only need to create the RegEx if this switch is enabled.
|
||||
if (AppSettings.DisableRegEx) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const string pattern = @"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$";
|
||||
const RegexOptions options = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
|
||||
|
||||
// Set explicit regex match timeout, sufficient enough for email parsing
|
||||
// Unless the global REGEX_DEFAULT_MATCH_TIMEOUT is already set
|
||||
TimeSpan matchTimeout = TimeSpan.FromSeconds(2);
|
||||
|
||||
try {
|
||||
if (AppDomain.CurrentDomain.GetData("REGEX_DEFAULT_MATCH_TIMEOUT") == null) {
|
||||
return new Regex(pattern, options, matchTimeout);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Fallback on error
|
||||
}
|
||||
|
||||
// Legacy fallback (without explicit match timeout)
|
||||
return new Regex(pattern, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,10 @@ namespace System.ComponentModel.DataAnnotations {
|
||||
|
||||
public FileExtensionsAttribute()
|
||||
: base(DataType.Upload) {
|
||||
ErrorMessage = DataAnnotationsResources.FileExtensionsAttribute_Invalid;
|
||||
|
||||
// DevDiv 468241: set DefaultErrorMessage not ErrorMessage, allowing user to set
|
||||
// ErrorMessageResourceType and ErrorMessageResourceName to use localized messages.
|
||||
DefaultErrorMessage = DataAnnotationsResources.FileExtensionsAttribute_Invalid;
|
||||
}
|
||||
|
||||
public string Extensions {
|
||||
|
@@ -8,4 +8,4 @@ namespace System.ComponentModel.DataAnnotations {
|
||||
IEnumerable<ValidationResult> Validate(ValidationContext validationContext);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
@@ -0,0 +1,28 @@
|
||||
// <copyright>
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
namespace System.ComponentModel.DataAnnotations {
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// When adding a quirk, name it such that false is new behavior and true is old behavior.
|
||||
// You are opting IN to old behavior. The new behavior is default.
|
||||
// For example, we don't want to use legacy regex timeout for RegularExpressionAttribute in 4.6.1+.
|
||||
// So we set UseLegacyRegExTimeout to true if running 4.6 or less.
|
||||
internal static class LocalAppContextSwitches {
|
||||
private const string UseLegacyRegExTimeoutString = "Switch.System.ComponentModel.DataAnnotations.RegularExpressionAttribute.UseLegacyRegExTimeout";
|
||||
private static int useLegacyRegExTimeout;
|
||||
|
||||
public static bool UseLegacyRegExTimeout {
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get {
|
||||
return LocalAppContext.GetCachedSwitchValue(UseLegacyRegExTimeoutString, ref useLegacyRegExTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetDefaultsLessOrEqual_46() {
|
||||
// Define the switches that should be true for 4.6 or less, false for 4.6.1+.
|
||||
LocalAppContext.DefineSwitchDefault(UseLegacyRegExTimeoutString, true);
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,11 +6,15 @@
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
|
||||
public sealed class PhoneAttribute : DataTypeAttribute {
|
||||
// see unit tests for examples
|
||||
private static Regex _regex = new Regex(@"^(\+\s?)?((?<!\+.*)\(\+?\d+([\s\-\.]?\d+)?\)|\d+)([\s\-\.]?(\(\d+([\s\-\.]?\d+)?\)|\d+))*(\s?(x|ext\.?)\s?\d+)?$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
|
||||
private static Regex _regex = CreateRegEx();
|
||||
private const string _additionalPhoneNumberCharacters = "-.()";
|
||||
|
||||
public PhoneAttribute()
|
||||
: base(DataType.PhoneNumber) {
|
||||
ErrorMessage = DataAnnotationsResources.PhoneAttribute_Invalid;
|
||||
|
||||
// DevDiv 468241: set DefaultErrorMessage not ErrorMessage, allowing user to set
|
||||
// ErrorMessageResourceType and ErrorMessageResourceName to use localized messages.
|
||||
DefaultErrorMessage = DataAnnotationsResources.PhoneAttribute_Invalid;
|
||||
}
|
||||
|
||||
public override bool IsValid(object value) {
|
||||
@@ -19,7 +23,114 @@
|
||||
}
|
||||
|
||||
string valueAsString = value as string;
|
||||
return valueAsString != null && _regex.Match(valueAsString).Length > 0;
|
||||
|
||||
// Use RegEx implementation if it has been created, otherwise use a non RegEx version.
|
||||
if (_regex != null) {
|
||||
return valueAsString != null && _regex.Match(valueAsString).Length > 0;
|
||||
}
|
||||
else {
|
||||
if (valueAsString == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
valueAsString = valueAsString.Replace("+", string.Empty).TrimEnd();
|
||||
valueAsString = RemoveExtension(valueAsString);
|
||||
|
||||
bool digitFound = false;
|
||||
foreach (char c in valueAsString) {
|
||||
if (Char.IsDigit(c)) {
|
||||
digitFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!digitFound) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (char c in valueAsString)
|
||||
{
|
||||
if (!(Char.IsDigit(c)
|
||||
|| Char.IsWhiteSpace(c)
|
||||
|| _additionalPhoneNumberCharacters.IndexOf(c) != -1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static Regex CreateRegEx() {
|
||||
// We only need to create the RegEx if this switch is enabled.
|
||||
if (AppSettings.DisableRegEx) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const string pattern = @"^(\+\s?)?((?<!\+.*)\(\+?\d+([\s\-\.]?\d+)?\)|\d+)([\s\-\.]?(\(\d+([\s\-\.]?\d+)?\)|\d+))*(\s?(x|ext\.?)\s?\d+)?$";
|
||||
const RegexOptions options = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
|
||||
|
||||
// Set explicit regex match timeout, sufficient enough for phone parsing
|
||||
// Unless the global REGEX_DEFAULT_MATCH_TIMEOUT is already set
|
||||
TimeSpan matchTimeout = TimeSpan.FromSeconds(2);
|
||||
|
||||
try {
|
||||
if (AppDomain.CurrentDomain.GetData("REGEX_DEFAULT_MATCH_TIMEOUT") == null) {
|
||||
return new Regex(pattern, options, matchTimeout);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Fallback on error
|
||||
}
|
||||
|
||||
// Legacy fallback (without explicit match timeout)
|
||||
return new Regex(pattern, options);
|
||||
}
|
||||
|
||||
private static string RemoveExtension(string potentialPhoneNumber) {
|
||||
int lastIndexOfExtension = potentialPhoneNumber
|
||||
.LastIndexOf("ext.", StringComparison.InvariantCultureIgnoreCase);
|
||||
if (lastIndexOfExtension >= 0) {
|
||||
string extension = potentialPhoneNumber.Substring(lastIndexOfExtension + 4);
|
||||
if (MatchesExtension(extension)) {
|
||||
return potentialPhoneNumber.Substring(0, lastIndexOfExtension);
|
||||
}
|
||||
}
|
||||
|
||||
lastIndexOfExtension = potentialPhoneNumber
|
||||
.LastIndexOf("ext", StringComparison.InvariantCultureIgnoreCase);
|
||||
if (lastIndexOfExtension >= 0) {
|
||||
string extension = potentialPhoneNumber.Substring(lastIndexOfExtension + 3);
|
||||
if (MatchesExtension(extension)) {
|
||||
return potentialPhoneNumber.Substring(0, lastIndexOfExtension);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lastIndexOfExtension = potentialPhoneNumber
|
||||
.LastIndexOf("x", StringComparison.InvariantCultureIgnoreCase);
|
||||
if (lastIndexOfExtension >= 0) {
|
||||
string extension = potentialPhoneNumber.Substring(lastIndexOfExtension + 1);
|
||||
if (MatchesExtension(extension)) {
|
||||
return potentialPhoneNumber.Substring(0, lastIndexOfExtension);
|
||||
}
|
||||
}
|
||||
|
||||
return potentialPhoneNumber;
|
||||
}
|
||||
|
||||
private static bool MatchesExtension(string potentialExtension) {
|
||||
potentialExtension = potentialExtension.TrimStart();
|
||||
if (potentialExtension.Length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (char c in potentialExtension) {
|
||||
if (!Char.IsDigit(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations.Resources;
|
||||
using System.ComponentModel.DataAnnotations.Resources;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -15,6 +15,12 @@ namespace System.ComponentModel.DataAnnotations {
|
||||
/// </summary>
|
||||
public string Pattern { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout to use when matching the regular expression pattern (in milliseconds)
|
||||
/// (-1 means never timeout).
|
||||
/// </summary>
|
||||
public int MatchTimeoutInMilliseconds { get; set; } = GetDefaultTimeout();
|
||||
|
||||
private Regex Regex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -77,13 +83,33 @@ namespace System.ComponentModel.DataAnnotations {
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException"> is thrown if the current <see cref="Pattern"/> cannot be parsed</exception>
|
||||
/// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"> thrown if <see cref="MatchTimeoutInMilliseconds" /> is negative (except -1),
|
||||
/// zero or greater than approximately 24 days </exception>
|
||||
private void SetupRegex() {
|
||||
if (this.Regex == null) {
|
||||
if (string.IsNullOrEmpty(this.Pattern)) {
|
||||
throw new InvalidOperationException(DataAnnotationsResources.RegularExpressionAttribute_Empty_Pattern);
|
||||
}
|
||||
this.Regex = new Regex(this.Pattern);
|
||||
Regex = MatchTimeoutInMilliseconds == -1
|
||||
? new Regex(Pattern)
|
||||
: Regex = new Regex(Pattern, default(RegexOptions), TimeSpan.FromMilliseconds((double)MatchTimeoutInMilliseconds));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default MatchTimeout based on UseLegacyRegExTimeout switch.
|
||||
/// </summary>
|
||||
private static int GetDefaultTimeout() {
|
||||
#if !MONO
|
||||
if (LocalAppContextSwitches.UseLegacyRegExTimeout) {
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
return 2000;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,4 +10,4 @@ namespace System.ComponentModel.DataAnnotations.Schema {
|
||||
[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "We want users to be able to extend this class")]
|
||||
public class ComplexTypeAttribute : Attribute {
|
||||
}
|
||||
}
|
||||
}
|
@@ -18,4 +18,4 @@ namespace System.ComponentModel.DataAnnotations.Schema {
|
||||
/// </summary>
|
||||
Computed
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,4 +8,4 @@ namespace System.ComponentModel.DataAnnotations.Schema {
|
||||
[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "We want users to be able to extend this class")]
|
||||
public class NotMappedAttribute : Attribute {
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,11 +8,14 @@
|
||||
|
||||
// This attribute provides server-side url validation equivalent to jquery validate,
|
||||
// and therefore shares the same regular expression. See unit tests for examples.
|
||||
private static Regex _regex = new Regex(@"^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
|
||||
private static Regex _regex = CreateRegEx();
|
||||
|
||||
public UrlAttribute()
|
||||
: base(DataType.Url) {
|
||||
ErrorMessage = DataAnnotationsResources.UrlAttribute_Invalid;
|
||||
|
||||
// DevDiv 468241: set DefaultErrorMessage not ErrorMessage, allowing user to set
|
||||
// ErrorMessageResourceType and ErrorMessageResourceName to use localized messages.
|
||||
DefaultErrorMessage = DataAnnotationsResources.UrlAttribute_Invalid;
|
||||
}
|
||||
|
||||
public override bool IsValid(object value) {
|
||||
@@ -21,7 +24,44 @@
|
||||
}
|
||||
|
||||
string valueAsString = value as string;
|
||||
return valueAsString != null && _regex.Match(valueAsString).Length > 0;
|
||||
|
||||
// Use RegEx implementation if it has been created, otherwise use a non RegEx version.
|
||||
if (_regex != null) {
|
||||
return valueAsString != null && _regex.Match(valueAsString).Length > 0;
|
||||
}
|
||||
else {
|
||||
return valueAsString != null &&
|
||||
(valueAsString.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)
|
||||
|| valueAsString.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)
|
||||
|| valueAsString.StartsWith("ftp://", StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
private static Regex CreateRegEx() {
|
||||
// We only need to create the RegEx if this switch is enabled.
|
||||
if (AppSettings.DisableRegEx) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const string pattern = @"^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$";
|
||||
|
||||
const RegexOptions options = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
|
||||
|
||||
// Set explicit regex match timeout, sufficient enough for url parsing
|
||||
// Unless the global REGEX_DEFAULT_MATCH_TIMEOUT is already set
|
||||
TimeSpan matchTimeout = TimeSpan.FromSeconds(2);
|
||||
|
||||
try {
|
||||
if (AppDomain.CurrentDomain.GetData("REGEX_DEFAULT_MATCH_TIMEOUT") == null) {
|
||||
return new Regex(pattern, options, matchTimeout);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Fallback on error
|
||||
}
|
||||
|
||||
// Legacy fallback (without explicit match timeout)
|
||||
return new Regex(pattern, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -21,8 +21,9 @@ namespace System.ComponentModel.DataAnnotations {
|
||||
private Func<string> _errorMessageResourceAccessor;
|
||||
private string _errorMessageResourceName;
|
||||
private Type _errorMessageResourceType;
|
||||
private bool _isCallingOverload;
|
||||
private object _syncLock = new object();
|
||||
private string _defaultErrorMessage;
|
||||
|
||||
private volatile bool _hasBaseIsValid;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -60,6 +61,31 @@ namespace System.ComponentModel.DataAnnotations {
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Properties
|
||||
/// <summary>
|
||||
/// Gets or sets and the default error message string.
|
||||
/// This message will be used if the user has not set <see cref="ErrorMessage"/>
|
||||
/// or the <see cref="ErrorMessageResourceType"/> and <see cref="ErrorMessageResourceName"/> pair.
|
||||
/// This property was added after the public contract for DataAnnotations was created.
|
||||
/// It was added to fix DevDiv issue 468241.
|
||||
/// It is internal to avoid changing the DataAnnotations contract.
|
||||
/// </summary>
|
||||
internal string DefaultErrorMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
return this._defaultErrorMessage;
|
||||
}
|
||||
set
|
||||
{
|
||||
this._defaultErrorMessage = value;
|
||||
this._errorMessageResourceAccessor = null;
|
||||
this.CustomErrorMessageSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Properties
|
||||
|
||||
/// <summary>
|
||||
@@ -75,7 +101,7 @@ namespace System.ComponentModel.DataAnnotations {
|
||||
|
||||
/// <summary>
|
||||
/// A flag indicating whether a developer has customized the attribute's error message by setting any one of
|
||||
/// ErrorMessage, ErrorMessageResourceName, or ErrorMessageResourceType.
|
||||
/// ErrorMessage, ErrorMessageResourceName, ErrorMessageResourceType or DefaultErrorMessage.
|
||||
/// </summary>
|
||||
internal bool CustomErrorMessageSet {
|
||||
get;
|
||||
@@ -105,12 +131,23 @@ namespace System.ComponentModel.DataAnnotations {
|
||||
/// </value>
|
||||
public string ErrorMessage {
|
||||
get {
|
||||
return this._errorMessage;
|
||||
// DevDiv: 468241
|
||||
// If _errorMessage is not set, return the default. This is done to preserve
|
||||
// behavior prior to the fix where ErrorMessage showed the non-null message to use.
|
||||
return this._errorMessage ?? this._defaultErrorMessage;
|
||||
}
|
||||
set {
|
||||
this._errorMessage = value;
|
||||
this._errorMessageResourceAccessor = null;
|
||||
this.CustomErrorMessageSet = true;
|
||||
|
||||
// DevDiv: 468241
|
||||
// Explicitly setting ErrorMessage also sets DefaultErrorMessage if null.
|
||||
// This prevents subsequent read of ErrorMessage from returning default.
|
||||
if (value == null)
|
||||
{
|
||||
this._defaultErrorMessage = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,14 +200,16 @@ namespace System.ComponentModel.DataAnnotations {
|
||||
/// <exception cref="InvalidOperationException"> is thrown if the current attribute is malformed.</exception>
|
||||
private void SetupResourceAccessor() {
|
||||
if (this._errorMessageResourceAccessor == null) {
|
||||
string localErrorMessage = this._errorMessage;
|
||||
string localErrorMessage = this.ErrorMessage;
|
||||
bool resourceNameSet = !string.IsNullOrEmpty(this._errorMessageResourceName);
|
||||
bool errorMessageSet = !string.IsNullOrEmpty(localErrorMessage);
|
||||
bool errorMessageSet = !string.IsNullOrEmpty(this._errorMessage);
|
||||
bool resourceTypeSet = this._errorMessageResourceType != null;
|
||||
bool defaultMessageSet = !string.IsNullOrEmpty(this._defaultErrorMessage);
|
||||
|
||||
// Either ErrorMessageResourceName or ErrorMessage may be set, but not both.
|
||||
// The following test checks both being set as well as both being not set.
|
||||
if (resourceNameSet == errorMessageSet) {
|
||||
// The following combinations are illegal and throw InvalidOperationException:
|
||||
// 1) Both ErrorMessage and ErrorMessageResourceName are set, or
|
||||
// 2) None of ErrorMessage, ErrorMessageReourceName, and DefaultErrorMessage are set.
|
||||
if ((resourceNameSet && errorMessageSet) || !(resourceNameSet || errorMessageSet || defaultMessageSet)) {
|
||||
throw new InvalidOperationException(DataAnnotationsResources.ValidationAttribute_Cannot_Set_ErrorMessage_And_Resource);
|
||||
}
|
||||
|
||||
@@ -281,20 +320,14 @@ namespace System.ComponentModel.DataAnnotations {
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
virtual bool IsValid(object value) {
|
||||
lock (this._syncLock) {
|
||||
if (this._isCallingOverload) {
|
||||
throw new NotImplementedException(DataAnnotationsResources.ValidationAttribute_IsValid_NotImplemented);
|
||||
} else {
|
||||
this._isCallingOverload = true;
|
||||
|
||||
try {
|
||||
return this.IsValid(value, null) == null;
|
||||
} finally {
|
||||
this._isCallingOverload = false;
|
||||
}
|
||||
}
|
||||
virtual bool IsValid(object value) {
|
||||
if(!this._hasBaseIsValid) {
|
||||
// track that this method overload has not been overridden.
|
||||
this._hasBaseIsValid = true;
|
||||
}
|
||||
|
||||
// call overridden method.
|
||||
return this.IsValid(value, null) == null;
|
||||
}
|
||||
|
||||
#if !SILVERLIGHT
|
||||
@@ -336,25 +369,20 @@ namespace System.ComponentModel.DataAnnotations {
|
||||
/// </exception>
|
||||
#endif
|
||||
protected virtual ValidationResult IsValid(object value, ValidationContext validationContext) {
|
||||
lock (this._syncLock) {
|
||||
if (this._isCallingOverload) {
|
||||
throw new NotImplementedException(DataAnnotationsResources.ValidationAttribute_IsValid_NotImplemented);
|
||||
} else {
|
||||
this._isCallingOverload = true;
|
||||
if (this._hasBaseIsValid) {
|
||||
// this means neither of the IsValid methods has been overriden, throw.
|
||||
throw new NotImplementedException(DataAnnotationsResources.ValidationAttribute_IsValid_NotImplemented);
|
||||
}
|
||||
|
||||
ValidationResult result = ValidationResult.Success;
|
||||
|
||||
try {
|
||||
ValidationResult result = ValidationResult.Success;
|
||||
|
||||
if (!this.IsValid(value)) {
|
||||
string[] memberNames = validationContext.MemberName != null ? new string[] { validationContext.MemberName } : null;
|
||||
result = new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName), memberNames);
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
this._isCallingOverload = false;
|
||||
}
|
||||
}
|
||||
// call overridden method.
|
||||
if (!this.IsValid(value)) {
|
||||
string[] memberNames = validationContext.MemberName != null ? new string[] { validationContext.MemberName } : null;
|
||||
result = new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName), memberNames);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
Reference in New Issue
Block a user