Fix bugs in sizing TableLayoutPanel (Xamarin bug 18638)
[mono.git] / mcs / class / System.ComponentModel.DataAnnotations / System.ComponentModel.DataAnnotations / EmailAddressAttribute.cs
1 //
2 // EmailAddressAttribute.cs
3 //
4 // Authors:
5 //      Marek Safar  <marek.safar@gmail.com>
6 //      Pablo Ruiz García <pablo.ruiz@gmail.com>
7 //
8 // Copyright (C) 2013 Xamarin Inc (http://www.xamarin.com)
9 // Copyright (C) 2013 Pablo Ruiz García
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 #if NET_4_5
32
33 using System;
34 using System.Globalization;
35 using System.Text.RegularExpressions;
36
37 namespace System.ComponentModel.DataAnnotations
38 {
39         [AttributeUsageAttribute (AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
40         public class EmailAddressAttribute : DataTypeAttribute
41         {
42                 private const string DefaultErrorMessage = "The {0} field is not a valid e-mail address.";
43                 const string AtomCharacters = "!#$%&'*+-/=?^_`{|}~";
44
45                 static bool IsLetterOrDigit (char c)
46                 {
47                         return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
48                 }
49
50                 static bool IsAtom (char c)
51                 {
52                         return IsLetterOrDigit (c) || AtomCharacters.IndexOf (c) != -1;
53                 }
54
55                 static bool IsDomain (char c)
56                 {
57                         return IsLetterOrDigit (c) || c == '-';
58                 }
59
60                 static bool SkipAtom (string text, ref int index)
61                 {
62                         int startIndex = index;
63
64                         while (index < text.Length && IsAtom (text[index]))
65                                 index++;
66
67                         return index > startIndex;
68                 }
69
70                 static bool SkipSubDomain (string text, ref int index)
71                 {
72                         if (!IsDomain (text[index]) || text[index] == '-')
73                                 return false;
74
75                         index++;
76
77                         while (index < text.Length && IsDomain (text[index]))
78                                 index++;
79
80                         return true;
81                 }
82
83                 static bool SkipDomain (string text, ref int index)
84                 {
85                         if (!SkipSubDomain (text, ref index))
86                                 return false;
87
88                         while (index < text.Length && text[index] == '.') {
89                                 index++;
90
91                                 if (index == text.Length)
92                                         return false;
93
94                                 if (!SkipSubDomain (text, ref index))
95                                         return false;
96                         }
97
98                         return true;
99                 }
100
101                 static bool SkipQuoted (string text, ref int index)
102                 {
103                         bool escaped = false;
104
105                         // skip over leading '"'
106                         index++;
107
108                         while (index < text.Length) {
109                                 if (text[index] == (byte) '\\') {
110                                         escaped = !escaped;
111                                 } else if (!escaped) {
112                                         if (text[index] == (byte) '"')
113                                                 break;
114                                 } else {
115                                         escaped = false;
116                                 }
117
118                                 index++;
119                         }
120
121                         if (index >= text.Length || text[index] != (byte) '"')
122                                 return false;
123
124                         index++;
125
126                         return true;
127                 }
128
129                 static bool SkipWord (string text, ref int index)
130                 {
131                         if (text[index] == (byte) '"')
132                                 return SkipQuoted (text, ref index);
133
134                         return SkipAtom (text, ref index);
135                 }
136
137                 static bool SkipIPv4Literal (string text, ref int index)
138                 {
139                         int groups = 0;
140
141                         while (index < text.Length && groups < 4) {
142                                 int startIndex = index;
143                                 int value = 0;
144
145                                 while (index < text.Length && text[index] >= '0' && text[index] <= '9') {
146                                         value = (value * 10) + (text[index] - '0');
147                                         index++;
148                                 }
149
150                                 if (index == startIndex || index - startIndex > 3 || value > 255)
151                                         return false;
152
153                                 groups++;
154
155                                 if (groups < 4 && index < text.Length && text[index] == '.')
156                                         index++;
157                         }
158
159                         return groups == 4;
160                 }
161
162                 static bool IsHexDigit (char c)
163                 {
164                         return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
165                 }
166
167                 // This needs to handle the following forms:
168                 //
169                 // IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
170                 // IPv6-hex  = 1*4HEXDIG
171                 // IPv6-full = IPv6-hex 7(":" IPv6-hex)
172                 // IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::" [IPv6-hex *5(":" IPv6-hex)]
173                 //             ; The "::" represents at least 2 16-bit groups of zeros
174                 //             ; No more than 6 groups in addition to the "::" may be
175                 //             ; present
176                 // IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
177                 // IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
178                 //               [IPv6-hex *3(":" IPv6-hex) ":"] IPv4-address-literal
179                 //             ; The "::" represents at least 2 16-bit groups of zeros
180                 //             ; No more than 4 groups in addition to the "::" and
181                 //             ; IPv4-address-literal may be present
182                 static bool SkipIPv6Literal (string text, ref int index)
183                 {
184                         bool compact = false;
185                         int colons = 0;
186
187                         while (index < text.Length) {
188                                 int startIndex = index;
189
190                                 while (index < text.Length && IsHexDigit (text[index]))
191                                         index++;
192
193                                 if (index >= text.Length)
194                                         break;
195
196                                 if (index > startIndex && colons > 2 && text[index] == '.') {
197                                         // IPv6v4
198                                         index = startIndex;
199
200                                         if (!SkipIPv4Literal (text, ref index))
201                                                 return false;
202
203                                         break;
204                                 }
205
206                                 int count = index - startIndex;
207                                 if (count > 4)
208                                         return false;
209
210                                 if (text[index] != ':')
211                                         break;
212
213                                 startIndex = index;
214                                 while (index < text.Length && text[index] == ':')
215                                         index++;
216
217                                 count = index - startIndex;
218                                 if (count > 2)
219                                         return false;
220
221                                 if (count == 2) {
222                                         if (compact)
223                                                 return false;
224
225                                         compact = true;
226                                         colons += 2;
227                                 } else {
228                                         colons++;
229                                 }
230                         }
231
232                         if (colons < 2)
233                                 return false;
234
235                         if (compact)
236                                 return colons < 6;
237
238                         return colons < 7;
239                 }
240
241                 static bool Validate (string email)
242                 {
243                         int index = 0;
244
245                         if (email.Length == 0)
246                                 return false;
247
248                         if (!SkipWord (email, ref index) || index >= email.Length)
249                                 return false;
250
251                         while (index < email.Length && email[index] == '.') {
252                                 index++;
253
254                                 if (!SkipWord (email, ref index) || index >= email.Length)
255                                         return false;
256                         }
257
258                         if (index + 1 >= email.Length || email[index++] != '@')
259                                 return false;
260
261                         if (email[index] != '[') {
262                                 // domain
263                                 if (!SkipDomain (email, ref index))
264                                         return false;
265
266                                 return index == email.Length;
267                         }
268
269                         // address literal
270                         index++;
271
272                         // we need at least 8 more characters
273                         if (index + 8 >= email.Length)
274                                 return false;
275
276                         var ipv6 = email.Substring (index, 5);
277                         if (ipv6.ToLowerInvariant () == "ipv6:") {
278                                 index += "IPv6:".Length;
279                                 if (!SkipIPv6Literal (email, ref index))
280                                         return false;
281                         } else {
282                                 if (!SkipIPv4Literal (email, ref index))
283                                         return false;
284                         }
285
286                         if (index >= email.Length || email[index++] != ']')
287                                 return false;
288
289                         return index == email.Length;
290                 }
291
292                 public EmailAddressAttribute ()
293                         : base(DataType.EmailAddress)
294                 {
295                         // XXX: There is no .ctor accepting Func<string> on DataTypeAttribute.. :?
296                         base.ErrorMessage = DefaultErrorMessage;
297                 }
298
299                 public override bool IsValid(object value)
300                 {
301                         if (value == null)
302                                 return true;
303
304                         string email = value as string;
305                         if (email == null)
306                                 return false;
307
308                         return Validate (email);
309                 }
310         }
311 }
312
313 #endif