Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,20 @@ private static bool TryParse([NotNullWhen(true)] string? address, string? displa
return false;
}

// Reject CR/LF in caller-supplied display names. Such characters would corrupt
// the encoded mail header when the address is serialized into a message
// (e.g. From/To headers written by SmtpClient).
if (MailBnfHelper.HasCROrLF(displayName))
{
if (throwExceptionIfFail)
{
throw new FormatException(SR.MailAddressInvalidFormat);
}

parsedData = default;
return false;
}

if (displayName.Length >= 2 && displayName.StartsWith('\"') && displayName.EndsWith('\"'))
{
// Peel bounding quotes, they'll get re-added later.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ private static bool TryParseAddress(string data, bool expectMultipleAddresses, r
Debug.Assert(!string.IsNullOrEmpty(data));
Debug.Assert(index >= 0 && index < data.Length, $"Index out of range: {index}, {data.Length}");

// Reverse parsing starts from the last character of the input in the public entry points.
// ParseMultipleAddresses decrements index after each parsed address, so only this initial
// reverse-parse call performs the O(n) CR/LF scan for the full input string.
if (index == data.Length - 1)
{
if (MailBnfHelper.HasCROrLF(data))
{
if (throwExceptionIfFail)
{
throw new FormatException(SR.MailAddressInvalidFormat);
}

parseAddressInfo = default;
return false;
}
}

// Parsed components to be assembled as a MailAddress later
string? displayName;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,9 @@ public async Task TestMultipleMailDelivery()

[Theory]
[MemberData(nameof(SendMail_MultiLineDomainLiterals_Data))]
public async Task MultiLineDomainLiterals_Disabled_Throws(string from, string to)
public void MultiLineDomainLiterals_Disabled_Throws(string from, string to)
{
Smtp.Credentials = new NetworkCredential("Foo", "Bar");

using var msg = new MailMessage(@from, @to, "subject", "body");

await SendMail<SmtpException>(msg);
Assert.Throws<FormatException>(() => new MailMessage(@from, @to, "subject", "body"));
}

public static IEnumerable<object[]> SendMail_MultiLineDomainLiterals_Data()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,21 @@ public void MailAddress_Ctor_FullString_Succeeds(string address, string displayN
Assert.Equal(expectedDisplayName ?? displayName, mailAddress.DisplayName);
Assert.Equal(expectedToString, mailAddress.ToString());
}

[Theory]
[InlineData("Display\rName")]
[InlineData("Display\nName")]
[InlineData("Display\r\nName")]
[InlineData("DisplayName\r")]
[InlineData("DisplayName\n")]
[InlineData("DisplayName\r\n")]
[InlineData("\rDisplayName")]
[InlineData("\nDisplayName")]
[InlineData("\"Display\r\nName\"")]
public void MailAddress_Ctor_DisplayNameContainsCRLF_Throws(string displayName)
{
Assert.Throws<FormatException>(() => new MailAddress(Address, displayName));
Assert.False(MailAddress.TryCreate(Address, displayName, out _));
}
}
Comment on lines +65 to 80
}
Original file line number Diff line number Diff line change
Expand Up @@ -533,5 +533,39 @@ public void ParseAddresses_WithManyComplexAddresses_ShouldReadCorrectly()
Assert.Equal("\" asciin;,oqu o.tesws \"", result[5].User);
Assert.Equal("this.test.this", result[5].Host);
}

[Theory]
[InlineData("test\r@example.com")]
[InlineData("test\n@example.com")]
[InlineData("test\r\n@example.com")]
[InlineData("Display\r\nName <test@example.com>")]
[InlineData("test@example\r.com")]
[InlineData("test@example\n.com")]
[InlineData("test@example.com\r\n")]
public void TryParseAddress_WithCROrLF_ShouldThrow(string address)
{
Assert.Throws<FormatException>(() => MailAddressParser.TryParseAddress(address, out _, throwExceptionIfFail: true));
}

[Theory]
[InlineData("test\r@example.com")]
[InlineData("test\n@example.com")]
[InlineData("test\r\n@example.com")]
[InlineData("Display\r\nName <test@example.com>")]
[InlineData("test@example\r.com")]
[InlineData("test@example\n.com")]
[InlineData("test@example.com\r\n")]
public void TryParseAddress_WithCROrLF_ShouldReturnFalse(string address)
{
Assert.False(MailAddressParser.TryParseAddress(address, out _, throwExceptionIfFail: false));
}

[Fact]
public void ParseMultipleAddresses_WithCROrLF_ShouldThrow()
{
Assert.Throws<FormatException>(() => MailAddressParser.ParseMultipleAddresses("a@b.com, test\r\n@example.com"));
Assert.Throws<FormatException>(() => MailAddressParser.ParseMultipleAddresses("test\n@example.com, a@b.com"));
Assert.Throws<FormatException>(() => MailAddressParser.ParseMultipleAddresses("a@b.com, c@d.com, e\r@f.com"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ public static IEnumerable<object[]> GetValidEmailTestData()
yield return new object[] { "testuser@[mail.com]" };
yield return new object[] { "testuser@[ mail.com] " };
yield return new object[] { "testuser@[mail.com ]" };
yield return new object[] { "testuser@[mail.com \r\n ]" };
yield return new object[] { "testuser@[ \r\n mail.com]" };
yield return new object[] { "testuser@[mail.com ]" };
yield return new object[] { "testuser@[ mail.com]" };
yield return new object[] { "testuser <testuser@mail.com>" };
yield return new object[] { "Test\u3044\u3069 User <testUser1@NCLMailTest.com>" };
Expand Down Expand Up @@ -88,8 +87,8 @@ public static IEnumerable<object[]> GetValidEmailTestData()
yield return new object[] { "\"disp \f lay\" <\"testsome\"@NCLMailTest.com>" };
yield return new object[] { "\"EscapedUnicode \\\u3044\\\u3069 display\" <testUser1@NCLMailTest.com>" };
yield return new object[] { "(Unicode \u3044 Comment) <\"testsome\"@NCLMailTest.com>" };
yield return new object[] { "\"display \r\n name\" <\"folding\"@domain.com>" };
yield return new object[] { "\"test\r\n test\"@mail.com" };
yield return new object[] { "\"display name\" <\"folding\"@domain.com>" };
yield return new object[] { "\"test test\"@mail.com" };
// Email Address Internationalization (EAI)
yield return new object[] { "UnicodeUserName \"Test\u3044\u3069\"@NCLMailTest.com" };
yield return new object[] { "<\"EscapedUnicode \\\u3044\\\u3069 User\"@NCLMailTest.com>" };
Expand Down Expand Up @@ -125,9 +124,12 @@ public static IEnumerable<object[]> GetInvalidEmailTestData()
yield return new object[] { "Bob \"display\" <user@host>" };
yield return new object[] { "testuser@[mail.com \r ]" };
yield return new object[] { "testuser@[mail.com \n ]" };
yield return new object[] { "testuser@[mail.com \r\n ]" };
yield return new object[] { "testuser@[ \r\n mail.com]" };
yield return new object[] { "\"display \r\n name\" <\"folding\"@domain.com>" };
yield return new object[] { "\"test\r\n test\"@mail.com" };
yield return new object[] { "testuser@[mail\u3069.com]" }; // No unicode allowed in square brackets
yield return new object[] { "invalid@unicode\uD800.com" }; // D800 is a high surrogate
yield return new object[] { "invalid@unicode\uD800.com" }; // D800 is a high surrogate
yield return new object[] { "invalid\uD800@unicode.com" }; // D800 is a high surrogate
yield return new object[] { "\uD800 invalid@unicode.com" }; // D800 is a high surrogate
yield return new object[] { null };
Expand Down