From a2bec4b4fa3cdb3586f5f5591873ad566a94204d Mon Sep 17 00:00:00 2001 From: anon Date: Fri, 19 Apr 2024 01:20:08 +0200 Subject: [PATCH 1/3] Remove an allocation when decoding base64 --- email.d | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/email.d b/email.d index 6c704ca..a665650 100644 --- a/email.d +++ b/email.d @@ -9,6 +9,9 @@ pragma(lib, "curl"); import std.base64; import std.string; import std.range; +import std.utf; +import std.array; +import std.algorithm.iteration; import arsd.characterencodings; @@ -866,18 +869,10 @@ class IncomingEmailMessage { break; case "base64": if(textMessageBody.length) { - // alas, phobos' base64 decoder cannot accept ranges, so we have to allocate here - char[] mmb; - mmb.reserve(textMessageBody.length); - foreach (char ch; textMessageBody) if (ch > ' ' && ch < 127) mmb ~= ch; - textMessageBody = convertToUtf8Lossy(Base64.decode(mmb), charset); + textMessageBody = textMessageBody.decodeBase64Mime.convertToUtf8Lossy(charset); } if(htmlMessageBody.length) { - // alas, phobos' base64 decoder cannot accept ranges, so we have to allocate here - char[] mmb; - mmb.reserve(htmlMessageBody.length); - foreach (char ch; htmlMessageBody) if (ch > ' ' && ch < 127) mmb ~= ch; - htmlMessageBody = convertToUtf8Lossy(Base64.decode(mmb), charset); + htmlMessageBody = htmlMessageBody.decodeBase64Mime.convertToUtf8Lossy(charset); } break; @@ -1172,6 +1167,20 @@ string encodeBase64Mime(const(ubyte[]) content, string LINESEP = "\r\n") { return cast(immutable(char[]))content.chunks(SOURCE_CHUNK_LENGTH).base64encode.join(LINESEP); } +/// Base64 range decoder UFCS helper. +alias base64decode = Base64.decoder; + +/// Base64 decoder, ignoring linebreaks which are mandated by RFC2045 +immutable(ubyte[]) decodeBase64Mime(string encodedPart) { + return cast(immutable(ubyte[])) encodedPart + .byChar // prevent Autodecoding, which will break Base64 decoder. Since its base64, it's guarenteed to be 7bit ascii + .filter!((c) => c != '\r') + .splitter!((c) => c == '\n') + .joiner + .base64decode + .array; +} + /+ void main() { import std.file; From ca4adf991f4287de596bf31e6fa96c668d6bcfaa Mon Sep 17 00:00:00 2001 From: anon Date: Fri, 19 Apr 2024 12:46:57 +0200 Subject: [PATCH 2/3] Clean up stupid code --- email.d | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/email.d b/email.d index a665650..a121900 100644 --- a/email.d +++ b/email.d @@ -1174,9 +1174,7 @@ alias base64decode = Base64.decoder; immutable(ubyte[]) decodeBase64Mime(string encodedPart) { return cast(immutable(ubyte[])) encodedPart .byChar // prevent Autodecoding, which will break Base64 decoder. Since its base64, it's guarenteed to be 7bit ascii - .filter!((c) => c != '\r') - .splitter!((c) => c == '\n') - .joiner + .filter!((c) => (c != '\r') & (c != '\n')) .base64decode .array; } From aa0f47caa1e3d4521f9d6f1dbb7b6ef2b104c41d Mon Sep 17 00:00:00 2001 From: anon Date: Fri, 19 Apr 2024 12:47:16 +0200 Subject: [PATCH 3/3] Unittest mime base64 decoder encoder --- email.d | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/email.d b/email.d index a121900..79f652a 100644 --- a/email.d +++ b/email.d @@ -1179,6 +1179,18 @@ immutable(ubyte[]) decodeBase64Mime(string encodedPart) { .array; } +unittest { + // Mime base64 roundtrip + import std.algorithm.comparison; + string source = chain( + repeat('n', 1200), //long line + "\r\n", + "äöü\r\n", + "ඞ\rn", + ).byChar.array; + assert( source.representation.encodeBase64Mime.decodeBase64Mime.equal(source)); +} + /+ void main() { import std.file;