Skip to content

Commit

Permalink
Decode IDN-encoded local-parts in addrspecs when parsing
Browse files Browse the repository at this point in the history
We already decoded IDN domains, but did not do it for local-parts. Now we do it for both.

Also added new MailboxAddress.GetAddress(bool idnEncode) for use with the SmtpClient in MailKit.

Should fix jstedfast/MailKit#1026
  • Loading branch information
jstedfast committed May 24, 2020
1 parent 53463b7 commit ec5ac75
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 31 deletions.
3 changes: 3 additions & 0 deletions MimeKit/InternetAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@ internal static bool TryParseLocalPart (byte[] text, ref int index, int endIndex

localpart = token.ToString ();

if (ParseUtils.IsIdnEncoded (localpart))
localpart = ParseUtils.IdnDecode (localpart);

return true;
}

Expand Down
36 changes: 22 additions & 14 deletions MimeKit/MailboxAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,9 @@ public string Address {

if (value.Length > 0) {
var buffer = CharsetUtils.UTF8.GetBytes (value);
string addrspec;
int index = 0;
int atIndex;

TryParseAddrspec (buffer, ref index, buffer.Length, new byte[0], true, out addrspec, out atIndex);
TryParseAddrspec (buffer, ref index, buffer.Length, new byte[0], true, out string addrspec, out int atIndex);

if (index != buffer.Length)
throw new ParseException (string.Format ("Unexpected token at offset {0}", index), index, index);
Expand Down Expand Up @@ -339,10 +337,9 @@ public static string EncodeAddrspec (string addrspec)
return addrspec;

var buffer = CharsetUtils.UTF8.GetBytes (addrspec);
int at, index = 0;
string address;
int index = 0;

if (!TryParseAddrspec (buffer, ref index, buffer.Length, new byte[0], false, out address, out at))
if (!TryParseAddrspec (buffer, ref index, buffer.Length, new byte[0], false, out string address, out int at))
return addrspec;

return EncodeAddrspec (address, at);
Expand Down Expand Up @@ -386,26 +383,37 @@ public static string DecodeAddrspec (string addrspec)
return addrspec;

var buffer = CharsetUtils.UTF8.GetBytes (addrspec);
int at, index = 0;
string address;
int index = 0;

if (!TryParseAddrspec (buffer, ref index, buffer.Length, new byte[0], false, out address, out at))
if (!TryParseAddrspec (buffer, ref index, buffer.Length, new byte[0], false, out string address, out int at))
return addrspec;

return DecodeAddrspec (address, at);
}

/// <summary>
/// Get the mailbox address, optionally encoded according to IDN encoding rules.
/// </summary>
/// <remarks>
/// If <see cref="idnEncode"/> is <c>true</c>, then the returned mailbox address will be encoded according to the IDN encoding rules.
/// </remarks>
/// <param name="idnEncode"><c>true</c> if the address should be encoded according to IDN encoding rules; otherwise, <c>false</c>.</param>
/// <returns>The mailbox address.</returns>
public string GetAddress (bool idnEncode)
{
if (idnEncode)
return EncodeAddrspec (address, at);

return DecodeAddrspec (address, at);
}

internal override void Encode (FormatOptions options, StringBuilder builder, bool firstToken, ref int lineLength)
{
var route = Route.Encode (options);
if (!string.IsNullOrEmpty (route))
route += ":";

string addrspec;
if (options.International)
addrspec = DecodeAddrspec (address, at);
else
addrspec = EncodeAddrspec (address, at);
var addrspec = GetAddress (!options.International);

if (!string.IsNullOrEmpty (Name)) {
string name;
Expand Down
84 changes: 67 additions & 17 deletions UnitTests/MailboxAddressTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

using System;
using System.Text;
using System.Globalization;
using System.Collections.Generic;

using NUnit.Framework;
Expand Down Expand Up @@ -563,36 +564,85 @@ public void TestParseGroupNameColon ()
AssertParseFailure (text, false, tokenIndex, errorIndex);
}

[Test]
public void TestGetAddress ()
{
var idn = new IdnMapping ();
MailboxAddress mailbox;

mailbox = new MailboxAddress ("Unit Test", "點看@domain.com");
Assert.AreEqual ("點看@domain.com", mailbox.GetAddress (false), "IDN-decode #1");
Assert.AreEqual (idn.GetAscii ("點看") + "@domain.com", mailbox.GetAddress (true), "IDN-encode #1");

mailbox = new MailboxAddress ("Unit Test", idn.GetAscii ("點看") + "@domain.com");
Assert.AreEqual ("點看@domain.com", mailbox.GetAddress (false), "IDN-decode #2");
Assert.AreEqual (idn.GetAscii ("點看") + "@domain.com", mailbox.GetAddress (true), "IDN-encode #2");

mailbox = new MailboxAddress ("Unit Test", "user@名がドメイン.com");
Assert.AreEqual ("user@名がドメイン.com", mailbox.GetAddress (false), "IDN-decode #3");
Assert.AreEqual ("user@" + idn.GetAscii ("名がドメイン.com"), mailbox.GetAddress (true), "IDN-encode #3");

mailbox = new MailboxAddress ("Unit Test", "user@" + idn.GetAscii ("名がドメイン.com"));
Assert.AreEqual ("user@名がドメイン.com", mailbox.GetAddress (false), "IDN-decode #4");
Assert.AreEqual ("user@" + idn.GetAscii ("名がドメイン.com"), mailbox.GetAddress (true), "IDN-encode #4");

mailbox = new MailboxAddress ("Unit Test", "點看@名がドメイン.com");
Assert.AreEqual ("點看@名がドメイン.com", mailbox.GetAddress (false), "IDN-decode #5");
Assert.AreEqual (idn.GetAscii ("點看") + "@" + idn.GetAscii ("名がドメイン.com"), mailbox.GetAddress (true), "IDN-encode #5");

mailbox = new MailboxAddress ("Unit Test", idn.GetAscii ("點看") + "@" + idn.GetAscii ("名がドメイン.com"));
Assert.AreEqual ("點看@名がドメイン.com", mailbox.GetAddress (false), "IDN-decode #6");
Assert.AreEqual (idn.GetAscii ("點看") + "@" + idn.GetAscii ("名がドメイン.com"), mailbox.GetAddress (true), "IDN-encode #6");
}

[Test]
public void TestIsInternational ()
{
var mailbox = new MailboxAddress ("Kristoffer Brånemyr", "brå[email protected]");
const string expected = "Kristoffer Brånemyr <brå[email protected]>";
var options = FormatOptions.Default.Clone ();
options.International = true;
var idn = new IdnMapping ();
MailboxAddress mailbox;
string encoded;

options.International = true;
// Test IsInternational local-parts
mailbox = new MailboxAddress ("Unit Test", "點看@domain.com");
Assert.IsTrue (mailbox.IsInternational, "IsInternational local-part");
encoded = mailbox.ToString (options, true);
Assert.AreEqual ("Unit Test <點看@domain.com>", encoded, "ToString local-part");

// Test IsInternational IDN-encoded local-parts
mailbox = new MailboxAddress ("Unit Test", idn.GetAscii ("點看") + "@domain.com");
Assert.IsTrue (mailbox.IsInternational, "IsInternational IDN-encoded local-part");
encoded = mailbox.ToString (options, true);
Assert.AreEqual (expected, encoded, "ToString");
Assert.AreEqual ("Unit Test <點看@domain.com>", encoded, "ToString IDN-encoded local-part");

Assert.IsTrue (mailbox.IsInternational, "IsInternational");
// Test IsInternational domain
mailbox = new MailboxAddress ("Unit Test", "user@名がドメイン.com");
Assert.IsTrue (mailbox.IsInternational, "IsInternational domain");
encoded = mailbox.ToString (options, true);
Assert.AreEqual ("Unit Test <user@名がドメイン.com>", encoded, "ToString domain");

mailbox = new MailboxAddress ("Kristoffer Brånemyr", "[email protected]");
// Test IsInternational IDN-encoded domain
mailbox = new MailboxAddress ("Unit Test", "user@" + idn.GetAscii ("名がドメイン.com"));
Assert.IsTrue (mailbox.IsInternational, "IsInternational IDN-encoded domain");
encoded = mailbox.ToString (options, true);
Assert.AreEqual ("Unit Test <user@名がドメイン.com>", encoded, "ToString IDN-encoded domain");

// Test IsInternational routes
mailbox = new MailboxAddress ("Unit Test", "[email protected]");
Assert.IsFalse (mailbox.IsInternational, "IsInternational");

mailbox.Route.Add ("kristoffer"); // non-international route
mailbox.Route.Add ("brånemyr"); // international route

Assert.IsTrue (mailbox.IsInternational, "IsInternational");
mailbox.Route.Add ("route1"); // non-international route
mailbox.Route.Add ("名がドメイン.com"); // international route
Assert.IsTrue (mailbox.IsInternational, "IsInternational route");
encoded = mailbox.ToString (options, true);
Assert.AreEqual ("Unit Test <@route1,@名がドメイン.com:[email protected]>", encoded, "ToString route");
}

[Test]
public void TestIdnEncoding ()
{
//const string userAscii = "xn--c1yn36f@domain";
//const string userUnicode = "點看@domain";
const string userAscii = "xn--c1yn36f@domain.com";
const string userUnicode = "點看@domain.com";
const string domainAscii = "[email protected]";
const string domainUnicode = "user@名がドメイン.com";
string encoded;
Expand All @@ -603,11 +653,11 @@ public void TestIdnEncoding ()
encoded = MailboxAddress.DecodeAddrspec (domainAscii);
Assert.AreEqual (domainUnicode, encoded, "Domain (Decode)");

//encoded = MailboxAddress.EncodeAddrspec (userUnicode);
//Assert.AreEqual (userAscii, encoded, "Local-part (Encode)");
encoded = MailboxAddress.EncodeAddrspec (userUnicode);
Assert.AreEqual (userAscii, encoded, "Local-part (Encode)");

//encoded = MailboxAddress.DecodeAddrspec (userAscii);
//Assert.AreEqual (userUnicode, encoded, "Local-part (Decode)");
encoded = MailboxAddress.DecodeAddrspec (userAscii);
Assert.AreEqual (userUnicode, encoded, "Local-part (Decode)");
}

[Test]
Expand Down

0 comments on commit ec5ac75

Please sign in to comment.