From 61c2db7eb3d1d7449ad6b0de981fb3101b03873f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 11:51:20 +0000 Subject: [PATCH 1/6] Initial plan From 445e8bab2cbc182354d5cda799337c1892285ca3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 12:19:44 +0000 Subject: [PATCH 2/6] Escape quotes in MailAddress.Encode display name Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../src/System/Net/Mail/MailAddress.cs | 4 ++- .../MailAddressTests/MailAddressEncodeTest.cs | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) 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 a4cc338453b8ed..ac63566008254e 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 @@ -299,7 +299,9 @@ internal string Encode(int charsConsumed, bool allowUnicode) //be appended. if (MimeBasePart.IsAscii(_displayName, false) || allowUnicode) { - encodedAddress = "\"" + _displayName + "\""; + // Escape any embedded quotes so the display name forms a valid + // RFC 5322 quoted-string (e.g. Henry "The Fonz" Winkler). + encodedAddress = "\"" + _displayName.Replace("\"", "\\\"") + "\""; } else { diff --git a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs index d96fc787ccbd04..614a0791bdb9ee 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs +++ b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs @@ -214,6 +214,37 @@ public void EncodeMultipleMailAddress_WithManyAddressesThatAreDifferentAndContai + " \"test\u00DC\" ", result); } + [Theory] + [MemberData(nameof(MailAddressFactory_AddressDisplayName))] + public void EncodeSingleMailAddress_WithAddressAndDisplayNameContainingQuotes_ShouldEscapeQuotes(Func factory) + { + // Regression test for https://github.com/dotnet/runtime/issues/52439: + // embedded double quotes in the DisplayName must be escaped as quoted-pairs + // when produced for SMTP headers, otherwise the header is corrupted. + MailAddress testAddress = factory("test@example.com", "Henry \"The Fonz\" Winkler"); + + string result = testAddress.Encode(0, false); + Assert.Equal("\"Henry \\\"The Fonz\\\" Winkler\" ", result); + + result = testAddress.Encode(0, true); + Assert.Equal("\"Henry \\\"The Fonz\\\" Winkler\" ", result); + } + + [Theory] + [MemberData(nameof(MailAddressFactory_AddressDisplayName))] + public void EncodeMultipleMailAddress_WithDisplayNameContainingQuotes_ShouldEscapeQuotes(Func factory) + { + MailAddress testAddress = factory("test@example.com", "Henry \"The Fonz\" Winkler"); + MailAddressCollection collection = new MailAddressCollection(); + collection.Add(testAddress); + + string result = collection.Encode(0, false); + Assert.Equal("\"Henry \\\"The Fonz\\\" Winkler\" ", result); + + result = collection.Encode(0, true); + Assert.Equal("\"Henry \\\"The Fonz\\\" Winkler\" ", result); + } + [Fact] public void CustomEncoding_Null() { From f2fdba85648da98337c06a8566931aa15966e677 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 13:45:05 +0000 Subject: [PATCH 3/6] Also escape backslashes in MailAddress display name Co-authored-by: mrek-msft <188900745+mrek-msft@users.noreply.github.com> --- .../src/System/Net/Mail/MailAddress.cs | 14 +++-- .../MailAddressDisplayNameTest.cs | 9 +++- .../MailAddressTests/MailAddressEncodeTest.cs | 53 +++++++++++++------ 3 files changed, 53 insertions(+), 23 deletions(-) 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 ac63566008254e..31006d72849709 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 @@ -264,7 +264,10 @@ public override string ToString() } else { - return "\"" + DisplayName.Replace("\"", "\\\"") + "\" " + SmtpAddress; + // Escape backslashes first, then quotes, so the display name forms + // a valid RFC 5322 quoted-string. Order matters: escaping quotes + // first would also escape the backslashes introduced by it. + return "\"" + DisplayName.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\" " + SmtpAddress; } } @@ -299,9 +302,12 @@ internal string Encode(int charsConsumed, bool allowUnicode) //be appended. if (MimeBasePart.IsAscii(_displayName, false) || allowUnicode) { - // Escape any embedded quotes so the display name forms a valid - // RFC 5322 quoted-string (e.g. Henry "The Fonz" Winkler). - encodedAddress = "\"" + _displayName.Replace("\"", "\\\"") + "\""; + // Escape backslashes and embedded quotes so the display name + // forms a valid RFC 5322 quoted-string (e.g. Henry "The Fonz" + // Winkler or a name containing '\'). Order matters: escaping + // quotes first would also escape the backslashes introduced + // by it. + encodedAddress = "\"" + _displayName.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; } else { diff --git a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressDisplayNameTest.cs b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressDisplayNameTest.cs index 198ff9c24aad8a..e08151c9195b44 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressDisplayNameTest.cs +++ b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressDisplayNameTest.cs @@ -28,11 +28,16 @@ public static IEnumerable MailAddressTestDataQuotes() "Display \"Test\" Name", "Display \\\"Test\\\" Name", "\"", + "Display \\ Name", + "Display \\\\ Name", + "C:\\path\\to\\file", + "\\", }; foreach (var displayName in displayNamesWithQuotes) { - yield return new object[]{ Address, displayName, null, $"\"{displayName.Replace("\"", "\\\"")}\" <{Address}>" }; - yield return new object[]{ Address, $"\"{displayName}\"", displayName, $"\"{displayName.Replace("\"", "\\\"")}\" <{Address}>" }; + string escaped = displayName.Replace("\\", "\\\\").Replace("\"", "\\\""); + yield return new object[]{ Address, displayName, null, $"\"{escaped}\" <{Address}>" }; + yield return new object[]{ Address, $"\"{displayName}\"", displayName, $"\"{escaped}\" <{Address}>" }; } yield return new object[]{ Address, null, "", Address }; diff --git a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs index 614a0791bdb9ee..77a430f7a6fc11 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs +++ b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs @@ -136,8 +136,11 @@ public void EncodeSingleMailAddress_WithAddressAndNonAsciiAndRangeOfChars_Should + "\r\n =?utf-8?Q?=60abcdefghijklmnopqrstuvwxyz=7B=7C=7D=7E?= ", result); result = testAddress.Encode(0, true); + // The literal '\' in the input is RFC 5322-escaped as '\\' in the + // resulting quoted-string when allowUnicode permits the quoted-string + // form (i.e. no Q-encoding). Assert.Equal( - "\"\u00AE !#$%&'()+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\" " + "\"\u00AE !#$%&'()+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\" " + "", result); } @@ -214,35 +217,51 @@ public void EncodeMultipleMailAddress_WithManyAddressesThatAreDifferentAndContai + " \"test\u00DC\" ", result); } + public static IEnumerable DisplayNamesWithSpecialChars() + { + // displayName, expected encoded form (without the address suffix). + // Both '"' and '\' are RFC 5322 quoted-string specials and must be + // escaped as quoted-pairs ('\"' and '\\'). + yield return new object[] { "Henry \"The Fonz\" Winkler", "\"Henry \\\"The Fonz\\\" Winkler\"" }; + yield return new object[] { "with \" quote", "\"with \\\" quote\"" }; + yield return new object[] { "with \\ backslash", "\"with \\\\ backslash\"" }; + yield return new object[] { "with \\\" both", "\"with \\\\\\\" both\"" }; + yield return new object[] { "C:\\path\\to\\file", "\"C:\\\\path\\\\to\\\\file\"" }; + yield return new object[] { "\\", "\"\\\\\"" }; + yield return new object[] { "\"", "\"\\\"\"" }; + } + [Theory] - [MemberData(nameof(MailAddressFactory_AddressDisplayName))] - public void EncodeSingleMailAddress_WithAddressAndDisplayNameContainingQuotes_ShouldEscapeQuotes(Func factory) + [MemberData(nameof(DisplayNamesWithSpecialChars))] + public void EncodeSingleMailAddress_WithDisplayNameContainingSpecials_ShouldEscapeAsQuotedPairs(string displayName, string expectedEncodedDisplayName) { // Regression test for https://github.com/dotnet/runtime/issues/52439: - // embedded double quotes in the DisplayName must be escaped as quoted-pairs + // embedded '"' and '\' in the DisplayName must be escaped as quoted-pairs // when produced for SMTP headers, otherwise the header is corrupted. - MailAddress testAddress = factory("test@example.com", "Henry \"The Fonz\" Winkler"); + string expected = $"{expectedEncodedDisplayName} "; - string result = testAddress.Encode(0, false); - Assert.Equal("\"Henry \\\"The Fonz\\\" Winkler\" ", result); + MailAddress ctorAddress = new MailAddress("test@example.com", displayName); + Assert.Equal(expected, ctorAddress.Encode(0, false)); + Assert.Equal(expected, ctorAddress.Encode(0, true)); + Assert.Equal(expected, ctorAddress.ToString()); - result = testAddress.Encode(0, true); - Assert.Equal("\"Henry \\\"The Fonz\\\" Winkler\" ", result); + Assert.True(MailAddress.TryCreate("test@example.com", displayName, out MailAddress tryCreateAddress)); + Assert.Equal(expected, tryCreateAddress.Encode(0, false)); + Assert.Equal(expected, tryCreateAddress.Encode(0, true)); + Assert.Equal(expected, tryCreateAddress.ToString()); } [Theory] - [MemberData(nameof(MailAddressFactory_AddressDisplayName))] - public void EncodeMultipleMailAddress_WithDisplayNameContainingQuotes_ShouldEscapeQuotes(Func factory) + [MemberData(nameof(DisplayNamesWithSpecialChars))] + public void EncodeMultipleMailAddress_WithDisplayNameContainingSpecials_ShouldEscapeAsQuotedPairs(string displayName, string expectedEncodedDisplayName) { - MailAddress testAddress = factory("test@example.com", "Henry \"The Fonz\" Winkler"); + MailAddress testAddress = new MailAddress("test@example.com", displayName); MailAddressCollection collection = new MailAddressCollection(); collection.Add(testAddress); - string result = collection.Encode(0, false); - Assert.Equal("\"Henry \\\"The Fonz\\\" Winkler\" ", result); - - result = collection.Encode(0, true); - Assert.Equal("\"Henry \\\"The Fonz\\\" Winkler\" ", result); + string expected = $"{expectedEncodedDisplayName} "; + Assert.Equal(expected, collection.Encode(0, false)); + Assert.Equal(expected, collection.Encode(0, true)); } [Fact] From 41e68cd7776f4bb36b22093eea51265c7bc8dcff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Jun 2026 12:42:18 +0000 Subject: [PATCH 4/6] Extract EscapeQuotedStringContent helper for display name escaping Co-authored-by: mrek-msft <188900745+mrek-msft@users.noreply.github.com> --- .../src/System/Net/Mail/MailAddress.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) 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 31006d72849709..3c19b90c149e0d 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 @@ -264,13 +264,16 @@ public override string ToString() } else { - // Escape backslashes first, then quotes, so the display name forms - // a valid RFC 5322 quoted-string. Order matters: escaping quotes - // first would also escape the backslashes introduced by it. - return "\"" + DisplayName.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\" " + SmtpAddress; + return "\"" + EscapeQuotedStringContent(DisplayName) + "\" " + SmtpAddress; } } + // Escapes backslashes and embedded quotes so the display name forms a + // valid RFC 5322 quoted-string. Order matters: escaping quotes first + // would also escape the backslashes introduced by it. + private static string EscapeQuotedStringContent(string displayName) => + displayName.Replace("\\", "\\\\").Replace("\"", "\\\""); + public override bool Equals([NotNullWhen(true)] object? value) { if (value == null) @@ -302,12 +305,7 @@ internal string Encode(int charsConsumed, bool allowUnicode) //be appended. if (MimeBasePart.IsAscii(_displayName, false) || allowUnicode) { - // Escape backslashes and embedded quotes so the display name - // forms a valid RFC 5322 quoted-string (e.g. Henry "The Fonz" - // Winkler or a name containing '\'). Order matters: escaping - // quotes first would also escape the backslashes introduced - // by it. - encodedAddress = "\"" + _displayName.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; + encodedAddress = "\"" + EscapeQuotedStringContent(_displayName) + "\""; } else { From 52b71beea2b9c0829c179cfc526a3c913f0320ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Jun 2026 15:10:48 +0000 Subject: [PATCH 5/6] Add " to range-of-chars test input; remove comment Co-authored-by: mrek-msft <188900745+mrek-msft@users.noreply.github.com> --- .../Unit/MailAddressTests/MailAddressEncodeTest.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs index 77a430f7a6fc11..ef7660c01fa8a5 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs +++ b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs @@ -129,18 +129,15 @@ public void EncodeSingleMailAddress_WithAddressAndLongDisplayNameUnicode_ShouldQ public void EncodeSingleMailAddress_WithAddressAndNonAsciiAndRangeOfChars_ShouldQEncode(Func factory) { MailAddress testAddress = factory("test@example.com", - "\u00AE !#$%&'()+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); + "\u00AE !\"#$%&'()+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); string result = testAddress.Encode(0, false); - Assert.Equal("=?utf-8?Q?=C2=AE_=21=23=24=25=26=27=28=29=2B=2C=2D=2E=2F0123456789=3A?=" - + "\r\n =?utf-8?Q?=3B=3C=3D=3E=3F=40ABCDEFGHIJKLMNOPQRSTUVWXYZ=5B=5C=5D=5E=5F?=" - + "\r\n =?utf-8?Q?=60abcdefghijklmnopqrstuvwxyz=7B=7C=7D=7E?= ", result); + Assert.Equal("=?utf-8?Q?=C2=AE_=21=22=23=24=25=26=27=28=29=2B=2C=2D=2E=2F012345678?=" + + "\r\n =?utf-8?Q?9=3A=3B=3C=3D=3E=3F=40ABCDEFGHIJKLMNOPQRSTUVWXYZ=5B=5C=5D?=" + + "\r\n =?utf-8?Q?=5E=5F=60abcdefghijklmnopqrstuvwxyz=7B=7C=7D=7E?= ", result); result = testAddress.Encode(0, true); - // The literal '\' in the input is RFC 5322-escaped as '\\' in the - // resulting quoted-string when allowUnicode permits the quoted-string - // form (i.e. no Q-encoding). Assert.Equal( - "\"\u00AE !#$%&'()+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\" " + "\"\u00AE !\\\"#$%&'()+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\" " + "", result); } From 616dd5134b485e745cf8532ac63419a4a5c2809f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Jun 2026 15:15:31 +0000 Subject: [PATCH 6/6] Remove regression-test issue link comment Co-authored-by: mrek-msft <188900745+mrek-msft@users.noreply.github.com> --- .../tests/Unit/MailAddressTests/MailAddressEncodeTest.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs index ef7660c01fa8a5..016e707be5023c 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs +++ b/src/libraries/System.Net.Mail/tests/Unit/MailAddressTests/MailAddressEncodeTest.cs @@ -232,9 +232,6 @@ public static IEnumerable DisplayNamesWithSpecialChars() [MemberData(nameof(DisplayNamesWithSpecialChars))] public void EncodeSingleMailAddress_WithDisplayNameContainingSpecials_ShouldEscapeAsQuotedPairs(string displayName, string expectedEncodedDisplayName) { - // Regression test for https://github.com/dotnet/runtime/issues/52439: - // embedded '"' and '\' in the DisplayName must be escaped as quoted-pairs - // when produced for SMTP headers, otherwise the header is corrupted. string expected = $"{expectedEncodedDisplayName} "; MailAddress ctorAddress = new MailAddress("test@example.com", displayName);