From bdc30accb9c6fb32cc285522ad065e7b4f19e86b Mon Sep 17 00:00:00 2001 From: Rob Wood Date: Sat, 9 Mar 2024 12:36:20 +0000 Subject: [PATCH] =?UTF-8?q?Fix:=20Bug=20#170=20-=20"just=20send=208-bit?= =?UTF-8?q?=E2=80=9D=20messages=20shown=20with=20wrong=20encoding=20(#1344?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 1 + .gitignore | 1 + .vscode/launch.json | 2 +- Rnwood.Smtp4dev/ApiModel/Message.cs | 24 +- .../src/components/messagesource.vue | 2 +- .../ClientApp/src/components/messageview.vue | 565 +++++++++--------- Rnwood.Smtp4dev/CommandLineOptions.cs | 1 + Rnwood.Smtp4dev/CommandLineParser.cs | 5 +- .../Controllers/MessagesController.cs | 15 +- Rnwood.Smtp4dev/DbModel/Message.cs | 4 + ...40308081358_AddSessionEncoding.Designer.cs | 173 ++++++ .../20240308081358_AddSessionEncoding.cs | 28 + ...42_AddMessageEightBitTransport.Designer.cs | 176 ++++++ ...40309084542_AddMessageEightBitTransport.cs | 28 + .../Smtp4devDbContextModelSnapshot.cs | 11 +- .../Properties/launchSettings.json | 2 +- Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj | 10 +- Rnwood.Smtp4dev/Server/MessageConverter.cs | 4 +- test/.vscode/launch.json | 15 + test/Program.cs | 73 +++ test/test.csproj | 10 + test/test.sln | 25 + 22 files changed, 878 insertions(+), 297 deletions(-) create mode 100644 Rnwood.Smtp4dev/Migrations/20240308081358_AddSessionEncoding.Designer.cs create mode 100644 Rnwood.Smtp4dev/Migrations/20240308081358_AddSessionEncoding.cs create mode 100644 Rnwood.Smtp4dev/Migrations/20240309084542_AddMessageEightBitTransport.Designer.cs create mode 100644 Rnwood.Smtp4dev/Migrations/20240309084542_AddMessageEightBitTransport.cs create mode 100644 test/.vscode/launch.json create mode 100644 test/Program.cs create mode 100644 test/test.csproj create mode 100644 test/test.sln diff --git a/.dockerignore b/.dockerignore index fbc601b53..39ab3aed3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,7 @@ .gitignore .vs .vscode +.idea **/bin **/obj Rnwood.Smtp4dev/ClientApp/node_modules diff --git a/.gitignore b/.gitignore index d0e86d1a1..c545b40ae 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.user *.userosscache *.sln.docstates +.idea # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/.vscode/launch.json b/.vscode/launch.json index 2caed837c..8a25256e7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "type": "coreclr", "request": "launch", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/Rnwood.Smtp4dev/bin/Debug/netcoreapp3.1/Rnwood.Smtp4dev.dll", + "program": "${workspaceFolder}/Rnwood.Smtp4dev/bin/Debug/net8.0/Rnwood.Smtp4dev.dll", "cwd": "${workspaceFolder}/Rnwood.Smtp4dev", "args": "--urls http://localhost:5000", "stopAtEntry": false, diff --git a/Rnwood.Smtp4dev/ApiModel/Message.cs b/Rnwood.Smtp4dev/ApiModel/Message.cs index b20027ecc..659516ac3 100644 --- a/Rnwood.Smtp4dev/ApiModel/Message.cs +++ b/Rnwood.Smtp4dev/ApiModel/Message.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using Microsoft.AspNetCore.Mvc; namespace Rnwood.Smtp4dev.ApiModel @@ -20,6 +21,8 @@ public Message(DbModel.Message dbMessage) ReceivedDate = dbMessage.ReceivedDate; Subject = dbMessage.Subject; SecureConnection = dbMessage.SecureConnection; + SessionEncoding = dbMessage.SessionEncoding; + EightBitTransport = dbMessage.EightBitTransport; Parts = new List(1); RelayError = dbMessage.RelayError; @@ -72,6 +75,10 @@ public Message(DbModel.Message dbMessage) } } + public string SessionEncoding { get; set; } + + public bool? EightBitTransport { get; set; } + private MessageEntitySummary HandleMimeEntity(MimeEntity entity) { @@ -154,10 +161,11 @@ internal static FileStreamResult GetPartContent(Message result, string cid) internal static string GetPartContentAsText(Message result, string id) { var contentEntity = GetPart(result, id); - + if (contentEntity is MimePart part) { - using var reader = new StreamReader(part.Content.Open()); + var encoding = part.ContentType.CharsetEncoding ?? ApiModel.Message.GetSessionEncodingOrAssumed(result); + using var reader = new StreamReader(part.Content.Open(), encoding); return reader.ReadToEnd(); } @@ -165,12 +173,22 @@ internal static string GetPartContentAsText(Message result, string id) } + internal static Encoding GetSessionEncodingOrAssumed(Message result) + { + return !string.IsNullOrEmpty(result.SessionEncoding) ? Encoding.GetEncoding(result.SessionEncoding) : Encoding.Latin1; + } internal static string GetPartSource(Message message, string id) { var contentEntity = GetPart(message, id); - return contentEntity.ToString(); + using (MemoryStream ms = new MemoryStream()) + { + contentEntity.WriteTo(ms, false); + var encoding = contentEntity.ContentType.CharsetEncoding ??ApiModel.Message.GetSessionEncodingOrAssumed(message); + return encoding.GetString(ms.GetBuffer()); + } + } diff --git a/Rnwood.Smtp4dev/ClientApp/src/components/messagesource.vue b/Rnwood.Smtp4dev/ClientApp/src/components/messagesource.vue index 68c68253e..afca92b3c 100644 --- a/Rnwood.Smtp4dev/ClientApp/src/components/messagesource.vue +++ b/Rnwood.Smtp4dev/ClientApp/src/components/messagesource.vue @@ -68,7 +68,7 @@ } else { this.sourceurl = new MessagesController().getMessageSource_url(this.message.id); - this.source = await new MessagesController().getMessageSourceRaw(this.message.id); + this.source = await new MessagesController().getMessageSource(this.message.id); } } diff --git a/Rnwood.Smtp4dev/ClientApp/src/components/messageview.vue b/Rnwood.Smtp4dev/ClientApp/src/components/messageview.vue index 4041b4968..f2ebc2b6e 100644 --- a/Rnwood.Smtp4dev/ClientApp/src/components/messageview.vue +++ b/Rnwood.Smtp4dev/ClientApp/src/components/messageview.vue @@ -1,325 +1,332 @@  \ No newline at end of file diff --git a/Rnwood.Smtp4dev/CommandLineOptions.cs b/Rnwood.Smtp4dev/CommandLineOptions.cs index a644adce8..cc2cb592b 100644 --- a/Rnwood.Smtp4dev/CommandLineOptions.cs +++ b/Rnwood.Smtp4dev/CommandLineOptions.cs @@ -13,5 +13,6 @@ public class CommandLineOptions public bool DebugSettings { get; set; } public string BaseAppDataPath { get; set; } public string InstallPath { get; set; } + public string ApplicationName { get; set; } } } \ No newline at end of file diff --git a/Rnwood.Smtp4dev/CommandLineParser.cs b/Rnwood.Smtp4dev/CommandLineParser.cs index c04f618a5..ed0f865c7 100644 --- a/Rnwood.Smtp4dev/CommandLineParser.cs +++ b/Rnwood.Smtp4dev/CommandLineParser.cs @@ -43,8 +43,9 @@ public static MapOptions TryParseCommandLine(IEnumerable map.Add((data !=null).ToString(), x=> x.ServerOptions.RecreateDb) }, { "locksettings", "Locks settings from being changed by user via web interface", data => map.Add((data !=null).ToString(), x=> x.ServerOptions.LockSettings) }, { "installpath=", "Sets path to folder containing wwwroot and other files", data => map.Add(data, x=> x.InstallPath) }, - { "disablemessagesanitisation", "Disables message HTML sanitisation.", data => map.Add((data !=null).ToString(), x=> x.ServerOptions.DisableMessageSanitisation) } - + { "disablemessagesanitisation", "Disables message HTML sanitisation.", data => map.Add((data !=null).ToString(), x=> x.ServerOptions.DisableMessageSanitisation) }, + {"applicationName=","", data => map.Add(data, x => x.ApplicationName)} + }; diff --git a/Rnwood.Smtp4dev/Controllers/MessagesController.cs b/Rnwood.Smtp4dev/Controllers/MessagesController.cs index 5d3a94ba5..1600c9ff6 100644 --- a/Rnwood.Smtp4dev/Controllers/MessagesController.cs +++ b/Rnwood.Smtp4dev/Controllers/MessagesController.cs @@ -31,7 +31,8 @@ public MessagesController(IMessagesRepository messagesRepository, ISmtp4devServe private readonly ISmtp4devServer server; [HttpGet] - public ApiModel.PagedResult GetSummaries(string sortColumn = "receivedDate", bool sortIsDescending = true, int page = 1, int pageSize=5) + public ApiModel.PagedResult GetSummaries(string sortColumn = "receivedDate", bool sortIsDescending = true, int page = 1, + int pageSize = 5) { return messagesRepository.GetMessages(false).Include(m => m.Relays) .OrderBy(sortColumn + (sortIsDescending ? " DESC" : "")) @@ -85,14 +86,17 @@ public IActionResult RelayMessage(Guid id, [FromBody] MessageRelayOptions option var relayErrorSummary = string.Join(". ", relayResult.Exceptions.Select(e => e.Key.Address + ": " + e.Value.Message)); return Problem("Failed to relay to recipients: " + relayErrorSummary); } + if (relayResult.WasRelayed) { foreach (var relay in relayResult.RelayRecipients) { message.AddRelay(new MessageRelay { SendDate = relay.RelayDate, To = relay.Email }); } + messagesRepository.DbContext.SaveChanges(); } + return Ok(); } @@ -122,7 +126,8 @@ public string GetPartSourceRaw(Guid id, string partid) public string GetMessageSourceRaw(Guid id) { ApiModel.Message message = GetMessage(id); - return System.Text.Encoding.UTF8.GetString(message.Data); + var encoding = message.MimeMessage?.Body?.ContentType.CharsetEncoding ?? ApiModel.Message.GetSessionEncodingOrAssumed(message); + return encoding.GetString(message.Data); } [HttpGet("{id}/source")] @@ -130,7 +135,8 @@ public string GetMessageSourceRaw(Guid id) public string GetMessageSource(Guid id) { ApiModel.Message message = GetMessage(id); - return message.MimeMessage.ToString(); + + return message.MimeMessage?.HtmlBody ?? message.MimeMessage?.TextBody ?? ""; } [HttpGet("{id}/html")] @@ -145,8 +151,7 @@ public string GetMessageHtml(Guid id) { html = "
" + HtmlAgilityPack.HtmlDocument.HtmlEncode(message.MimeMessage?.TextBody ?? "") + "
"; } - - + HtmlDocument doc = new HtmlDocument(); doc.LoadHtml(html); diff --git a/Rnwood.Smtp4dev/DbModel/Message.cs b/Rnwood.Smtp4dev/DbModel/Message.cs index ccb2faa1d..1e6f80c98 100644 --- a/Rnwood.Smtp4dev/DbModel/Message.cs +++ b/Rnwood.Smtp4dev/DbModel/Message.cs @@ -19,6 +19,8 @@ public class Message public byte[] Data { get; set; } public string MimeParseError { get; set; } + + public string SessionEncoding { get; set; } public Session Session { get; set; } public int AttachmentCount { get; set; } @@ -27,6 +29,8 @@ public class Message public string RelayError { get; internal set; } public bool SecureConnection { get; set; } + + public bool? EightBitTransport { get; set; } public virtual List Relays { get; set; } = new List(); diff --git a/Rnwood.Smtp4dev/Migrations/20240308081358_AddSessionEncoding.Designer.cs b/Rnwood.Smtp4dev/Migrations/20240308081358_AddSessionEncoding.Designer.cs new file mode 100644 index 000000000..8f70d6e3c --- /dev/null +++ b/Rnwood.Smtp4dev/Migrations/20240308081358_AddSessionEncoding.Designer.cs @@ -0,0 +1,173 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Rnwood.Smtp4dev.Data; + +#nullable disable + +namespace Rnwood.Smtp4dev.Migrations +{ + [DbContext(typeof(Smtp4devDbContext))] + [Migration("20240308081358_AddSessionEncoding")] + partial class AddSessionEncoding + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.ImapState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("LastUid") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ImapState"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AttachmentCount") + .HasColumnType("INTEGER"); + + b.Property("Data") + .HasColumnType("BLOB"); + + b.Property("From") + .HasColumnType("TEXT"); + + b.Property("ImapUid") + .HasColumnType("INTEGER"); + + b.Property("IsUnread") + .HasColumnType("INTEGER"); + + b.Property("MimeParseError") + .HasColumnType("TEXT"); + + b.Property("ReceivedDate") + .HasColumnType("TEXT"); + + b.Property("RelayError") + .HasColumnType("TEXT"); + + b.Property("SecureConnection") + .HasColumnType("INTEGER"); + + b.Property("SessionEncoding") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.Property("To") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SessionId"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.MessageRelay", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("MessageId") + .HasColumnType("TEXT"); + + b.Property("SendDate") + .HasColumnType("TEXT"); + + b.Property("To") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.ToTable("MessageRelays"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.Session", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClientAddress") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("Log") + .HasColumnType("TEXT"); + + b.Property("NumberOfMessages") + .HasColumnType("INTEGER"); + + b.Property("SessionError") + .HasColumnType("TEXT"); + + b.Property("SessionErrorType") + .HasColumnType("INTEGER"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Sessions"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.Message", b => + { + b.HasOne("Rnwood.Smtp4dev.DbModel.Session", "Session") + .WithMany() + .HasForeignKey("SessionId"); + + b.Navigation("Session"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.MessageRelay", b => + { + b.HasOne("Rnwood.Smtp4dev.DbModel.Message", "Message") + .WithMany("Relays") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.Message", b => + { + b.Navigation("Relays"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Rnwood.Smtp4dev/Migrations/20240308081358_AddSessionEncoding.cs b/Rnwood.Smtp4dev/Migrations/20240308081358_AddSessionEncoding.cs new file mode 100644 index 000000000..25c247418 --- /dev/null +++ b/Rnwood.Smtp4dev/Migrations/20240308081358_AddSessionEncoding.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Rnwood.Smtp4dev.Migrations +{ + /// + public partial class AddSessionEncoding : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SessionEncoding", + table: "Messages", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SessionEncoding", + table: "Messages"); + } + } +} diff --git a/Rnwood.Smtp4dev/Migrations/20240309084542_AddMessageEightBitTransport.Designer.cs b/Rnwood.Smtp4dev/Migrations/20240309084542_AddMessageEightBitTransport.Designer.cs new file mode 100644 index 000000000..e7d1139d9 --- /dev/null +++ b/Rnwood.Smtp4dev/Migrations/20240309084542_AddMessageEightBitTransport.Designer.cs @@ -0,0 +1,176 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Rnwood.Smtp4dev.Data; + +#nullable disable + +namespace Rnwood.Smtp4dev.Migrations +{ + [DbContext(typeof(Smtp4devDbContext))] + [Migration("20240309084542_AddMessageEightBitTransport")] + partial class AddMessageEightBitTransport + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.ImapState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("LastUid") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ImapState"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AttachmentCount") + .HasColumnType("INTEGER"); + + b.Property("Data") + .HasColumnType("BLOB"); + + b.Property("EightBitTransport") + .HasColumnType("INTEGER"); + + b.Property("From") + .HasColumnType("TEXT"); + + b.Property("ImapUid") + .HasColumnType("INTEGER"); + + b.Property("IsUnread") + .HasColumnType("INTEGER"); + + b.Property("MimeParseError") + .HasColumnType("TEXT"); + + b.Property("ReceivedDate") + .HasColumnType("TEXT"); + + b.Property("RelayError") + .HasColumnType("TEXT"); + + b.Property("SecureConnection") + .HasColumnType("INTEGER"); + + b.Property("SessionEncoding") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.Property("To") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SessionId"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.MessageRelay", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("MessageId") + .HasColumnType("TEXT"); + + b.Property("SendDate") + .HasColumnType("TEXT"); + + b.Property("To") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.ToTable("MessageRelays"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.Session", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClientAddress") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("Log") + .HasColumnType("TEXT"); + + b.Property("NumberOfMessages") + .HasColumnType("INTEGER"); + + b.Property("SessionError") + .HasColumnType("TEXT"); + + b.Property("SessionErrorType") + .HasColumnType("INTEGER"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Sessions"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.Message", b => + { + b.HasOne("Rnwood.Smtp4dev.DbModel.Session", "Session") + .WithMany() + .HasForeignKey("SessionId"); + + b.Navigation("Session"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.MessageRelay", b => + { + b.HasOne("Rnwood.Smtp4dev.DbModel.Message", "Message") + .WithMany("Relays") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.Message", b => + { + b.Navigation("Relays"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Rnwood.Smtp4dev/Migrations/20240309084542_AddMessageEightBitTransport.cs b/Rnwood.Smtp4dev/Migrations/20240309084542_AddMessageEightBitTransport.cs new file mode 100644 index 000000000..94e88e2cd --- /dev/null +++ b/Rnwood.Smtp4dev/Migrations/20240309084542_AddMessageEightBitTransport.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Rnwood.Smtp4dev.Migrations +{ + /// + public partial class AddMessageEightBitTransport : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EightBitTransport", + table: "Messages", + type: "INTEGER", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EightBitTransport", + table: "Messages"); + } + } +} diff --git a/Rnwood.Smtp4dev/Migrations/Smtp4devDbContextModelSnapshot.cs b/Rnwood.Smtp4dev/Migrations/Smtp4devDbContextModelSnapshot.cs index 90210a292..0b1ed9c4f 100644 --- a/Rnwood.Smtp4dev/Migrations/Smtp4devDbContextModelSnapshot.cs +++ b/Rnwood.Smtp4dev/Migrations/Smtp4devDbContextModelSnapshot.cs @@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Rnwood.Smtp4dev.Data; +#nullable disable + namespace Rnwood.Smtp4dev.Migrations { [DbContext(typeof(Smtp4devDbContext))] @@ -13,8 +15,7 @@ partial class Smtp4devDbContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.8"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); modelBuilder.Entity("Rnwood.Smtp4dev.DbModel.ImapState", b => { @@ -42,6 +43,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Data") .HasColumnType("BLOB"); + b.Property("EightBitTransport") + .HasColumnType("INTEGER"); + b.Property("From") .HasColumnType("TEXT"); @@ -63,6 +67,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("SecureConnection") .HasColumnType("INTEGER"); + b.Property("SessionEncoding") + .HasColumnType("TEXT"); + b.Property("SessionId") .HasColumnType("TEXT"); diff --git a/Rnwood.Smtp4dev/Properties/launchSettings.json b/Rnwood.Smtp4dev/Properties/launchSettings.json index bdf9011ba..3f0a598c8 100644 --- a/Rnwood.Smtp4dev/Properties/launchSettings.json +++ b/Rnwood.Smtp4dev/Properties/launchSettings.json @@ -18,7 +18,7 @@ }, "Rnwood.Smtp4dev": { "commandName": "Project", - "commandLineArgs": "--imapport=0 --db=c:/temp/smtp4dev.db", + "commandLineArgs": "--imapport=0", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj b/Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj index 8d402551d..ebc5f2f54 100644 --- a/Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj +++ b/Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj @@ -40,7 +40,9 @@ - + + + @@ -50,7 +52,11 @@ - + + + + + diff --git a/Rnwood.Smtp4dev/Server/MessageConverter.cs b/Rnwood.Smtp4dev/Server/MessageConverter.cs index 6e84b11e6..296e6805a 100644 --- a/Rnwood.Smtp4dev/Server/MessageConverter.cs +++ b/Rnwood.Smtp4dev/Server/MessageConverter.cs @@ -3,6 +3,7 @@ using System; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Rnwood.SmtpServer; @@ -81,7 +82,8 @@ public class MessageConverter Data = data, MimeParseError = mimeParseError, AttachmentCount = 0, - SecureConnection = message.SecureConnection + SecureConnection = message.SecureConnection, + SessionEncoding = message.EightBitTransport ? Encoding.UTF8.WebName : Encoding.Latin1.WebName }; var parts = new Message(result).Parts; diff --git a/test/.vscode/launch.json b/test/.vscode/launch.json new file mode 100644 index 000000000..cfb94697c --- /dev/null +++ b/test/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "C#: test Debug", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/test.csproj" + } + + ] +} \ No newline at end of file diff --git a/test/Program.cs b/test/Program.cs new file mode 100644 index 000000000..84ceb20b4 --- /dev/null +++ b/test/Program.cs @@ -0,0 +1,73 @@ +using System.Net.Mail; +using System.Net.Mime; +using System.Text; + +var smtpClient = new SmtpClient("localhost") + { + Port = 2525, + }; + + var mailMessage = new MailMessage + { + From = new MailAddress("latin.test@mailinator.com"), + Subject = "Latin test - Latin1 just send 8", + //BodyTransferEncoding = TransferEncoding., + Body = + "Homines in indicaverunt nam purus quáestionem sentiri unum. Afflueret contentus diam errore faciam, honoris mucius omnem pélléntésqué reiciendis. Acuti admissum arbitrantur concederetur dediti, ferrentur fugiendus inferiorem peccant ponti quando solam ullius. áb atilii concursio constituamus, définitioném diligenter graeci illam máius operis opinionum pótióne versatur. Alliciat aspernari consoletur disserunt, impendere interiret reliquarum verum. Convállis essent foedus gravida iustioribus, mox notissima perpaulum praeclare probatum, prohiberet sensibus. Condimentum efficeretur iis insipientiam, inutile logikh ne ornare, paulo primis primo pugnare putarent quiddam reperiuntur. \r\nCéramico cónsistat éiusdém licet offendimur, recusandae referendá. Cupiditatés hónesta musicis possent, respondendum sollicitudines. Breviter democrito dolor electram illa, ludicra non occulta pérféréndis principio servare suum tranquillitatem. Consentinis probatus qualisque tollatur veritatis. In inséquitur ortum pertinaces, sentit stoici sum téréntii.", + BodyEncoding = Encoding.Latin1, + BodyTransferEncoding = TransferEncoding.EightBit, + IsBodyHtml = true, + }; + mailMessage.To.Add("latin.test@mailinator.com"); + + smtpClient.Send(mailMessage); + + mailMessage = new MailMessage + { + From = new MailAddress("latin.test@mailinator.com"), + Subject = "Latin test - Latin1 QP", + //BodyTransferEncoding = TransferEncoding., + Body = + "Homines in indicaverunt nam purus quáestionem sentiri unum. Afflueret contentus diam errore faciam, honoris mucius omnem pélléntésqué reiciendis. Acuti admissum arbitrantur concederetur dediti, ferrentur fugiendus inferiorem peccant ponti quando solam ullius. áb atilii concursio constituamus, définitioném diligenter graeci illam máius operis opinionum pótióne versatur. Alliciat aspernari consoletur disserunt, impendere interiret reliquarum verum. Convállis essent foedus gravida iustioribus, mox notissima perpaulum praeclare probatum, prohiberet sensibus. Condimentum efficeretur iis insipientiam, inutile logikh ne ornare, paulo primis primo pugnare putarent quiddam reperiuntur. \r\nCéramico cónsistat éiusdém licet offendimur, recusandae referendá. Cupiditatés hónesta musicis possent, respondendum sollicitudines. Breviter democrito dolor electram illa, ludicra non occulta pérféréndis principio servare suum tranquillitatem. Consentinis probatus qualisque tollatur veritatis. In inséquitur ortum pertinaces, sentit stoici sum téréntii.", + BodyEncoding = Encoding.Latin1, + BodyTransferEncoding = TransferEncoding.QuotedPrintable, + IsBodyHtml = true, + }; + mailMessage.To.Add("latin.test@mailinator.com"); + smtpClient.Send(mailMessage); + + mailMessage = new MailMessage + { + From = new MailAddress("latin.test@mailinator.com"), + Subject = "Latin test - UTF-8 just send 8", + //BodyTransferEncoding = TransferEncoding., + Body = + "Homines in indicaverunt nam purus quáestionem sentiri unum. Afflueret contentus diam errore faciam, honoris mucius omnem pélléntésqué reiciendis. Acuti admissum arbitrantur concederetur dediti, ferrentur fugiendus inferiorem peccant ponti quando solam ullius. áb atilii concursio constituamus, définitioném diligenter graeci illam máius operis opinionum pótióne versatur. Alliciat aspernari consoletur disserunt, impendere interiret reliquarum verum. Convállis essent foedus gravida iustioribus, mox notissima perpaulum praeclare probatum, prohiberet sensibus. Condimentum efficeretur iis insipientiam, inutile logikh ne ornare, paulo primis primo pugnare putarent quiddam reperiuntur. \r\nCéramico cónsistat éiusdém licet offendimur, recusandae referendá. Cupiditatés hónesta musicis possent, respondendum sollicitudines. Breviter democrito dolor electram illa, ludicra non occulta pérféréndis principio servare suum tranquillitatem. Consentinis probatus qualisque tollatur veritatis. In inséquitur ortum pertinaces, sentit stoici sum téréntii.", + BodyEncoding = Encoding.UTF8, + BodyTransferEncoding = TransferEncoding.EightBit, + IsBodyHtml = true + }; + mailMessage.To.Add("latin.test@mailinator.com"); + + smtpClient.Send(mailMessage); + + mailMessage = new MailMessage + { + From = new MailAddress("latin.test@mailinator.com"), + Subject = "Latin test - UTF-8 8bit mime", + //BodyTransferEncoding = TransferEncoding., + Body = + "Homines in indicaverunt nam purus quáestionem sentiri unum. Afflueret contentus diam errore faciam, honoris mucius omnem pélléntésqué reiciendis. Acuti admissum arbitrantur concederetur dediti, ferrentur fugiendus inferiorem peccant ponti quando solam ullius. áb atilii concursio constituamus, définitioném diligenter graeci illam máius operis opinionum pótióne versatur. Alliciat aspernari consoletur disserunt, impendere interiret reliquarum verum. Convállis essent foedus gravida iustioribus, mox notissima perpaulum praeclare probatum, prohiberet sensibus. Condimentum efficeretur iis insipientiam, inutile logikh ne ornare, paulo primis primo pugnare putarent quiddam reperiuntur. \r\nCéramico cónsistat éiusdém licet offendimur, recusandae referendá. Cupiditatés hónesta musicis possent, respondendum sollicitudines. Breviter democrito dolor electram illa, ludicra non occulta pérféréndis principio servare suum tranquillitatem. Consentinis probatus qualisque tollatur veritatis. In inséquitur ortum pertinaces, sentit stoici sum téréntii.", + BodyEncoding = Encoding.UTF8, + BodyTransferEncoding = TransferEncoding.EightBit, + IsBodyHtml = true + }; + mailMessage.To.Add("latin.test@mailinator.com"); + + smtpClient = new SmtpClient("localhost") + { + Port = 2525, + DeliveryFormat = SmtpDeliveryFormat.International + }; + + smtpClient.Send(mailMessage); \ No newline at end of file diff --git a/test/test.csproj b/test/test.csproj new file mode 100644 index 000000000..206b89a9a --- /dev/null +++ b/test/test.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/test/test.sln b/test/test.sln new file mode 100644 index 000000000..6468f1d41 --- /dev/null +++ b/test/test.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test", "test.csproj", "{EDB2CA4B-890E-4C1A-902B-DD282C6207E4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EDB2CA4B-890E-4C1A-902B-DD282C6207E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDB2CA4B-890E-4C1A-902B-DD282C6207E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDB2CA4B-890E-4C1A-902B-DD282C6207E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDB2CA4B-890E-4C1A-902B-DD282C6207E4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {344E30D1-C074-4E04-A80E-3FFDAC231B85} + EndGlobalSection +EndGlobal