Skip to content

Commit 25139d0

Browse files
committed
newlines in domain literals
newlines in domain literals This adds validation for embedded newlines in email addresses. There is opt-in System.Net.Mail.EnableFullDomainLiterals switch to allow previous behavior
1 parent 025271a commit 25139d0

File tree

4 files changed

+77
-0
lines changed

4 files changed

+77
-0
lines changed

src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/EmailAddressAttribute.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ namespace System.ComponentModel.DataAnnotations
77
AllowMultiple = false)]
88
public sealed class EmailAddressAttribute : DataTypeAttribute
99
{
10+
private static bool EnableFullDomainLiterals { get; } =
11+
AppContext.TryGetSwitch("System.Net.AllowFullDomainLiterals", out bool enable) ? enable : false;
12+
1013
public EmailAddressAttribute()
1114
: base(DataType.EmailAddress)
1215
{
@@ -27,6 +30,11 @@ public override bool IsValid(object? value)
2730
return false;
2831
}
2932

33+
if (!EnableFullDomainLiterals && (valueAsString.Contains('\r') || valueAsString.Contains('\n')))
34+
{
35+
return false;
36+
}
37+
3038
// only return true if there is only 1 '@' character
3139
// and it is neither the first nor the last character
3240
int index = valueAsString.IndexOf('@');

src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/EmailAddressAttributeTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ protected override IEnumerable<TestCase> InvalidValues()
2929
yield return new TestCase(new EmailAddressAttribute(), 0);
3030
yield return new TestCase(new EmailAddressAttribute(), "");
3131
yield return new TestCase(new EmailAddressAttribute(), " \r \t \n" );
32+
yield return new TestCase(new EmailAddressAttribute(), "someName@[\r\n\tsomeDomain]");
3233
yield return new TestCase(new EmailAddressAttribute(), "@someDomain.com");
3334
yield return new TestCase(new EmailAddressAttribute(), "@[email protected]");
3435
yield return new TestCase(new EmailAddressAttribute(), "someName");

src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddress.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ namespace System.Net.Mail
1515
//
1616
public partial class MailAddress
1717
{
18+
private static bool EnableFullDomainLiterals { get; } =
19+
AppContext.TryGetSwitch("System.Net.AllowFullDomainLiterals", out bool enable) ? enable : false;
20+
1821
// These components form an e-mail address when assembled as follows:
1922
// "EncodedDisplayname" <userName@host>
2023
private readonly Encoding _displayNameEncoding;
@@ -216,6 +219,12 @@ private string GetHost(bool allowUnicode)
216219
throw new SmtpException(SR.Format(SR.SmtpInvalidHostName, Address), argEx);
217220
}
218221
}
222+
223+
if (!EnableFullDomainLiterals && domain.AsSpan().IndexOfAny('\r', '\n') >= 0)
224+
{
225+
throw new SmtpException(SR.Format(SR.SmtpInvalidHostName, Address));
226+
}
227+
219228
return domain;
220229
}
221230

src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@
99
// (C) 2006 John Luke
1010
//
1111

12+
using System.Collections.Generic;
13+
using System.Globalization;
1214
using System.IO;
1315
using System.Net.NetworkInformation;
1416
using System.Net.Sockets;
17+
using System.Reflection;
1518
using System.Threading;
1619
using System.Threading.Tasks;
20+
using Microsoft.DotNet.RemoteExecutor;
1721
using Systen.Net.Mail.Tests;
1822
using System.Net.Test.Common;
1923
using Xunit;
@@ -573,5 +577,60 @@ public void TestGssapiAuthentication()
573577

574578
Assert.Equal("GSSAPI", server.AuthMethodUsed, StringComparer.OrdinalIgnoreCase);
575579
}
580+
581+
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
582+
[InlineData("foo@[\r\n bar]")]
583+
[InlineData("foo@[bar\r\n ]")]
584+
[InlineData("foo@[bar\r\n baz]")]
585+
public void MultiLineDomainLiterals_Enabled_Success(string input)
586+
{
587+
RemoteExecutor.Invoke(static (string @input) =>
588+
{
589+
AppContext.SetSwitch("System.Net.AllowFullDomainLiterals", true);
590+
591+
var address = new MailAddress(@input);
592+
593+
// Using address with new line breaks the protocol so we cannot easily use LoopbackSmtpServer
594+
// Instead we call internal method that does the extra validation.
595+
string? host = (string?)typeof(MailAddress).InvokeMember("GetAddress", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, address, new object[] { true });
596+
Assert.Equal(input, host);
597+
}, input).Dispose();
598+
}
599+
600+
[Theory]
601+
[MemberData(nameof(SendMail_MultiLineDomainLiterals_Data))]
602+
public async Task SendMail_MultiLineDomainLiterals_Disabled_Throws(string from, string to, bool asyncSend)
603+
{
604+
using var server = new LoopbackSmtpServer();
605+
606+
using SmtpClient client = server.CreateClient();
607+
client.Credentials = new NetworkCredential("Foo", "Bar");
608+
609+
using var msg = new MailMessage(@from, @to, "subject", "body");
610+
611+
await Assert.ThrowsAsync<SmtpException>(async () =>
612+
{
613+
if (asyncSend)
614+
{
615+
await client.SendMailAsync(msg).WaitAsync(TimeSpan.FromSeconds(30));
616+
}
617+
else
618+
{
619+
client.Send(msg);
620+
}
621+
});
622+
}
623+
624+
public static IEnumerable<object[]> SendMail_MultiLineDomainLiterals_Data()
625+
{
626+
foreach (bool async in new[] { true, false })
627+
{
628+
foreach (string address in new[] { "foo@[\r\n bar]", "foo@[bar\r\n ]", "foo@[bar\r\n baz]" })
629+
{
630+
yield return new object[] { address, "[email protected]", async };
631+
yield return new object[] { "[email protected]", address, async };
632+
}
633+
}
634+
}
576635
}
577636
}

0 commit comments

Comments
 (0)