mirror of https://github.com/adamdruppe/arsd.git
better mime fixing
This commit is contained in:
parent
620d5587f0
commit
4ff49846d4
219
email.d
219
email.d
|
|
@ -4,6 +4,7 @@ import std.net.curl;
|
||||||
pragma(lib, "curl");
|
pragma(lib, "curl");
|
||||||
|
|
||||||
import std.base64;
|
import std.base64;
|
||||||
|
import std.string;
|
||||||
|
|
||||||
// SEE ALSO: std.net.curl.SMTP
|
// SEE ALSO: std.net.curl.SMTP
|
||||||
|
|
||||||
|
|
@ -50,49 +51,136 @@ class EmailMessage {
|
||||||
string type;
|
string type;
|
||||||
string filename;
|
string filename;
|
||||||
const(void)[] content;
|
const(void)[] content;
|
||||||
|
string id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const(MimeAttachment)[] attachments;
|
const(MimeAttachment)[] attachments;
|
||||||
|
|
||||||
void addAttachment(string mimeType, string filename, in void[] content) {
|
void addAttachment(string mimeType, string filename, in void[] content, string id = null) {
|
||||||
isMime = true;
|
isMime = true;
|
||||||
attachments ~= MimeAttachment(mimeType, filename, content);
|
attachments ~= MimeAttachment(mimeType, filename, content, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// in the html, use img src="cid:ID_GIVEN_HERE"
|
||||||
|
void addInlineImage(string id, string mimeType, string filename, in void[] content) {
|
||||||
|
assert(isHtml);
|
||||||
|
isMime = true;
|
||||||
|
inlineImages ~= MimeAttachment(mimeType, filename, content, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const(MimeAttachment)[] inlineImages;
|
||||||
|
|
||||||
|
|
||||||
|
/* we should build out the mime thingy
|
||||||
|
related
|
||||||
|
mixed
|
||||||
|
alternate
|
||||||
|
*/
|
||||||
|
|
||||||
override string toString() {
|
override string toString() {
|
||||||
string boundary = "0016e64be86203dd36047610926a"; // FIXME
|
|
||||||
|
|
||||||
assert(!isHtml || (isHtml && isMime));
|
assert(!isHtml || (isHtml && isMime));
|
||||||
|
|
||||||
auto headers = this.headers;
|
auto headers = this.headers;
|
||||||
|
|
||||||
string toHeader = "To: ";
|
|
||||||
bool toHeaderOutputted = false;
|
|
||||||
foreach(t; to) {
|
|
||||||
if(toHeaderOutputted)
|
|
||||||
toHeader ~= ", ";
|
|
||||||
else
|
|
||||||
toHeaderOutputted = true;
|
|
||||||
|
|
||||||
toHeader ~= t;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(to.length)
|
if(to.length)
|
||||||
headers ~= toHeader;
|
headers ~= "To: " ~ join(to, ", ");
|
||||||
|
if(cc.length)
|
||||||
|
headers ~= "Cc: " ~ join(cc, ", ");
|
||||||
|
|
||||||
|
if(from.length)
|
||||||
|
headers ~= "From: " ~ from;
|
||||||
|
|
||||||
if(subject !is null)
|
if(subject !is null)
|
||||||
headers ~= "Subject: " ~ subject;
|
headers ~= "Subject: " ~ subject;
|
||||||
|
if(replyTo !is null)
|
||||||
|
headers ~= "Reply-To: " ~ replyTo;
|
||||||
|
if(inReplyTo !is null)
|
||||||
|
headers ~= "In-Reply-To: " ~ inReplyTo;
|
||||||
|
|
||||||
if(isMime)
|
if(isMime)
|
||||||
headers ~= "MIME-Version: 1.0";
|
headers ~= "MIME-Version: 1.0";
|
||||||
|
|
||||||
|
/+
|
||||||
|
if(inlineImages.length) {
|
||||||
|
headers ~= "Content-Type: multipart/related; boundary=" ~ boundary;
|
||||||
|
// so we put the alternative inside asthe first attachment with as seconary boundary
|
||||||
|
// then we do the images
|
||||||
|
} else
|
||||||
if(attachments.length)
|
if(attachments.length)
|
||||||
headers ~= "Content-Type: multipart/mixed; boundary=" ~ boundary;
|
headers ~= "Content-Type: multipart/mixed; boundary=" ~ boundary;
|
||||||
else if(isHtml)
|
else if(isHtml)
|
||||||
headers ~= "Content-Type: multipart/alternative; boundary=" ~ boundary;
|
headers ~= "Content-Type: multipart/alternative; boundary=" ~ boundary;
|
||||||
else
|
else
|
||||||
headers ~= "Content-Type: text/plain; charset=UTF-8";
|
headers ~= "Content-Type: text/plain; charset=UTF-8";
|
||||||
|
+/
|
||||||
|
|
||||||
|
|
||||||
|
string msgContent;
|
||||||
|
|
||||||
|
if(isMime) {
|
||||||
|
MimeContainer top;
|
||||||
|
|
||||||
|
{
|
||||||
|
MimeContainer mimeMessage;
|
||||||
|
if(isHtml) {
|
||||||
|
auto alternative = new MimeContainer("multipart/alternative");
|
||||||
|
alternative.stuff ~= new MimeContainer("text/plain; charset=UTF-8", textBody);
|
||||||
|
alternative.stuff ~= new MimeContainer("text/html; charset=UTF-8", htmlBody);
|
||||||
|
mimeMessage = alternative;
|
||||||
|
} else {
|
||||||
|
mimeMessage = new MimeContainer("text/plain; charset=UTF-8", textBody);
|
||||||
|
}
|
||||||
|
top = mimeMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MimeContainer mimeRelated;
|
||||||
|
if(inlineImages.length) {
|
||||||
|
mimeRelated = new MimeContainer("multipart/related");
|
||||||
|
|
||||||
|
mimeRelated.stuff ~= top;
|
||||||
|
top = mimeRelated;
|
||||||
|
|
||||||
|
foreach(attachment; inlineImages) {
|
||||||
|
auto mimeAttachment = new MimeContainer(attachment.type ~ "; name=\""~attachment.filename~"\"");
|
||||||
|
mimeAttachment.headers ~= "Content-Transfer-Encoding: base64";
|
||||||
|
mimeAttachment.headers ~= "Content-ID: <" ~ attachment.id ~ ">";
|
||||||
|
mimeAttachment.content = Base64.encode(cast(const(ubyte)[]) attachment.content);
|
||||||
|
|
||||||
|
mimeRelated.stuff ~= mimeAttachment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MimeContainer mimeMixed;
|
||||||
|
if(attachments.length) {
|
||||||
|
mimeMixed = new MimeContainer("multipart/mixed");
|
||||||
|
|
||||||
|
mimeMixed.stuff ~= top;
|
||||||
|
top = mimeMixed;
|
||||||
|
|
||||||
|
foreach(attachment; attachments) {
|
||||||
|
auto mimeAttachment = new MimeContainer(attachment.type);
|
||||||
|
mimeAttachment.headers ~= "Content-Disposition: attachment; filename=\""~attachment.filename~"\"";
|
||||||
|
mimeAttachment.headers ~= "Content-Transfer-Encoding: base64";
|
||||||
|
if(attachment.id.length)
|
||||||
|
mimeAttachment.headers ~= "Content-ID: <" ~ attachment.id ~ ">";
|
||||||
|
|
||||||
|
mimeAttachment.content = Base64.encode(cast(const(ubyte)[]) attachment.content);
|
||||||
|
|
||||||
|
mimeMixed.stuff ~= mimeAttachment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headers ~= top.contentType;
|
||||||
|
msgContent = top.toMimeString(true);
|
||||||
|
} else {
|
||||||
|
headers ~= "Content-Type: text/plain; charset=UTF-8";
|
||||||
|
msgContent = textBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
string msg;
|
string msg;
|
||||||
msg.reserve(htmlBody.length + textBody.length + 1024);
|
msg.reserve(htmlBody.length + textBody.length + 1024);
|
||||||
|
|
@ -102,46 +190,28 @@ class EmailMessage {
|
||||||
if(msg.length) // has headers
|
if(msg.length) // has headers
|
||||||
msg ~= "\r\n";
|
msg ~= "\r\n";
|
||||||
|
|
||||||
if(isMime) {
|
msg ~= msgContent;
|
||||||
msg ~= "--" ~ boundary ~ "\r\n";
|
|
||||||
msg ~= "Content-Type: text/plain; charset=UTF-8\r\n\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
msg ~= textBody;
|
|
||||||
|
|
||||||
if(isMime)
|
|
||||||
msg ~= "\r\n--" ~ boundary;
|
|
||||||
if(isHtml) {
|
|
||||||
msg ~= "\r\n";
|
|
||||||
msg ~= "Content-Type: text/html; charset=UTF-8\r\n\r\n";
|
|
||||||
msg ~= htmlBody;
|
|
||||||
msg ~= "\r\n--" ~ boundary;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(attachment; attachments) {
|
|
||||||
assert(isMime);
|
|
||||||
msg ~= "\r\n";
|
|
||||||
msg ~= "Content-Type: " ~ attachment.type ~ "\r\n";
|
|
||||||
msg ~= "Content-Disposition: attachment; filename=\""~attachment.filename~"\"\r\n";
|
|
||||||
msg ~= "Content-Transfer-Encoding: base64\r\n";
|
|
||||||
msg ~= "\r\n";
|
|
||||||
msg ~= Base64.encode(cast(const(ubyte)[]) attachment.content);
|
|
||||||
msg ~= "\r\n--" ~ boundary;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isMime)
|
|
||||||
msg ~= "--\r\n";
|
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
void send(RelayInfo mailServer = RelayInfo("smtp://localhost")) {
|
void send(RelayInfo mailServer = RelayInfo("smtp://localhost")) {
|
||||||
auto smtp = new SMTP(mailServer.server);
|
auto smtp = new SMTP(mailServer.server);
|
||||||
|
// smtp.verbose = true;
|
||||||
if(mailServer.username.length)
|
if(mailServer.username.length)
|
||||||
smtp.setAuthentication(mailServer.username, mailServer.password);
|
smtp.setAuthentication(mailServer.username, mailServer.password);
|
||||||
const(char)[][] allRecipients = cast(const(char)[][]) (to ~ cc ~ bcc); // WTF cast
|
const(char)[][] allRecipients = cast(const(char)[][]) (to ~ cc ~ bcc); // WTF cast
|
||||||
smtp.mailTo(allRecipients);
|
smtp.mailTo(allRecipients);
|
||||||
smtp.mailFrom = from;
|
|
||||||
|
auto mailFrom = from;
|
||||||
|
auto idx = mailFrom.indexOf("<");
|
||||||
|
if(idx != -1)
|
||||||
|
mailFrom = mailFrom[idx + 1 .. $];
|
||||||
|
idx = mailFrom.indexOf(">");
|
||||||
|
if(idx != -1)
|
||||||
|
mailFrom = mailFrom[0 .. idx];
|
||||||
|
|
||||||
|
smtp.mailFrom = mailFrom;
|
||||||
smtp.message = this.toString();
|
smtp.message = this.toString();
|
||||||
smtp.perform();
|
smtp.perform();
|
||||||
}
|
}
|
||||||
|
|
@ -155,3 +225,60 @@ void email(string to, string subject, string message, string from, RelayInfo mai
|
||||||
msg.textBody = message;
|
msg.textBody = message;
|
||||||
msg.send(mailServer);
|
msg.send(mailServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// private:
|
||||||
|
|
||||||
|
import std.conv;
|
||||||
|
|
||||||
|
class MimeContainer {
|
||||||
|
private static int sequence;
|
||||||
|
|
||||||
|
immutable string _contentType;
|
||||||
|
immutable string boundary;
|
||||||
|
|
||||||
|
string[] headers; // NOT including content-type
|
||||||
|
string content;
|
||||||
|
MimeContainer[] stuff;
|
||||||
|
|
||||||
|
this(string contentType, string content = null) {
|
||||||
|
this._contentType = contentType;
|
||||||
|
this.content = content;
|
||||||
|
sequence++;
|
||||||
|
if(_contentType.indexOf("multipart/") == 0)
|
||||||
|
boundary = "0016e64be86203dd36047610926a" ~ to!string(sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
@property string contentType() {
|
||||||
|
string ct = "Content-Type: "~_contentType;
|
||||||
|
if(boundary.length)
|
||||||
|
ct ~= "; boundary=" ~ boundary;
|
||||||
|
return ct;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string toMimeString(bool isRoot = false) {
|
||||||
|
string ret;
|
||||||
|
|
||||||
|
if(!isRoot) {
|
||||||
|
ret ~= contentType;
|
||||||
|
foreach(header; headers) {
|
||||||
|
ret ~= "\r\n";
|
||||||
|
ret ~= header;
|
||||||
|
}
|
||||||
|
ret ~= "\r\n\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
ret ~= content;
|
||||||
|
|
||||||
|
foreach(idx, thing; stuff) {
|
||||||
|
assert(boundary.length);
|
||||||
|
ret ~= "\r\n--" ~ boundary ~ "\r\n";
|
||||||
|
ret ~= thing.toMimeString(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(boundary.length)
|
||||||
|
ret ~= "\r\n--" ~ boundary ~ "--";
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue