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
10 changes: 8 additions & 2 deletions src/libraries/System.Net.Mail/src/System/Net/Mail/MailAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,16 @@ public override string ToString()
}
else
{
return "\"" + DisplayName.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)
Expand Down Expand Up @@ -299,7 +305,7 @@ internal string Encode(int charsConsumed, bool allowUnicode)
//be appended.
if (MimeBasePart.IsAscii(_displayName, false) || allowUnicode)
{
encodedAddress = "\"" + _displayName + "\"";
encodedAddress = "\"" + EscapeQuotedStringContent(_displayName) + "\"";
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,16 @@ public static IEnumerable<object[]> 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 };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,15 @@ public void EncodeSingleMailAddress_WithAddressAndLongDisplayNameUnicode_ShouldQ
public void EncodeSingleMailAddress_WithAddressAndNonAsciiAndRangeOfChars_ShouldQEncode(Func<string, string, MailAddress> 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?= <test@example.com>", 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?= <test@example.com>", result);

result = testAddress.Encode(0, true);
Assert.Equal(
"\"\u00AE !#$%&'()+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\" "
"\"\u00AE !\\\"#$%&'()+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\" "
+ "<test@example.com>", result);
}

Expand Down Expand Up @@ -214,6 +214,50 @@ public void EncodeMultipleMailAddress_WithManyAddressesThatAreDifferentAndContai
+ " \"test\u00DC\" <test2@example.com>", result);
}

public static IEnumerable<object[]> 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(DisplayNamesWithSpecialChars))]
public void EncodeSingleMailAddress_WithDisplayNameContainingSpecials_ShouldEscapeAsQuotedPairs(string displayName, string expectedEncodedDisplayName)
{
string expected = $"{expectedEncodedDisplayName} <test@example.com>";

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());

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(DisplayNamesWithSpecialChars))]
public void EncodeMultipleMailAddress_WithDisplayNameContainingSpecials_ShouldEscapeAsQuotedPairs(string displayName, string expectedEncodedDisplayName)
{
MailAddress testAddress = new MailAddress("test@example.com", displayName);
MailAddressCollection collection = new MailAddressCollection();
collection.Add(testAddress);

string expected = $"{expectedEncodedDisplayName} <test@example.com>";
Assert.Equal(expected, collection.Encode(0, false));
Assert.Equal(expected, collection.Encode(0, true));
}

[Fact]
public void CustomEncoding_Null()
{
Expand Down
Loading