1 module smtp.message;
2 
3 import std.string;
4 import std.uuid;
5 
6 import smtp.attachment;
7 
8 /++
9  Struct that holds name and address of a person holding e-mail box
10   and is capable of sending messages.
11  +/
12 struct Recipient {
13 	string address;
14 	string name;
15 }
16 
17 /++
18   Implements SmtpMessage compositor.
19   Allows to get string representation of message and also send it via SmtpClient.
20 
21   SmtpMessage is used by `SmtpClient.send` high-level method in order to compose
22   and send mail via SMTP.
23  +/
24 struct SmtpMessage {
25 	static string boundary;        // Parts delmiter in multipart message
26 	Recipient sender;              // Specifies name/address of a sender
27 	Recipient[] recipients;        // Specifies names/adresses of recipients
28 	string subject;                // Message subject
29 	string message;                // Message text (body)
30 	string replyTo;                // Messages chains maked (reply-to:)
31 	SmtpAttachment[] attachments;  // Attachments to message
32 
33 	/++
34 	  Initializes boundary for parts in multipart/mixed message type.
35 
36 		Boundary is a random sequence of chars that must divide message
37 		into parts: message, and attachments.
38 	 +/
39 	static this() {
40 		boundary = randomUUID().toString();
41 	}
42 
43 	/++
44 	  Add attachments to the `SmtpMessage`.
45 	 +/
46 	void attach(SmtpAttachment[] a...) {
47 		attachments ~= a;
48 	}
49 
50 	/++
51 	  Builds string representation for a list of copies-to-send over SMTP.
52 	 +/
53 	private string cc() const {
54 		string tCc = "Cc:\"%s\" <%s>\r\n";
55 		string cc = "";
56 		if (recipients.length > 1) {
57 			foreach(recipient; recipients) {
58 				cc ~= format(tCc, recipient.name, recipient.address);
59 			}
60 		} else {
61 			cc = "";
62 		}
63 		return cc;
64 	}
65 
66 	/++
67 	  Builds message representation in case we have multipart/mixed MIME-type
68 		of the message to send.
69 	 +/
70 	private string messageWithAttachments() const {
71 		const string crlf = "\r\n";
72 		return "Content-Type: text/html; charset=utf-8" ~ crlf
73 			~ crlf
74 			~ message ~ crlf
75 			~ crlf ~ "--" ~ SmtpMessage.boundary ~ crlf;
76 	}
77 
78 	/++
79 	 Partly converts attachments to string for SMTP protocol representation
80 	 +/
81 	private string attachmentsToString() const {
82 		string result = "";
83 		foreach(ref a; attachments) {
84 			result ~= a.toString(boundary);
85 		}
86 		return result[0..$ - 2] ~ ".\r\n";
87 	}
88 
89 	/++
90 	  This method converts SmtpMessage struct to string representation.
91 
92 		This string representation is a ready-to-send representation for
93 		SMTP protocol.
94 	 +/
95 	string toString() const {
96 		const string tFrom      = "From: \"%s\" <%s>\r\n";
97 		const string tTo        = "To: \"%s\" <%s>\r\n";
98 		const string tSubject   = "Subject: %s\r\n";
99 		const string mime       = "MIME-Version: 1.0\r\n";
100 		const string tMultipart = format("Content-Type: multipart/mixed; boundary=\"%s\"\r\n", SmtpMessage.boundary);
101 		const string tReplyTo   = "Reply-To:%s\r\n";
102 		const string crlf       = "\r\n";
103 
104 		if (!attachments.length) {
105 			return format(tFrom, sender.name, sender.address)
106    			 ~ format(tTo, recipients[0].name, recipients[0].address)
107 				 ~ cc()
108 				 ~ format(tSubject, subject)
109 				 ~ format(tReplyTo, replyTo)
110 				 ~ crlf
111 				 ~ message ~ "." ~ crlf;
112 		} else {
113 			return format(tFrom, sender.name, sender.address)
114 				 ~ format(tTo, recipients[0].name, recipients[0].address)
115 				 ~ cc()
116 				 ~ format(tSubject, subject)
117 				 ~ mime
118 				 ~ tMultipart
119 				 ~ format(tReplyTo, replyTo) ~ crlf
120 				 ~ "--" ~ boundary ~ crlf
121 				 ~ messageWithAttachments()
122 				 ~ attachmentsToString();
123 		}
124 	}
125 }