diff --git a/src/Core/Models/Api/Request/Accounts/EmailTokenRequestModel.cs b/src/Core/Models/Api/Request/Accounts/EmailTokenRequestModel.cs index 4dcd9608b1..1228fa9b9c 100644 --- a/src/Core/Models/Api/Request/Accounts/EmailTokenRequestModel.cs +++ b/src/Core/Models/Api/Request/Accounts/EmailTokenRequestModel.cs @@ -1,11 +1,12 @@ using System.ComponentModel.DataAnnotations; +using Bit.Core.Utilities; namespace Bit.Core.Models.Api { public class EmailTokenRequestModel { [Required] - [EmailAddress] + [StrictEmailAddress] [StringLength(256)] public string NewEmail { get; set; } [Required] diff --git a/src/Core/Utilities/StrictEmailAddressAttribute.cs b/src/Core/Utilities/StrictEmailAddressAttribute.cs index 870c45f305..0145e8c5ad 100644 --- a/src/Core/Utilities/StrictEmailAddressAttribute.cs +++ b/src/Core/Utilities/StrictEmailAddressAttribute.cs @@ -1,6 +1,5 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Text.RegularExpressions; +using System.ComponentModel.DataAnnotations; +using MimeKit; namespace Bit.Core.Utilities { @@ -18,12 +17,19 @@ namespace Bit.Core.Utilities return false; } - var illegalChars = @"[\s<>()]"; - if (Regex.IsMatch(emailAddress, illegalChars)) + try + { + var parsedEmailAddress = MailboxAddress.Parse(emailAddress).Address; + if (parsedEmailAddress != emailAddress) + { + return false; + } + } + catch (ParseException e) { return false; } - + return new EmailAddressAttribute().IsValid(emailAddress); } } diff --git a/test/Core.Test/Utilities/StrictEmailAddressAttributeTests.cs b/test/Core.Test/Utilities/StrictEmailAddressAttributeTests.cs new file mode 100644 index 0000000000..7ad4e2c0bf --- /dev/null +++ b/test/Core.Test/Utilities/StrictEmailAddressAttributeTests.cs @@ -0,0 +1,51 @@ +using Bit.Core.Utilities; +using Xunit; + +namespace Bit.Core.Test.Utilities +{ + public class StrictEmailAttributeTests + { + [Theory] + [InlineData("hello@world.com")] // regular email address + [InlineData("hello@world.planet.com")] // subdomain + [InlineData("hello+1@world.com")] // alias + [InlineData("hello.there@world.com")] // period in local-part + public void IsValid_ReturnsTrueWhenValid(string email) + { + var sut = new StrictEmailAddressAttribute(); + + var actual = sut.IsValid(email); + + Assert.True(actual); + } + + [Theory] + [InlineData(null)] // null + [InlineData("hello@world.com\t")] // trailing tab char + [InlineData("\thello@world.com")] // leading tab char + [InlineData("hel\tlo@world.com")] // local-part tab char + [InlineData("hello@world.com\b")] // trailing backspace char + [InlineData("\" \"hello@world.com")] // leading spaces in quotes + [InlineData("hello@world.com\" \"")] // trailing spaces in quotes + [InlineData("hel\" \"lo@world.com")] // local-part spaces in quotes + [InlineData("hello there@world.com")] // unescaped unquoted spaces + [InlineData("Hello ")] // friendly from + [InlineData("")] // wrapped angle brackets + [InlineData("hello(com)there@world.com")] // comment + [InlineData("hello@world.com.")] // trailing period + [InlineData(".hello@world.com")] // leading period + [InlineData("hello@world.com;")] // trailing semicolon + [InlineData(";hello@world.com")] // leading semicolon + [InlineData("hello@world.com; hello@world.com")] // semicolon separated list + [InlineData("hello@world.com, hello@world.com")] // comma separated list + + public void IsValid_ReturnsFalseWhenInvalid(string email) + { + var sut = new StrictEmailAddressAttribute(); + + var actual = sut.IsValid(email); + + Assert.False(actual); + } + } +}