From f14adaff66e3129acf7d828bce9c85054018f47f Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Fri, 3 Jul 2026 13:24:05 +0200 Subject: [PATCH] Reject CR/LF in MailAddress parsing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Net/Mail/MailAddressParser.cs | 14 ++++++++ .../Functional/SmtpClientSendMailTest.cs | 8 ++--- .../MailAddressTests/MailAddressParserTest.cs | 34 +++++++++++++++++++ .../MailAddressParsingTest.cs | 11 +++--- 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddressParser.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddressParser.cs index c3479598be7466..0770f80b72dbc7 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddressParser.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddressParser.cs @@ -69,6 +69,20 @@ 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}"); + // Check for CR or LF characters which are not allowed in mail addresses. + // Only scan on the first call (index == data.Length - 1) to avoid repeated O(n) scans + // when parsing multiple addresses from the same string. + if (index == data.Length - 1 && 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; diff --git a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientSendMailTest.cs b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientSendMailTest.cs index 00da56ee4f646f..95a1beb9c68d3f 100644 --- a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientSendMailTest.cs +++ b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientSendMailTest.cs @@ -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(msg); + Assert.Throws(() => new MailMessage(@from, @to, "subject", "body")); } public static IEnumerable SendMail_MultiLineDomainLiterals_Data() diff --git a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressParserTest.cs b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressParserTest.cs index b52a60ee14ef2f..1648320bc5a6fb 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressParserTest.cs +++ b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressParserTest.cs @@ -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 ")] + [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(() => 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 ")] + [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(() => MailAddressParser.ParseMultipleAddresses("a@b.com, test\r\n@example.com")); + Assert.Throws(() => MailAddressParser.ParseMultipleAddresses("test\n@example.com, a@b.com")); + Assert.Throws(() => MailAddressParser.ParseMultipleAddresses("a@b.com, c@d.com, e\r@f.com")); + } } } diff --git a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressParsingTest.cs b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressParsingTest.cs index b0436e77476e8c..0f91df3d42e2ad 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressParsingTest.cs +++ b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressParsingTest.cs @@ -36,8 +36,7 @@ public static IEnumerable 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 " }; yield return new object[] { "Test\u3044\u3069 User " }; @@ -88,8 +87,8 @@ public static IEnumerable GetValidEmailTestData() yield return new object[] { "\"disp \f lay\" <\"testsome\"@NCLMailTest.com>" }; yield return new object[] { "\"EscapedUnicode \\\u3044\\\u3069 display\" " }; 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>" }; @@ -125,6 +124,10 @@ public static IEnumerable GetInvalidEmailTestData() yield return new object[] { "Bob \"display\" " }; 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