diff --git a/Source/StrongGrid.IntegrationTests/Program.cs b/Source/StrongGrid.IntegrationTests/Program.cs index df7657be..98732fb7 100644 --- a/Source/StrongGrid.IntegrationTests/Program.cs +++ b/Source/StrongGrid.IntegrationTests/Program.cs @@ -10,7 +10,7 @@ namespace StrongGrid.IntegrationTests { public class Program { - public static async Task Main(string[] args) + public static async Task Main() { var services = new ServiceCollection(); ConfigureServices(services); diff --git a/Source/StrongGrid.IntegrationTests/StrongGrid.IntegrationTests.csproj b/Source/StrongGrid.IntegrationTests/StrongGrid.IntegrationTests.csproj index 18531366..26d009ed 100644 --- a/Source/StrongGrid.IntegrationTests/StrongGrid.IntegrationTests.csproj +++ b/Source/StrongGrid.IntegrationTests/StrongGrid.IntegrationTests.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/Source/StrongGrid.UnitTests/StrongGrid.UnitTests.csproj b/Source/StrongGrid.UnitTests/StrongGrid.UnitTests.csproj index 0e4b15a8..f6065e4a 100644 --- a/Source/StrongGrid.UnitTests/StrongGrid.UnitTests.csproj +++ b/Source/StrongGrid.UnitTests/StrongGrid.UnitTests.csproj @@ -7,12 +7,12 @@ - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -22,10 +22,6 @@ - - - - Always diff --git a/Source/StrongGrid.UnitTests/WebhookParserTests.cs b/Source/StrongGrid.UnitTests/WebhookParserTests.cs index c5637247..8f197fac 100644 --- a/Source/StrongGrid.UnitTests/WebhookParserTests.cs +++ b/Source/StrongGrid.UnitTests/WebhookParserTests.cs @@ -318,6 +318,55 @@ Bob Smith --xYzZY Content-Disposition: form-data; name=""attachments"" +0 +--xYzZY--"; + + private const string INBOUND_EMAIL_UNUSUAL_ENCODING_WEBHOOK = @"--xYzZY +Content-Disposition: form-data; name=""dkim"" + +{@hotmail.com : pass} +--xYzZY +Content-Disposition: form-data; name=""envelope"" + +{""to"":[""test@api.yourdomain.com""],""from"":""bob@example.com""} +--xYzZY +Content-Disposition: form-data; name=""subject"" + +Test #1 +--xYzZY +Content-Disposition: form-data; name=""charsets"" + +{""to"":""UTF-8"",""html"":""us-ascii"",""subject"":""UTF-8"",""from"":""UTF-8"",""text"":""iso-8859-10""} +--xYzZY +Content-Disposition: form-data; name=""SPF"" + +softfail + +--xYzZY +Content-Disposition: form-data; name=""to"" + +""Test Recipient"" +--xYzZY +Content-Disposition: form-data; name=""html"" + +Hello SendGrid! + +--xYzZY +Content-Disposition: form-data; name=""from"" + +Bob Smith +--xYzZY +Content-Disposition: form-data; name=""text"" + +Hello SendGrid! + +--xYzZY +Content-Disposition: form-data; name=""sender_ip"" + +10.43.24.23 +--xYzZY +Content-Disposition: form-data; name=""attachments"" + 0 --xYzZY--"; @@ -503,6 +552,30 @@ public async Task RawPayloadWithAttachmentsAsync() } } + [Fact] + public void InboundEmail_with_unusual_encoding() + { + // Arrange + var parser = new WebhookParser(); + using (var stream = GetStream(INBOUND_EMAIL_UNUSUAL_ENCODING_WEBHOOK)) + { + // Act + var inboundEmail = parser.ParseInboundEmailWebhook(stream); + + // Assert + inboundEmail.Charsets.ShouldNotBeNull(); + inboundEmail.Charsets.Except(new[] + { + new KeyValuePair("to", Encoding.UTF8), + new KeyValuePair("subject", Encoding.UTF8), + new KeyValuePair("from", Encoding.UTF8), + new KeyValuePair("html", Encoding.ASCII), + new KeyValuePair("text", Encoding.UTF8) // The original encoding is iso-8859-10 but we fallback on UTF-8 + }).Count().ShouldBe(0); + inboundEmail.Text.ShouldBe("Hello SendGrid!\r\n"); + } + } + [Fact] public void Parse_processed_JSON() { diff --git a/Source/StrongGrid/StrongGrid.csproj b/Source/StrongGrid/StrongGrid.csproj index b9a0872f..07c34a43 100644 --- a/Source/StrongGrid/StrongGrid.csproj +++ b/Source/StrongGrid/StrongGrid.csproj @@ -33,8 +33,8 @@ - - + + diff --git a/Source/StrongGrid/WebhookParser.cs b/Source/StrongGrid/WebhookParser.cs index 778c0e20..fe582577 100644 --- a/Source/StrongGrid/WebhookParser.cs +++ b/Source/StrongGrid/WebhookParser.cs @@ -90,8 +90,24 @@ public async Task ParseInboundEmailWebhookAsync(Stream stream) .Select(prop => { var key = prop.Name; - var value = Encoding.GetEncoding(prop.Value.ToString()); - return new KeyValuePair(key, value); + var encodingName = prop.Value.ToString(); + + try + { + var encoding = Encoding.GetEncoding(encodingName); + return new KeyValuePair(key, encoding); + } + catch (ArgumentException) + { + // ArgumentException is thrown when an "unusual" code page was used to encode a section of the email + // For example: {"to":"UTF-8","subject":"UTF-8","from":"UTF-8","text":"iso-8859-10"} + // We can see that 'iso-8859-10' was used to encode the "Text" but this encoding is not supported in + // .net (neither dotnet full nor dotnet core). Therefore we fallback on UTF-8. This is obviously not + // perfect because UTF-8 may or may not be able to handle all the encoded characters, but it's better + // than simply erroring out. + // See https://github.com/Jericho/StrongGrid/issues/341 for discussion. + return new KeyValuePair(key, Encoding.UTF8); + } }).ToArray(); // Create a dictionary of parsers, one parser for each desired encoding. @@ -99,7 +115,7 @@ public async Task ParseInboundEmailWebhookAsync(Stream stream) // encoding and SendGrid can use different encodings for parameters such // as "from", "to", "text" and "html". var encodedParsers = charsets - .Where(c => c.Value != Encoding.UTF8) + .Where(c => !c.Value.Equals(Encoding.UTF8)) .Select(c => c.Value) .Distinct() .Select(async encoding => @@ -152,8 +168,24 @@ public InboundEmail ParseInboundEmailWebhook(Stream stream) .Select(prop => { var key = prop.Name; - var value = Encoding.GetEncoding(prop.Value.ToString()); - return new KeyValuePair(key, value); + var encodingName = prop.Value.ToString(); + + try + { + var encoding = Encoding.GetEncoding(encodingName); + return new KeyValuePair(key, encoding); + } + catch (ArgumentException) + { + // ArgumentException is thrown when an "unusual" code page was used to encode a section of the email + // For example: {"to":"UTF-8","subject":"UTF-8","from":"UTF-8","text":"iso-8859-10"} + // We can see that 'iso-8859-10' was used to encode the "Text" but this encoding is not supported in + // .net (neither dotnet full nor dotnet core). Therefore we fallback on UTF-8. This is obviously not + // perfect because UTF-8 may or may not be able to handle all the encoded characters, but it's better + // than simply erroring out. + // See https://github.com/Jericho/StrongGrid/issues/341 for discussion. + return new KeyValuePair(key, Encoding.UTF8); + } }).ToArray(); // Create a dictionary of parsers, one parser for each desired encoding. @@ -161,7 +193,7 @@ public InboundEmail ParseInboundEmailWebhook(Stream stream) // encoding and SendGrid can use different encodings for parameters such // as "from", "to", "text" and "html". var encodedParsers = charsets - .Where(c => c.Value != Encoding.UTF8) + .Where(c => !c.Value.Equals(Encoding.UTF8)) .Select(c => c.Value) .Distinct() .Select(encoding => @@ -210,7 +242,7 @@ private static string GetEncodedValue(string parameterName, IEnumerable encodedParsers, KeyValuePair[] charsets) { // Get the default UTF8 parser - var parser = encodedParsers.Single(p => p.Key == Encoding.UTF8).Value; + var parser = encodedParsers.Single(p => p.Key.Equals(Encoding.UTF8)).Value; // Convert the 'headers' from a string into array of KeyValuePair var headers = parser diff --git a/build.cake b/build.cake index 1362cd40..ef5ceda0 100644 --- a/build.cake +++ b/build.cake @@ -5,15 +5,15 @@ #tool dotnet:?package=BenchmarkDotNet.Tool&version=0.12.0 // Install tools. -#tool nuget:?package=GitVersion.CommandLine&version=5.3.6 +#tool nuget:?package=GitVersion.CommandLine&version=5.3.7 #tool nuget:?package=GitReleaseManager&version=0.11.0 #tool nuget:?package=OpenCover&version=4.7.922 -#tool nuget:?package=ReportGenerator&version=4.6.1 +#tool nuget:?package=ReportGenerator&version=4.6.6 #tool nuget:?package=coveralls.io&version=1.4.2 #tool nuget:?package=xunit.runner.console&version=2.4.1 // Install addins. -#addin nuget:?package=Cake.Coveralls&version=0.10.1 +#addin nuget:?package=Cake.Coveralls&version=0.10.2 /////////////////////////////////////////////////////////////////////////////// @@ -359,14 +359,6 @@ Task("Publish-GitHub-Release") .WithCriteria(() => isTagged) .Does(() => { - var settings = new GitReleaseManagerCreateSettings - { - Name = milestone, - Milestone = milestone, - Prerelease = false, - TargetCommitish = "master" - }; - if (!string.IsNullOrEmpty(gitHubToken)) { GitReleaseManagerClose(gitHubToken, gitHubRepoOwner, gitHubRepo, milestone);