[mono/mono] c82aea5f: [System.ComponentModel.DataAnnotations] Implemented EmailAddressAttribute.IsValid()

"Jeffrey Stedfast ([email protected])" <[email protected]>
Newsgroups gmane.comp.gnome.mono.patches
Message-ID <0000014194e98927-24fa06c3-d960-4371-8048-45def97df1ed-000000@email.amazonses.com>
   Branch: refs/heads/master
     Home: https://github.com/mono/mono
  Compare: https://github.com/mono/mono/compare/8d401cb39dcd...c82aea5fb21a

   Commit: c82aea5fb21ad1d47f2b3c118403bce0f2c10938
   Author: Jeffrey Stedfast <[email protected]> (jstedfast)
     Date: 2013-10-07 21:51:17 GMT
      URL: https://github.com/mono/mono/commit/c82aea5fb21ad1d47f2b3c118403bce0f2c10938

[System.ComponentModel.DataAnnotations] Implemented EmailAddressAttribute.IsValid()

Changed paths:
  M mcs/class/System.ComponentModel.DataAnnotations/System.ComponentModel.DataAnnotations/EmailAddressAttribute.cs
  M mcs/class/System.ComponentModel.DataAnnotations/Test/System.ComponentModel.DataAnnotations/EmailAddressAttributeTest.cs

Modified: mcs/class/System.ComponentModel.DataAnnotations/System.ComponentModel.DataAnnotations/EmailAddressAttribute.cs
===================================================================
@@ -40,6 +40,254 @@ namespace System.ComponentModel.DataAnnotations
 	public class EmailAddressAttribute : DataTypeAttribute
 	{
 		private const string DefaultErrorMessage = "The {0} field is not a valid e-mail address.";
+		const string AtomCharacters = "!#$%&'*+-/=?^_`{|}~";
+
+		static bool IsLetterOrDigit (char c)
+		{
+			return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
+		}
+
+		static bool IsAtom (char c)
+		{
+			return IsLetterOrDigit (c) || AtomCharacters.IndexOf (c) != -1;
+		}
+
+		static bool IsDomain (char c)
+		{
+			return IsLetterOrDigit (c) || c == '-';
+		}
+
+		static bool SkipAtom (string text, ref int index)
+		{
+			int startIndex = index;
+
+			while (index < text.Length && IsAtom (text[index]))
+				index++;
+
+			return index > startIndex;
+		}
+
+		static bool SkipSubDomain (string text, ref int index)
+		{
+			if (!IsDomain (text[index]) || text[index] == '-')
+				return false;
+
+			index++;
+
+			while (index < text.Length && IsDomain (text[index]))
+				index++;
+
+			return true;
+		}
+
+		static bool SkipDomain (string text, ref int index)
+		{
+			if (!SkipSubDomain (text, ref index))
+				return false;
+
+			while (index < text.Length && text[index] == '.') {
+				index++;
+
+				if (index == text.Length)
+					return false;
+
+				if (!SkipSubDomain (text, ref index))
+					return false;
+			}
+
+			return true;
+		}
+
+		static bool SkipQuoted (string text, ref int index)
+		{
+			bool escaped = false;
+
+			// skip over leading '"'
+			index++;
+
+			while (index < text.Length) {
+				if (text[index] == (byte) '\\') {
+					escaped = !escaped;
+				} else if (!escaped) {
+					if (text[index] == (byte) '"')
+						break;
+				} else {
+					escaped = false;
+				}
+
+				index++;
+			}
+
+			if (index >= text.Length || text[index] != (byte) '"')
+				return false;
+
+			index++;
+
+			return true;
+		}
+
+		static bool SkipWord (string text, ref int index)
+		{
+			if (text[index] == (byte) '"')
+				return SkipQuoted (text, ref index);
+
+			return SkipAtom (text, ref index);
+		}
+
+		static bool SkipIPv4Literal (string text, ref int index)
+		{
+			int groups = 0;
+
+			while (index < text.Length && groups < 4) {
+				int startIndex = index;
+				int value = 0;
+
+				while (index < text.Length && text[index] >= '0' && text[index] <= '9') {
+					value = (value * 10) + (text[index] - '0');
+					index++;
+				}
+
+				if (index == startIndex || index - startIndex > 3 || value > 255)
+					return false;
+
+				groups++;
+
+				if (groups < 4 && index < text.Length && text[index] == '.')
+					index++;
+			}
+
+			return groups == 4;
+		}
+
+		static bool IsHexDigit (char c)
+		{
+			return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
+		}
+
+		// This needs to handle the following forms:
+		//
+		// IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
+		// IPv6-hex  = 1*4HEXDIG
+		// IPv6-full = IPv6-hex 7(":" IPv6-hex)
+		// IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::" [IPv6-hex *5(":" IPv6-hex)]
+		//             ; The "::" represents at least 2 16-bit groups of zeros
+		//             ; No more than 6 groups in addition to the "::" may be
+		//             ; present
+		// IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
+		// IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
+		//               [IPv6-hex *3(":" IPv6-hex) ":"] IPv4-address-literal
+		//             ; The "::" represents at least 2 16-bit groups of zeros
+		//             ; No more than 4 groups in addition to the "::" and
+		//             ; IPv4-address-literal may be present
+		static bool SkipIPv6Literal (string text, ref int index)
+		{
+			bool compact = false;
+			int colons = 0;
+
+			while (index < text.Length) {
+				int startIndex = index;
+
+				while (index < text.Length && IsHexDigit (text[index]))
+					index++;
+
+				if (index >= text.Length)
+					break;
+
+				if (index > startIndex && colons > 2 && text[index] == '.') {
+					// IPv6v4
+					index = startIndex;
+
+					if (!SkipIPv4Literal (text, ref index))
+						return false;
+
+					break;
+				}
+
+				int count = index - startIndex;
+				if (count > 4)
+					return false;
+
+				if (text[index] != ':')
+					break;
+
+				startIndex = index;
+				while (index < text.Length && text[index] == ':')
+					index++;
+
+				count = index - startIndex;
+				if (count > 2)
+					return false;
+
+				if (count == 2) {
+					if (compact)
+						return false;
+
+					compact = true;
+					colons += 2;
+				} else {
+					colons++;
+				}
+			}
+
+			if (colons < 2)
+				return false;
+
+			if (compact)
+				return colons < 6;
+
+			return colons < 7;
+		}
+
+		static bool Validate (string email)
+		{
+			int index = 0;
+
+			if (email.Length == 0)
+				return false;
+
+			if (!SkipWord (email, ref index) || index >= email.Length)
+				return false;
+
+			while (index < email.Length && email[index] == '.') {
+				index++;
+
+				if (!SkipWord (email, ref index) || index >= email.Length)
+					return false;
+			}
+
+			if (index + 1 >= email.Length || email[index++] != '@')
+				return false;
+
+			if (email[index] != '[') {
+				// domain
+				if (!SkipDomain (email, ref index))
+					return false;
+
+				return index == email.Length;
+			}
+
+			// address literal
+			index++;
+
+			// we need at least 8 more characters
+			if (index + 8 >= email.Length)
+				return false;
+
+			var ipv6 = email.Substring (index, 5);
+			if (ipv6.ToLowerInvariant () == "ipv6:") {
+				index += "IPv6:".Length;
+				if (!SkipIPv6Literal (email, ref index))
+					return false;
+			} else {
+				if (!SkipIPv4Literal (email, ref index))
+					return false;
+			}
+
+			if (index >= email.Length || email[index++] != ']')
+				return false;
+
+			return index == email.Length;
+		}
 
 		public EmailAddressAttribute ()
 			: base(DataType.EmailAddress)
@@ -53,10 +301,11 @@ public override bool IsValid(object value)
 			if (value == null)
 				return true;
 
-			if (value is string)
-				return true;
+			string email = value as string;
+			if (email == null)
+				return false;
 
-			return false;
+			return Validate (email);
 		}
 	}
 }

Modified: mcs/class/System.ComponentModel.DataAnnotations/Test/System.ComponentModel.DataAnnotations/EmailAddressAttributeTest.cs
===================================================================
@@ -40,20 +40,47 @@ namespace MonoTests.System.ComponentModel.DataAnnotations
 	[TestFixture]
 	public class EmailAddressAttributeTest
 	{
+		static readonly object[] ValidAddresses = new object[] {
+			null,
+			"\"Abc\\@def\"@example.com",
+			"\"Fred Bloggs\"@example.com",
+			"\"Joe\\\\Blow\"@example.com",
+			"\"Abc@def\"@example.com",
+			"customer/[email protected]",
+			"[email protected]",
+			"!def!xyz%[email protected]",
+			"[email protected]",
+			"valid.ipv4.addr@[123.1.72.10]",
+			"valid.ipv6.addr@[IPv6:0::1]",
+			"valid.ipv6.addr@[IPv6:2607:f0d0:1002:51::4]",
+			"valid.ipv6.addr@[IPv6:fe80::230:48ff:fe33:bc33]",
+			"valid.ipv6v4.addr@[IPv6:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:127.0.0.1]",
+		};
+
+		static readonly object[] InvalidAddresses = new object[] {
+			"",
+			123,
+			DateTime.Now,
+			"invalid",
+			"invalid@",
+			"invalid @",
+			"invalid@[555.666.777.888]",
+			"invalid@[IPv6:123456]",
+			"invalid@[127.0.0.1.]",
+			"invalid@[127.0.0.1].",
+			"invalid@[127.0.0.1]x",
+		};
+
 		[Test]
 		public void IsValid ()
 		{
 			var sla = new EmailAddressAttribute ();
 
-			Assert.IsTrue (sla.IsValid (null), "#A1-1");
-#if false
-			Assert.IsFalse (sla.IsValid (String.Empty), "#A1-2");
-			Assert.IsFalse (sla.IsValid ("string"), "#A1-3");
-#endif
-			Assert.IsTrue (sla.IsValid ("[email protected]"), "#A1-4");
-			Assert.IsTrue (sla.IsValid ("[email protected]"), "#A1-5");
-			Assert.IsFalse (sla.IsValid (123), "#A1-6");
-			Assert.IsFalse (sla.IsValid (DateTime.Now), "#A1-7");
+			for (int i = 0; i < ValidAddresses.Length; i++)
+				Assert.IsTrue (sla.IsValid (ValidAddresses[i]), "#A1-{0}", i);
+
+			for (int i = 0; i < InvalidAddresses.Length; i++)
+				Assert.IsFalse (sla.IsValid (InvalidAddresses[i]), "#B1-{0}", i);
 		}
 	}
 #endif


_______________________________________________
Mono-patches maillist  -  [email protected]
http://lists.ximian.com/mailman/listinfo/mono-patches
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.