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");
|
||||
|
||||
import std.base64;
|
||||
import std.string;
|
||||
|
||||
// SEE ALSO: std.net.curl.SMTP
|
||||
|
||||
|
@ -50,49 +51,136 @@ class EmailMessage {
|
|||
string type;
|
||||
string filename;
|
||||
const(void)[] content;
|
||||
string id;
|
||||
}
|
||||
|
||||
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;
|
||||
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() {
|
||||
string boundary = "0016e64be86203dd36047610926a"; // FIXME
|
||||
|
||||
assert(!isHtml || (isHtml && isMime));
|
||||
|
||||
auto headers = this.headers;
|
||||
|
||||
string toHeader = "To: ";
|
||||
bool toHeaderOutputted = false;
|
||||
foreach(t; to) {
|
||||
if(toHeaderOutputted)
|
||||
toHeader ~= ", ";
|
||||
else
|
||||
toHeaderOutputted = true;
|
||||
|
||||
toHeader ~= t;
|
||||
}
|
||||
|
||||
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)
|
||||
headers ~= "Subject: " ~ subject;
|
||||
if(replyTo !is null)
|
||||
headers ~= "Reply-To: " ~ replyTo;
|
||||
if(inReplyTo !is null)
|
||||
headers ~= "In-Reply-To: " ~ inReplyTo;
|
||||
|
||||
if(isMime)
|
||||
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)
|
||||
headers ~= "Content-Type: multipart/mixed; boundary=" ~ boundary;
|
||||
else if(isHtml)
|
||||
headers ~= "Content-Type: multipart/alternative; boundary=" ~ boundary;
|
||||
else
|
||||
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;
|
||||
msg.reserve(htmlBody.length + textBody.length + 1024);
|
||||
|
@ -102,46 +190,28 @@ class EmailMessage {
|
|||
if(msg.length) // has headers
|
||||
msg ~= "\r\n";
|
||||
|
||||
if(isMime) {
|
||||
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";
|
||||
msg ~= msgContent;
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
void send(RelayInfo mailServer = RelayInfo("smtp://localhost")) {
|
||||
auto smtp = new SMTP(mailServer.server);
|
||||
// smtp.verbose = true;
|
||||
if(mailServer.username.length)
|
||||
smtp.setAuthentication(mailServer.username, mailServer.password);
|
||||
const(char)[][] allRecipients = cast(const(char)[][]) (to ~ cc ~ bcc); // WTF cast
|
||||
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.perform();
|
||||
}
|
||||
|
@ -155,3 +225,60 @@ void email(string to, string subject, string message, string from, RelayInfo mai
|
|||
msg.textBody = message;
|
||||
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