+# CURL Mail Request
+#
+# ~~~
+# # Craft mail
+# var mail = new CurlMail("sender@example.org",
+# to=["to@example.org"], cc=["bob@example.org"])
+#
+# mail.headers_body["Content-Type:"] = """text/html; charset="UTF-8""""
+# mail.headers_body["Content-Transfer-Encoding:"] = "quoted-printable"
+#
+# mail.body = "<h1>Here you can write HTML stuff.</h1>"
+# mail.subject = "Hello From My Nit Program"
+#
+# # Set mail server
+# var error = mail.set_outgoing_server("smtps://smtp.example.org:465",
+# "user@example.org", "mypassword")
+# if error != null then
+# print "Mail Server Error: {error}"
+# exit 0
+# end
+#
+# # Send
+# error = mail.execute
+# if error != null then
+# print "Transfer Error: {error}"
+# exit 0
+# end
+# ~~~
+class CurlMail
+ super CurlRequest
+ super NativeCurlCallbacks
+
+ # Address of the sender
+ var from: nullable String is writable
+
+ # Main recipients
+ var to: nullable Array[String] is writable
+
+ # Subject line
+ var subject: nullable String is writable
+
+ # Text content
+ var body: nullable String is writable
+
+ # CC recipients
+ var cc: nullable Array[String] is writable
+
+ # BCC recipients (hidden from other recipients)
+ var bcc: nullable Array[String] is writable
+
+ # HTTP header
+ var headers = new HeaderMap is lazy, writable
+
+ # Content header
+ var headers_body = new HeaderMap is lazy, writable
+
+ # Protocols supported to send mail to a server
+ #
+ # Default value at `["smtp", "smtps"]`
+ var supported_outgoing_protocol = ["smtp", "smtps"]
+
+ # Helper method to add pair values to mail content while building it (ex: "To:", "address@mail.com")
+ private fun add_pair_to_content(str: String, att: String, val: nullable String): String
+ do
+ if val != null then return "{str}{att}{val}\n"
+ return "{str}{att}\n"
+ end
+
+ # Helper method to add entire list of pairs to mail content
+ private fun add_pairs_to_content(content: String, pairs: HeaderMap): String
+ do
+ for h_key, h_val in pairs do content = add_pair_to_content(content, h_key, h_val)
+ return content
+ end
+
+ # Check for host and protocol availability
+ private fun is_supported_outgoing_protocol(host: String): CURLCode
+ do
+ var host_reach = host.split_with("://")
+ if host_reach.length > 1 and supported_outgoing_protocol.has(host_reach[0]) then return once new CURLCode.ok
+ return once new CURLCode.unsupported_protocol
+ end
+
+ # Configure server host and user credentials if needed.
+ fun set_outgoing_server(host: String, user: nullable String, pwd: nullable String): nullable CurlResponseFailed
+ do
+ # Check Curl initialisation
+ if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
+
+ var err
+
+ # Host & Protocol
+ err = is_supported_outgoing_protocol(host)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ err = self.curl.native.easy_setopt(new CURLOption.url, host)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ # Credentials
+ if not user == null and not pwd == null then
+ err = self.curl.native.easy_setopt(new CURLOption.username, user)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ err = self.curl.native.easy_setopt(new CURLOption.password, pwd)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+ end
+
+ return null
+ end
+
+ # Execute Mail request with settings configured through attribute
+ fun execute: nullable CurlResponseFailed
+ do
+ if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
+
+ var lines = new Array[String]
+
+ # Headers
+ var headers = self.headers
+ if not headers.is_empty then
+ for k, v in headers do lines.add "{k}{v}"
+ end
+
+ # Recipients
+ var all_recipients = new Array[String]
+ var to = self.to
+ if to != null and to.length > 0 then
+ lines.add "To:{to.join(",")}"
+ all_recipients.append to
+ end
+
+ var cc = self.cc
+ if cc != null and cc.length > 0 then
+ lines.add "Cc:{cc.join(",")}"
+ all_recipients.append cc
+ end
+
+ var bcc = self.bcc
+ if bcc != null and bcc.length > 0 then all_recipients.append bcc
+
+ if all_recipients.is_empty then return answer_failure(0, "There must be at lease one recipient")
+
+ var err = self.curl.native.easy_setopt(new CURLOption.follow_location, 1)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ err = self.curl.native.easy_setopt(new CURLOption.mail_rcpt, all_recipients.to_curlslist)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ # From
+ var from = self.from
+ if not from == null then
+ lines.add "From:{from}"
+
+ err = self.curl.native.easy_setopt(new CURLOption.mail_from, from)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+ end
+
+ # Subject
+ var subject = self.subject
+ if subject == null then subject = "" # Default
+ lines.add "Subject: {subject}"
+
+ # Headers body
+ var headers_body = self.headers_body
+ if not headers_body.is_empty then
+ for k, v in headers_body do lines.add "{k}{v}"
+ end
+
+ # Body
+ var body = self.body
+ if body == null then body = "" # Default
+
+ lines.add ""
+ lines.add body
+ lines.add ""
+
+ err = self.curl.native.register_callback_read(self)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ var content = lines.join("\n")
+ err = self.curl.native.register_read_datas_callback(self, content)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ var err_resp = perform
+ if err_resp != null then return err_resp
+
+ return null
+ end
+end
+