1 // Written in the D language 2 /* 3 * Code for creating and sending emails. 4 * 5 * Copyright (C) 2014 Jaypha 6 * 7 * Distributed under the Boost Software License, Version 1.0. 8 * (See http://www.boost.org/LICENSE_1_0.txt) 9 * 10 * Authors: Jason den Dulk 11 */ 12 13 module jaypha.inet.email; 14 15 import jaypha.types; 16 17 public import jaypha.inet.imf.writing; 18 import jaypha.inet.mime.writing; 19 import jaypha.inet.mime.content_disposition; 20 21 import std.file; 22 import std.process; 23 import std.array; 24 import std.stdio; 25 import std.range; 26 27 //---------------------------------------------------------------------------- 28 29 struct Email 30 { 31 struct Attachment 32 { 33 string name; 34 string mimeType; 35 ByteArray content; 36 string fileName; 37 } 38 39 private MimeHeader[] headers; 40 void addHeader(string name, string fieldBody) 41 { headers ~= unstructuredHeader(name,fieldBody); } 42 43 string subject; 44 45 private Mailbox _from; 46 private Mailbox[] _to, _cc, _bcc; 47 @property 48 { 49 Mailbox from() { return _from; } 50 void from(Mailbox v) { _from = v; } 51 void from(string v) { _from = Mailbox(v); } 52 53 Mailbox[] to() { return _to; } 54 void to(Mailbox[] v) { _to = v; } 55 void to(string[] v) { _to = []; foreach(x;v) _to ~= Mailbox(x); } 56 void to(Mailbox v) { _to = [ v ]; } 57 void to(string v) { _to = [ Mailbox(v) ]; } 58 59 Mailbox[] cc() { return _cc; } 60 void cc(Mailbox[] v) { _cc = v; } 61 void cc(string[] v) { _cc = []; foreach(x;v) _cc ~= Mailbox(x); } 62 void cc(Mailbox v) { _cc = [ v ]; } 63 void cc(string v) { _cc = [ Mailbox(v) ]; } 64 65 Mailbox[] bcc() { return _bcc; } 66 void bcc(Mailbox[] v) { _bcc = v; } 67 void bcc(string[] v) { _bcc = []; foreach(x;v) _bcc ~= Mailbox(x); } 68 void bcc(Mailbox v) { _bcc = [ v ]; } 69 void bcc(string v) { _bcc = [ Mailbox(v) ]; } 70 } 71 72 string text, html; 73 74 Attachment[] attachments; 75 76 //--------------------------------------------------------- 77 78 void copy(R) (R range) if (isOutputRange!(R,ByteArray)) 79 { 80 auto entity = build(); 81 entity.copy(range); 82 } 83 84 //--------------------------------------------------------- 85 86 version(linux) 87 { 88 void sendmail() 89 { 90 auto entity = build(); 91 92 auto pipes = pipeProcess(["sendmail","-t","-i"]); 93 auto wout = wOut(pipes.stdin); 94 entity.copy(wout); 95 //entity.copy(pipes.stdin); 96 pipes.stdin.close(); 97 wait(pipes.pid); 98 } 99 } 100 101 102 //--------------------------------------------------------- 103 // throws CurlException on failure. 104 105 void send(string host = "smtp://localhost", string authAccount = null, string authPass = null) 106 { 107 import std.net.curl; 108 auto smtp = SMTP(host); 109 if (authAccount) 110 smtp.setAuthentication(authAccount, authPass); 111 112 auto entity = build(); 113 114 string[] r; 115 foreach (b; to) 116 r ~= b.address; 117 foreach (b;cc) 118 r ~= b.address; 119 foreach (b;bcc) 120 r ~= b.address; 121 smtp.mailTo(cast(const(char)[][])r); 122 smtp.mailFrom = from.address; 123 smtp.message = entity.asString; 124 smtp.perform(); 125 } 126 127 //--------------------------------------------------------- 128 // Constructs the IMF document for the email. The mime 129 // entities themselves handle the serialisation. 130 131 private MimeEntity build() 132 { 133 MimeEntity entity; 134 135 if (!attachments.empty) 136 { 137 entity = getMultiPart("mixed"); 138 entity.content.bodParts ~= getMessagePart(); 139 foreach (a;attachments) 140 entity.content.bodParts ~= getAttachmentPart(a); 141 } 142 else 143 entity = getMessagePart(); 144 145 entity.headers ~= mimeVersion; 146 147 entity.headers ~= unstructuredHeader("Subject",subject); 148 entity.headers ~= addressHeader("From", [ from ] ); 149 if (!to.empty) entity.headers ~= addressHeader("To", to); 150 if (!cc.empty) entity.headers ~= addressHeader("Cc", cc); 151 if (!bcc.empty) entity.headers ~= addressHeader("Bcc", bcc); 152 153 entity.headers ~= headers; 154 155 return entity; 156 } 157 158 //--------------------------------------------------------- 159 // Creates an entity for the message, and fills it with the 160 // email message contents. 161 162 private MimeEntity getMessagePart() 163 { 164 if (!html.empty) 165 { 166 if (!text.empty) 167 { 168 auto entity = getMultiPart("alternate"); 169 entity.content.bodParts ~= getHtmlPart(); 170 entity.content.bodParts ~= getTextPart(); 171 return entity; 172 } 173 else 174 return getHtmlPart(); 175 } 176 else 177 return getTextPart(); 178 } 179 180 //--------------------------------------------------------- 181 // Creates a mime entity from the text component. 182 183 private MimeEntity getTextPart() 184 { 185 MimeContentType ct; 186 ct.type = "text/plain"; 187 ct.parameters["charset"] = "UTF-8"; 188 189 auto entity = MimeEntity(ct, true); 190 entity.encoding = "8bit"; 191 192 entity.content.bod = cast(ByteArray) text; 193 194 return entity; 195 } 196 197 //--------------------------------------------------------- 198 // Creates a multipart mime entity of the given subtype. 199 200 private MimeEntity getMultiPart(string subType) 201 { 202 MimeContentType ct; 203 ct.type = "multipart/"~subType; 204 auto entity = MimeEntity(ct, true); 205 return entity; 206 } 207 208 //--------------------------------------------------------- 209 // Creates a mime entity from the HTML component. 210 211 private MimeEntity getHtmlPart() 212 { 213 MimeContentType ct; 214 ct.type = "text/html"; 215 ct.parameters["charset"] = "UTF-8"; 216 217 auto entity = MimeEntity(ct, true); 218 entity.encoding = "8bit"; 219 220 entity.content.bod = cast(ByteArray) html; 221 222 return entity; 223 } 224 225 //--------------------------------------------------------- 226 // Creates a mime entity for an attachment. 227 228 private MimeEntity getAttachmentPart(ref Attachment a) 229 { 230 MimeContentType ct; 231 ct.type = a.mimeType; 232 MimeContentDisposition disp; 233 if (ct.type[0..5] != "image") 234 disp.type = "attachment"; 235 disp.parameters["filename"] = a.name; 236 237 auto entity = MimeEntity(ct, true); 238 entity.headers ~= disp.toMimeHeader(true); 239 entity.encoding = "base64"; 240 if (!a.content.empty) 241 entity.content.bod = a.content; 242 else 243 entity.content.bod = cast(ByteArray) read(a.fileName); 244 245 return entity; 246 } 247 } 248 249 // Quick and dirty wrapper to make files act as ranges. 250 251 struct wOut 252 { 253 File file; 254 255 void put(ByteArray s) { file.rawWrite(s); } 256 } 257