Skip to content

Commit be98e88

Browse files
Tomas Weinfurtmmitche
Tomas Weinfurt
authored andcommitted
Merged PR 21497: [release/6.0] MSRC 68590 - newlines in domain literals
This add validation for embedded newlines in email addresses. Based on https://dev.azure.com/dnceng/internal/_git/dotnet-runtime/pullrequest/20738 There is opt-in System.Net.Mail.EnableFullDomainLiterals switch to allow previous behavior
1 parent 1cb7505 commit be98e88

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

+8
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

+1
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

+9
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;
@@ -219,6 +222,12 @@ private string GetHost(bool allowUnicode)
219222
throw new SmtpException(SR.Format(SR.SmtpInvalidHostName, Address), argEx);
220223
}
221224
}
225+
226+
if (!EnableFullDomainLiterals && domain.AsSpan().IndexOfAny('\r', '\n') >= 0)
227+
{
228+
throw new SmtpException(SR.Format(SR.SmtpInvalidHostName, Address));
229+
}
230+
222231
return domain;
223232
}
224233

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

+59
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 Xunit;
1923

@@ -523,5 +527,60 @@ public async Task SendMail_SendQUITOnDispose(bool asyncSend)
523527
quitReceived.Wait(TimeSpan.FromSeconds(30));
524528
Assert.True(quitMessageReceived, "QUIT message not received");
525529
}
530+
531+
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
532+
[InlineData("foo@[\r\n bar]")]
533+
[InlineData("foo@[bar\r\n ]")]
534+
[InlineData("foo@[bar\r\n baz]")]
535+
public void MultiLineDomainLiterals_Enabled_Success(string input)
536+
{
537+
RemoteExecutor.Invoke(static (string @input) =>
538+
{
539+
AppContext.SetSwitch("System.Net.AllowFullDomainLiterals", true);
540+
541+
var address = new MailAddress(@input);
542+
543+
// Using address with new line breaks the protocol so we cannot easily use LoopbackSmtpServer
544+
// Instead we call internal method that does the extra validation.
545+
string? host = (string?)typeof(MailAddress).InvokeMember("GetAddress", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, address, new object[] { true });
546+
Assert.Equal(input, host);
547+
}, input).Dispose();
548+
}
549+
550+
[Theory]
551+
[MemberData(nameof(SendMail_MultiLineDomainLiterals_Data))]
552+
public async Task SendMail_MultiLineDomainLiterals_Disabled_Throws(string from, string to, bool asyncSend)
553+
{
554+
using var server = new LoopbackSmtpServer();
555+
556+
using SmtpClient client = server.CreateClient();
557+
client.Credentials = new NetworkCredential("Foo", "Bar");
558+
559+
using var msg = new MailMessage(@from, @to, "subject", "body");
560+
561+
await Assert.ThrowsAsync<SmtpException>(async () =>
562+
{
563+
if (asyncSend)
564+
{
565+
await client.SendMailAsync(msg).WaitAsync(TimeSpan.FromSeconds(30));
566+
}
567+
else
568+
{
569+
client.Send(msg);
570+
}
571+
});
572+
}
573+
574+
public static IEnumerable<object[]> SendMail_MultiLineDomainLiterals_Data()
575+
{
576+
foreach (bool async in new[] { true, false })
577+
{
578+
foreach (string address in new[] { "foo@[\r\n bar]", "foo@[bar\r\n ]", "foo@[bar\r\n baz]" })
579+
{
580+
yield return new object[] { address, "[email protected]", async };
581+
yield return new object[] { "[email protected]", address, async };
582+
}
583+
}
584+
}
526585
}
527586
}

0 commit comments

Comments
 (0)