diff --git a/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/EmailAddressAttribute.cs b/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/EmailAddressAttribute.cs index 2e28b0cb6ac3d..de5a86a64ac8c 100644 --- a/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/EmailAddressAttribute.cs +++ b/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/EmailAddressAttribute.cs @@ -7,6 +7,9 @@ namespace System.ComponentModel.DataAnnotations AllowMultiple = false)] public sealed class EmailAddressAttribute : DataTypeAttribute { + private static bool EnableFullDomainLiterals { get; } = + AppContext.TryGetSwitch("System.Net.AllowFullDomainLiterals", out bool enable) ? enable : false; + public EmailAddressAttribute() : base(DataType.EmailAddress) { @@ -27,6 +30,11 @@ public override bool IsValid(object? value) return false; } + if (!EnableFullDomainLiterals && (valueAsString.Contains('\r') || valueAsString.Contains('\n'))) + { + return false; + } + // only return true if there is only 1 '@' character // and it is neither the first nor the last character int index = valueAsString.IndexOf('@'); diff --git a/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/EmailAddressAttributeTests.cs b/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/EmailAddressAttributeTests.cs index a0c67ed7d2b92..da80016608e40 100644 --- a/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/EmailAddressAttributeTests.cs +++ b/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/EmailAddressAttributeTests.cs @@ -29,6 +29,7 @@ protected override IEnumerable InvalidValues() yield return new TestCase(new EmailAddressAttribute(), 0); yield return new TestCase(new EmailAddressAttribute(), ""); yield return new TestCase(new EmailAddressAttribute(), " \r \t \n" ); + yield return new TestCase(new EmailAddressAttribute(), "someName@[\r\n\tsomeDomain]"); yield return new TestCase(new EmailAddressAttribute(), "@someDomain.com"); yield return new TestCase(new EmailAddressAttribute(), "@someDomain@abc.com"); yield return new TestCase(new EmailAddressAttribute(), "someName"); diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddress.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddress.cs index f5ea92bf4e837..541deb3c95576 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddress.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddress.cs @@ -15,6 +15,9 @@ namespace System.Net.Mail // public partial class MailAddress { + private static bool EnableFullDomainLiterals { get; } = + AppContext.TryGetSwitch("System.Net.AllowFullDomainLiterals", out bool enable) ? enable : false; + // These components form an e-mail address when assembled as follows: // "EncodedDisplayname" private readonly Encoding _displayNameEncoding; @@ -219,6 +222,12 @@ private string GetHost(bool allowUnicode) throw new SmtpException(SR.Format(SR.SmtpInvalidHostName, Address), argEx); } } + + if (!EnableFullDomainLiterals && domain.AsSpan().IndexOfAny('\r', '\n') >= 0) + { + throw new SmtpException(SR.Format(SR.SmtpInvalidHostName, Address)); + } + return domain; } diff --git a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs index bd0fb5298432d..f635b0d67ac8e 100644 --- a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs +++ b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs @@ -9,11 +9,15 @@ // (C) 2006 John Luke // +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Net.NetworkInformation; using System.Net.Sockets; +using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; using Systen.Net.Mail.Tests; using Xunit; @@ -523,5 +527,60 @@ public async Task SendMail_SendQUITOnDispose(bool asyncSend) quitReceived.Wait(TimeSpan.FromSeconds(30)); Assert.True(quitMessageReceived, "QUIT message not received"); } + + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [InlineData("foo@[\r\n bar]")] + [InlineData("foo@[bar\r\n ]")] + [InlineData("foo@[bar\r\n baz]")] + public void MultiLineDomainLiterals_Enabled_Success(string input) + { + RemoteExecutor.Invoke(static (string @input) => + { + AppContext.SetSwitch("System.Net.AllowFullDomainLiterals", true); + + var address = new MailAddress(@input); + + // Using address with new line breaks the protocol so we cannot easily use LoopbackSmtpServer + // Instead we call internal method that does the extra validation. + string? host = (string?)typeof(MailAddress).InvokeMember("GetAddress", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, address, new object[] { true }); + Assert.Equal(input, host); + }, input).Dispose(); + } + + [Theory] + [MemberData(nameof(SendMail_MultiLineDomainLiterals_Data))] + public async Task SendMail_MultiLineDomainLiterals_Disabled_Throws(string from, string to, bool asyncSend) + { + using var server = new LoopbackSmtpServer(); + + using SmtpClient client = server.CreateClient(); + client.Credentials = new NetworkCredential("Foo", "Bar"); + + using var msg = new MailMessage(@from, @to, "subject", "body"); + + await Assert.ThrowsAsync(async () => + { + if (asyncSend) + { + await client.SendMailAsync(msg).WaitAsync(TimeSpan.FromSeconds(30)); + } + else + { + client.Send(msg); + } + }); + } + + public static IEnumerable SendMail_MultiLineDomainLiterals_Data() + { + foreach (bool async in new[] { true, false }) + { + foreach (string address in new[] { "foo@[\r\n bar]", "foo@[bar\r\n ]", "foo@[bar\r\n baz]" }) + { + yield return new object[] { address, "foo@example.com", async }; + yield return new object[] { "foo@example.com", address, async }; + } + } + } } }