Skip to content

Commit

Permalink
[net7.0] Fix e-mail URI escaping recipients (#15244)
Browse files Browse the repository at this point in the history
* Fix e-mail URI escaping recipients

Escaping the recipient e-mail addresses, leads to the character @ being escaped and creates an invalid URL

* Create proper RFC 2368 mailto url

* Add unit tests

* Optimize check if any items are present in collection

---------

Co-authored-by: Tomasz Cielecki <tci@trackman.com>
  • Loading branch information
github-actions[bot] and Cheesebaron authored May 30, 2023
1 parent 7c1f069 commit a2bf5b8
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 19 deletions.
36 changes: 17 additions & 19 deletions src/Essentials/src/Email/Email.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,31 +60,29 @@ public Task ComposeAsync(EmailMessage? message)
return PlatformComposeAsync(message);
}

static string GetMailToUri(EmailMessage message)
{
if (message != null && message.BodyFormat != EmailBodyFormat.PlainText)
throw new FeatureNotSupportedException("Only EmailBodyFormat.PlainText is supported if no email account is set up.");
internal static string GetMailToUri(EmailMessage message) =>
"mailto:?" + string.Join("&", Parameters(message));

var parts = new List<string>();
if (!string.IsNullOrEmpty(message?.Body))
parts.Add("body=" + Uri.EscapeDataString(message!.Body));
if (!string.IsNullOrEmpty(message?.Subject))
parts.Add("subject=" + Uri.EscapeDataString(message!.Subject));
if (message?.Cc?.Count > 0)
parts.Add("cc=" + Uri.EscapeDataString(string.Join(",", message.Cc)));
if (message?.Bcc?.Count > 0)
parts.Add("bcc=" + Uri.EscapeDataString(string.Join(",", message.Bcc)));
static IEnumerable<string> Parameters(EmailMessage message)
{
if (message.To?.Count > 0)
yield return "to=" + Recipients(message.To);

var uri = "mailto:";
if (message.Cc?.Count > 0)
yield return "cc=" + Recipients(message.Cc);

if (message?.To?.Count > 0)
uri += Uri.EscapeDataString(string.Join(",", message.To));
if (message.Bcc?.Count > 0)
yield return "bcc=" + Recipients(message.Bcc);

if (parts.Count > 0)
uri += "?" + string.Join("&", parts);
if (!string.IsNullOrWhiteSpace(message.Subject))
yield return "subject=" + Uri.EscapeDataString(message.Subject);

return uri;
if (!string.IsNullOrWhiteSpace(message.Body))
yield return "body=" + Uri.EscapeDataString(message.Body);
}

static string Recipients(IEnumerable<string> addresses) =>
string.Join(",", addresses.Select(Uri.EscapeDataString));
}

/// <summary>
Expand Down
187 changes: 187 additions & 0 deletions src/Essentials/test/UnitTests/Email_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,195 @@
using System;
using System.Collections.Generic;
using Microsoft.Maui.ApplicationModel.Communication;
using Xunit;

namespace Tests
{
public class EmailDataGenerator : IEnumerable<object[]>
{
private readonly List<object[]> _data = new()
{
// empty
new object[]
{
new EmailMessage(),
"mailto:?"
},

// body only
new object[]
{
new EmailMessage { Body = "Hello" },
"mailto:?body=Hello"
},

// subject only
new object[]
{
new EmailMessage { Subject = "Hello" },
"mailto:?subject=Hello"
},

// subject and body
new object[]
{
new EmailMessage { Subject = "Hello", Body = "Yo" },
"mailto:?subject=Hello&body=Yo"
},

// to only
new object[]
{
new EmailMessage { To = new List<string> { "john@doe.net" } },
"mailto:?to=john%40doe.net"
},

// cc only
new object[]
{
new EmailMessage { Cc = new List<string> { "john@doe.net" } },
"mailto:?cc=john%40doe.net"
},

// bcc only
new object[]
{
new EmailMessage { Bcc = new List<string> { "john@doe.net" } },
"mailto:?bcc=john%40doe.net"
},

// To 1 recipient
new object[]
{
new EmailMessage
{
To = new List<string> { "sauron@mordor.gov.middleearth" },
Subject = "Claim your free rings of power",
Body = "Click this link to get your rings..."
},
"mailto:?to=sauron%40mordor.gov.middleearth&subject=Claim%20your%20free%20rings%20of%20power&body=Click%20this%20link%20to%20get%20your%20rings..."
},

// To 2 recipients
new object[]
{
new EmailMessage
{
To = new List<string> { "bilbo@hobbiton.shire", "frodo@hobbiton.shire" },
Subject = "Greetings",
Body = "Greetings Hobbits!"
},
"mailto:?to=bilbo%40hobbiton.shire,frodo%40hobbiton.shire&subject=Greetings&body=Greetings%20Hobbits%21"
},

// Cc 1 recipient
new object[]
{
new EmailMessage
{
Cc = new List<string> { "surfer@maui.net" },
Subject = "Big waves",
Body = "Dude, there were huge waves yesterday"
},
"mailto:?cc=surfer%40maui.net&subject=Big%20waves&body=Dude%2C%20there%20were%20huge%20waves%20yesterday"
},

// Cc 2 recipients
new object[]
{
new EmailMessage
{
Cc = new List<string> { "surfer@maui.net", "dude@surf.net" },
Subject = "Duuuude",
Body = "Sweet"
},
"mailto:?cc=surfer%40maui.net,dude%40surf.net&subject=Duuuude&body=Sweet"
},

// Bcc 1 recipient
new object[]
{
new EmailMessage
{
Bcc = new List<string> { "knights@who.say.ni" },
Subject = "Shrubberies here!",
Body = "Ekke Ekke Ekke Ekke Ptang Zoo Boing!"
},
"mailto:?bcc=knights%40who.say.ni&subject=Shrubberies%20here%21&body=Ekke%20Ekke%20Ekke%20Ekke%20Ptang%20Zoo%20Boing%21"
},

// Bcc 2 recipients
new object[]
{
new EmailMessage
{
Bcc = new List<string> { "knights@who.say.ni", "arthur@who.says.nu"
},
Subject = "Shrubberies here!",
Body = "Ekke Ekke Ekke Ekke Ptang Zoo Boing!"
},
"mailto:?bcc=knights%40who.say.ni,arthur%40who.says.nu&subject=Shrubberies%20here%21&body=Ekke%20Ekke%20Ekke%20Ekke%20Ptang%20Zoo%20Boing%21"
},

// Mixed recipients
new object[]
{
new EmailMessage
{
To = new List<string> { "bilbo@hobbiton.shire", "frodo@hobbiton.shire" },
Cc = new List<string> { "knights@who.say.ni", "arthur@who.says.nu" },
Subject = "Greetings", Body = "Greetings Hobbits!"
},
"mailto:?to=bilbo%40hobbiton.shire,frodo%40hobbiton.shire&cc=knights%40who.say.ni,arthur%40who.says.nu&subject=Greetings&body=Greetings%20Hobbits%21"
},
};

public IEnumerator<object[]> GetEnumerator() => _data.GetEnumerator();

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Email_Tests
{
[Theory]
[ClassData(typeof(EmailDataGenerator))]
public void GetMailToUri_Returns_RFC2368_Valid_Url(EmailMessage message, string expectedUrl)
{
var result = EmailImplementation.GetMailToUri(message);

Assert.Equal(expectedUrl, result);
}

[Fact]
public void GetMailToUri_Ignores_Attachments()
{
var message = new EmailMessage
{
To = new List<string> { "mom@maui.net" },
Attachments = new List<EmailAttachment>
{
new EmailAttachment("/my/lovely/path/selfie.jpeg")
}
};

var result = EmailImplementation.GetMailToUri(message);

Assert.DoesNotContain("selfie", result, StringComparison.InvariantCultureIgnoreCase);
}

[Fact]
public void GetMailToUri_Ingores_BodyFormat()
{
var message = new EmailMessage
{
To = new List<string> { "mom@maui.net" },
BodyFormat = EmailBodyFormat.Html,
Body = "Hi Mom!"
};

var result = EmailImplementation.GetMailToUri(message);

Assert.Contains("Hi%20Mom%21", result, StringComparison.InvariantCulture);
}
}
}

0 comments on commit a2bf5b8

Please sign in to comment.