Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.ComponentModel.DataAnnotations / DataAnnotations / PhoneAttribute.cs
1 namespace System.ComponentModel.DataAnnotations {
2     using System;
3     using System.ComponentModel.DataAnnotations.Resources;
4     using System.Text.RegularExpressions;
5
6     [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
7     public sealed class PhoneAttribute : DataTypeAttribute {
8         // see unit tests for examples
9         private static Regex _regex = CreateRegEx();
10         private const string _additionalPhoneNumberCharacters = "-.()";
11
12         public PhoneAttribute()
13             : base(DataType.PhoneNumber) {
14             
15             // DevDiv 468241: set DefaultErrorMessage not ErrorMessage, allowing user to set
16             // ErrorMessageResourceType and ErrorMessageResourceName to use localized messages.
17             DefaultErrorMessage = DataAnnotationsResources.PhoneAttribute_Invalid;
18         }
19
20         public override bool IsValid(object value) {
21             if (value == null) {
22                 return true;
23             }
24
25             string valueAsString = value as string;
26
27             // Use RegEx implementation if it has been created, otherwise use a non RegEx version.
28             if (_regex != null) { 
29                 return valueAsString != null && _regex.Match(valueAsString).Length > 0;
30             }
31             else {
32                 if (valueAsString == null) {
33                     return false;
34                 }
35
36                 valueAsString = valueAsString.Replace("+", string.Empty).TrimEnd();
37                 valueAsString = RemoveExtension(valueAsString);
38
39                 bool digitFound = false;
40                 foreach (char c in valueAsString) {
41                     if (Char.IsDigit(c)) {
42                         digitFound = true;
43                         break;
44                     }
45                 }
46
47                 if (!digitFound) {
48                     return false;
49                 }
50
51                 foreach (char c in valueAsString)
52                 {
53                     if (!(Char.IsDigit(c) 
54                         || Char.IsWhiteSpace(c)
55                         || _additionalPhoneNumberCharacters.IndexOf(c) != -1)) {
56                         return false;
57                     }
58                 }
59                 return true;
60             }
61         }
62
63         private static Regex CreateRegEx() {
64             // We only need to create the RegEx if this switch is enabled.
65             if (AppSettings.DisableRegEx) {
66                 return null;
67             }
68
69             const string pattern = @"^(\+\s?)?((?<!\+.*)\(\+?\d+([\s\-\.]?\d+)?\)|\d+)([\s\-\.]?(\(\d+([\s\-\.]?\d+)?\)|\d+))*(\s?(x|ext\.?)\s?\d+)?$";
70             const RegexOptions options = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
71             
72             // Set explicit regex match timeout, sufficient enough for phone parsing
73             // Unless the global REGEX_DEFAULT_MATCH_TIMEOUT is already set
74             TimeSpan matchTimeout = TimeSpan.FromSeconds(2);
75             
76             try {
77                 if (AppDomain.CurrentDomain.GetData("REGEX_DEFAULT_MATCH_TIMEOUT") == null) {
78                     return new Regex(pattern, options, matchTimeout);
79                 }
80             }
81             catch {
82                 // Fallback on error
83             }
84             
85             // Legacy fallback (without explicit match timeout)
86             return new Regex(pattern, options);
87         }
88
89         private static string RemoveExtension(string potentialPhoneNumber) {
90             int lastIndexOfExtension = potentialPhoneNumber
91                 .LastIndexOf("ext.", StringComparison.InvariantCultureIgnoreCase);
92             if (lastIndexOfExtension >= 0)  {
93                 string extension = potentialPhoneNumber.Substring(lastIndexOfExtension + 4);
94                 if (MatchesExtension(extension)) {
95                     return potentialPhoneNumber.Substring(0, lastIndexOfExtension);
96                 }
97             }
98
99             lastIndexOfExtension = potentialPhoneNumber
100                 .LastIndexOf("ext", StringComparison.InvariantCultureIgnoreCase);
101             if (lastIndexOfExtension >= 0) {
102                 string extension = potentialPhoneNumber.Substring(lastIndexOfExtension + 3);
103                 if (MatchesExtension(extension)) {
104                     return potentialPhoneNumber.Substring(0, lastIndexOfExtension);
105                 }
106             }
107
108
109             lastIndexOfExtension = potentialPhoneNumber
110                 .LastIndexOf("x", StringComparison.InvariantCultureIgnoreCase);
111             if (lastIndexOfExtension >= 0) {
112                 string extension = potentialPhoneNumber.Substring(lastIndexOfExtension + 1);
113                 if (MatchesExtension(extension)) {
114                     return potentialPhoneNumber.Substring(0, lastIndexOfExtension);
115                 }
116             }
117
118             return potentialPhoneNumber;
119         }
120
121         private static bool MatchesExtension(string potentialExtension)  {
122             potentialExtension = potentialExtension.TrimStart();
123             if (potentialExtension.Length == 0) {
124                 return false;
125             }
126
127             foreach (char c in potentialExtension) {
128                 if (!Char.IsDigit(c)) {
129                     return false;
130                 }
131             }
132
133             return true;
134         }
135     }
136 }