mirror of https://github.com/adamdruppe/arsd.git
Merge branch 'master' into EMail_Remove_Allocation
This commit is contained in:
commit
2ce34139d6
63
email.d
63
email.d
|
@ -78,6 +78,15 @@ class EmailMessage {
|
||||||
|
|
||||||
string[] headers;
|
string[] headers;
|
||||||
|
|
||||||
|
/** If you use the send method with an SMTP server, you don't want to change this.
|
||||||
|
*
|
||||||
|
* While RFC 2045 mandates CRLF as a lineseperator, there are some edge-cases where this won't work.
|
||||||
|
* When passing the E-Mail string to a unix program which handles communication with the SMTP server, some (i.e. qmail)
|
||||||
|
* expect the system lineseperator (LF) instead.
|
||||||
|
* Notably, the google mail REST API will choke on CRLF lineseps and produce strange emails (as of 2024).
|
||||||
|
*/
|
||||||
|
string linesep = "\r\n";
|
||||||
|
|
||||||
private bool isMime = false;
|
private bool isMime = false;
|
||||||
private bool isHtml = false;
|
private bool isHtml = false;
|
||||||
|
|
||||||
|
@ -216,7 +225,7 @@ class EmailMessage {
|
||||||
auto mimeAttachment = new MimeContainer(attachment.type ~ "; name=\""~attachment.filename~"\"");
|
auto mimeAttachment = new MimeContainer(attachment.type ~ "; name=\""~attachment.filename~"\"");
|
||||||
mimeAttachment.headers ~= "Content-Transfer-Encoding: base64";
|
mimeAttachment.headers ~= "Content-Transfer-Encoding: base64";
|
||||||
mimeAttachment.headers ~= "Content-ID: <" ~ attachment.id ~ ">";
|
mimeAttachment.headers ~= "Content-ID: <" ~ attachment.id ~ ">";
|
||||||
mimeAttachment.content = encodeBase64Mime(cast(const(ubyte)[]) attachment.content);
|
mimeAttachment.content = encodeBase64Mime(cast(const(ubyte)[]) attachment.content, this.linesep);
|
||||||
|
|
||||||
mimeRelated.stuff ~= mimeAttachment;
|
mimeRelated.stuff ~= mimeAttachment;
|
||||||
}
|
}
|
||||||
|
@ -238,7 +247,7 @@ class EmailMessage {
|
||||||
if(attachment.id.length)
|
if(attachment.id.length)
|
||||||
mimeAttachment.headers ~= "Content-ID: <" ~ attachment.id ~ ">";
|
mimeAttachment.headers ~= "Content-ID: <" ~ attachment.id ~ ">";
|
||||||
|
|
||||||
mimeAttachment.content = encodeBase64Mime(cast(const(ubyte)[]) attachment.content);
|
mimeAttachment.content = encodeBase64Mime(cast(const(ubyte)[]) attachment.content, this.linesep);
|
||||||
|
|
||||||
mimeMixed.stuff ~= mimeAttachment;
|
mimeMixed.stuff ~= mimeAttachment;
|
||||||
}
|
}
|
||||||
|
@ -246,7 +255,7 @@ class EmailMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
headers ~= top.contentType;
|
headers ~= top.contentType;
|
||||||
msgContent = top.toMimeString(true);
|
msgContent = top.toMimeString(true, this.linesep);
|
||||||
} else {
|
} else {
|
||||||
headers ~= "Content-Type: text/plain; charset=UTF-8";
|
headers ~= "Content-Type: text/plain; charset=UTF-8";
|
||||||
msgContent = textBody;
|
msgContent = textBody;
|
||||||
|
@ -257,9 +266,9 @@ class EmailMessage {
|
||||||
msg.reserve(htmlBody.length + textBody.length + 1024);
|
msg.reserve(htmlBody.length + textBody.length + 1024);
|
||||||
|
|
||||||
foreach(header; headers)
|
foreach(header; headers)
|
||||||
msg ~= header ~ "\r\n";
|
msg ~= header ~ this.linesep;
|
||||||
if(msg.length) // has headers
|
if(msg.length) // has headers
|
||||||
msg ~= "\r\n";
|
msg ~= this.linesep;
|
||||||
|
|
||||||
msg ~= msgContent;
|
msg ~= msgContent;
|
||||||
|
|
||||||
|
@ -619,28 +628,28 @@ class MimeContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string toMimeString(bool isRoot = false) {
|
string toMimeString(bool isRoot = false, string linesep="\r\n") {
|
||||||
string ret;
|
string ret;
|
||||||
|
|
||||||
if(!isRoot) {
|
if(!isRoot) {
|
||||||
ret ~= contentType;
|
ret ~= contentType;
|
||||||
foreach(header; headers) {
|
foreach(header; headers) {
|
||||||
ret ~= "\r\n";
|
ret ~= linesep;
|
||||||
ret ~= header;
|
ret ~= header;
|
||||||
}
|
}
|
||||||
ret ~= "\r\n\r\n";
|
ret ~= linesep ~ linesep;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret ~= content;
|
ret ~= content;
|
||||||
|
|
||||||
foreach(idx, thing; stuff) {
|
foreach(idx, thing; stuff) {
|
||||||
assert(boundary.length);
|
assert(boundary.length);
|
||||||
ret ~= "\r\n--" ~ boundary ~ "\r\n";
|
ret ~= linesep ~ "--" ~ boundary ~ linesep;
|
||||||
ret ~= thing.toMimeString(false);
|
ret ~= thing.toMimeString(false, linesep);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(boundary.length)
|
if(boundary.length)
|
||||||
ret ~= "\r\n--" ~ boundary ~ "--";
|
ret ~= linesep ~ "--" ~ boundary ~ "--";
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -1167,6 +1176,7 @@ string encodeBase64Mime(const(ubyte[]) content, string LINESEP = "\r\n") {
|
||||||
return cast(immutable(char[]))content.chunks(SOURCE_CHUNK_LENGTH).base64encode.join(LINESEP);
|
return cast(immutable(char[]))content.chunks(SOURCE_CHUNK_LENGTH).base64encode.join(LINESEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Base64 range decoder UFCS helper.
|
/// Base64 range decoder UFCS helper.
|
||||||
alias base64decode = Base64.decoder;
|
alias base64decode = Base64.decoder;
|
||||||
|
|
||||||
|
@ -1191,6 +1201,37 @@ unittest {
|
||||||
assert( source.representation.encodeBase64Mime.decodeBase64Mime.equal(source));
|
assert( source.representation.encodeBase64Mime.decodeBase64Mime.equal(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
import std.algorithm;
|
||||||
|
import std.string;
|
||||||
|
// Mime message roundtrip
|
||||||
|
auto mail = new EmailMessage();
|
||||||
|
mail.to = ["recipient@example.org"];
|
||||||
|
mail.from = "sender@example.org";
|
||||||
|
mail.subject = "Subject";
|
||||||
|
|
||||||
|
auto text = cast(string) chain(
|
||||||
|
repeat('n', 1200),
|
||||||
|
"\r\n",
|
||||||
|
"äöü\r\n",
|
||||||
|
"ඞ\r\nlast",
|
||||||
|
).byChar.array;
|
||||||
|
mail.setTextBody(text);
|
||||||
|
mail.addAttachment("text/plain", "attachment.txt", text.representation);
|
||||||
|
// In case binary and plaintext get handled differently one day
|
||||||
|
mail.addAttachment("application/octet-stream", "attachment.bin", text.representation);
|
||||||
|
|
||||||
|
auto result = new IncomingEmailMessage(mail.toString().split("\r\n"));
|
||||||
|
|
||||||
|
assert(result.subject.equal(mail.subject));
|
||||||
|
assert(mail.to.canFind(result.to));
|
||||||
|
assert(result.from.equal(mail.from));
|
||||||
|
|
||||||
|
// This roundtrip works modulo trailing newline on the parsed message and LF vs CRLF
|
||||||
|
assert(result.textMessageBody.replace("\n", "\r\n").stripRight().equal(mail.textBody));
|
||||||
|
assert(result.attachments.equal(mail.attachments));
|
||||||
|
}
|
||||||
|
|
||||||
/+
|
/+
|
||||||
void main() {
|
void main() {
|
||||||
import std.file;
|
import std.file;
|
||||||
|
|
Loading…
Reference in New Issue