2 // EmailAddressAttribute.cs
5 // Marek Safar <marek.safar@gmail.com>
6 // Pablo Ruiz García <pablo.ruiz@gmail.com>
8 // Copyright (C) 2013 Xamarin Inc (http://www.xamarin.com)
9 // Copyright (C) 2013 Pablo Ruiz García
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
34 using System.Globalization;
35 using System.Text.RegularExpressions;
37 namespace System.ComponentModel.DataAnnotations
39 [AttributeUsageAttribute (AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
40 public class EmailAddressAttribute : DataTypeAttribute
42 private const string DefaultErrorMessage = "The {0} field is not a valid e-mail address.";
43 const string AtomCharacters = "!#$%&'*+-/=?^_`{|}~";
45 static bool IsLetterOrDigit (char c)
47 return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
50 static bool IsAtom (char c)
52 return IsLetterOrDigit (c) || AtomCharacters.IndexOf (c) != -1;
55 static bool IsDomain (char c)
57 return IsLetterOrDigit (c) || c == '-';
60 static bool SkipAtom (string text, ref int index)
62 int startIndex = index;
64 while (index < text.Length && IsAtom (text[index]))
67 return index > startIndex;
70 static bool SkipSubDomain (string text, ref int index)
72 if (!IsDomain (text[index]) || text[index] == '-')
77 while (index < text.Length && IsDomain (text[index]))
83 static bool SkipDomain (string text, ref int index)
85 if (!SkipSubDomain (text, ref index))
88 while (index < text.Length && text[index] == '.') {
91 if (index == text.Length)
94 if (!SkipSubDomain (text, ref index))
101 static bool SkipQuoted (string text, ref int index)
103 bool escaped = false;
105 // skip over leading '"'
108 while (index < text.Length) {
109 if (text[index] == (byte) '\\') {
111 } else if (!escaped) {
112 if (text[index] == (byte) '"')
121 if (index >= text.Length || text[index] != (byte) '"')
129 static bool SkipWord (string text, ref int index)
131 if (text[index] == (byte) '"')
132 return SkipQuoted (text, ref index);
134 return SkipAtom (text, ref index);
137 static bool SkipIPv4Literal (string text, ref int index)
141 while (index < text.Length && groups < 4) {
142 int startIndex = index;
145 while (index < text.Length && text[index] >= '0' && text[index] <= '9') {
146 value = (value * 10) + (text[index] - '0');
150 if (index == startIndex || index - startIndex > 3 || value > 255)
155 if (groups < 4 && index < text.Length && text[index] == '.')
162 static bool IsHexDigit (char c)
164 return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
167 // This needs to handle the following forms:
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
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)
184 bool compact = false;
187 while (index < text.Length) {
188 int startIndex = index;
190 while (index < text.Length && IsHexDigit (text[index]))
193 if (index >= text.Length)
196 if (index > startIndex && colons > 2 && text[index] == '.') {
200 if (!SkipIPv4Literal (text, ref index))
206 int count = index - startIndex;
210 if (text[index] != ':')
214 while (index < text.Length && text[index] == ':')
217 count = index - startIndex;
241 static bool Validate (string email)
245 if (email.Length == 0)
248 if (!SkipWord (email, ref index) || index >= email.Length)
251 while (index < email.Length && email[index] == '.') {
254 if (!SkipWord (email, ref index) || index >= email.Length)
258 if (index + 1 >= email.Length || email[index++] != '@')
261 if (email[index] != '[') {
263 if (!SkipDomain (email, ref index))
266 return index == email.Length;
272 // we need at least 8 more characters
273 if (index + 8 >= email.Length)
276 var ipv6 = email.Substring (index, 5);
277 if (ipv6.ToLowerInvariant () == "ipv6:") {
278 index += "IPv6:".Length;
279 if (!SkipIPv6Literal (email, ref index))
282 if (!SkipIPv4Literal (email, ref index))
286 if (index >= email.Length || email[index++] != ']')
289 return index == email.Length;
292 public EmailAddressAttribute ()
293 : base(DataType.EmailAddress)
295 // XXX: There is no .ctor accepting Func<string> on DataTypeAttribute.. :?
296 base.ErrorMessage = DefaultErrorMessage;
299 public override bool IsValid(object value)
304 string email = value as string;
308 return Validate (email);