diff --git a/library/runtime/process/notification_email/notification_email-safe.ecf b/library/runtime/process/notification_email/notification_email-safe.ecf index 01d95e0a..177c5441 100644 --- a/library/runtime/process/notification_email/notification_email-safe.ecf +++ b/library/runtime/process/notification_email/notification_email-safe.ecf @@ -1,5 +1,5 @@ - + @@ -11,8 +11,20 @@ + + + + + - + + + + + + + + diff --git a/library/runtime/process/notification_email/notification_email.e b/library/runtime/process/notification_email/notification_email.e index 4ca33864..5f835b82 100644 --- a/library/runtime/process/notification_email/notification_email.e +++ b/library/runtime/process/notification_email/notification_email.e @@ -2,9 +2,9 @@ note description : "[ Component representing an email ]" - author : "$Author$" - date : "$Date$" - revision : "$Revision$" + author : "$Author: jfiat $" + date : "$Date: 2015-06-30 11:07:17 +0200 (mar., 30 juin 2015) $" + revision : "$Revision: 97586 $" class NOTIFICATION_EMAIL @@ -14,15 +14,17 @@ create feature {NONE} -- Initialization - make (a_from: like from_address; a_to_address: READABLE_STRING_8; a_subject: like subject; a_body: like body) + make (a_from: like from_address; a_to_address: READABLE_STRING_8; a_subject: like subject; a_content: like content) -- Initialize `Current'. + require + well_formed_from_address: is_valid_address (a_from) + well_formed_to_address: a_to_address.has ('@') do initialize from_address := a_from subject := a_subject - body := a_body + content := a_content to_addresses.extend (a_to_address) - end initialize @@ -37,11 +39,36 @@ feature -- Access from_address: READABLE_STRING_8 + reply_to_address: detachable READABLE_STRING_8 + to_addresses: ARRAYED_LIST [READABLE_STRING_8] + cc_addresses: detachable ARRAYED_LIST [READABLE_STRING_8] + + bcc_addresses: detachable ARRAYED_LIST [READABLE_STRING_8] + subject: READABLE_STRING_8 - body: READABLE_STRING_8 + content: READABLE_STRING_8 + + additional_header_lines: detachable ARRAYED_LIST [READABLE_STRING_8] + -- Additional header lines. + + body: like content + obsolete + "Use `content' [June/2015]" + do + Result := body + end + +feature -- Status report + + is_valid: BOOLEAN + -- Is current email ready to be sent? + do + Result := is_valid_address (from_address) and + across to_addresses as ic all is_valid_address (ic.item) end + end feature -- Change @@ -50,13 +77,90 @@ feature -- Change date := d end + set_subject (s: READABLE_STRING_8) + -- Set `subject' to `s'. + do + subject := s + end + + set_content (s: READABLE_STRING_8) + -- Set `content' to `s'. + do + content := s + end + + set_from_address (add: READABLE_STRING_8) + require + well_formed_address: add.has ('@') + do + from_address := add + end + + add_cc_address (add: READABLE_STRING_8) + require + well_formed_address: add.has ('@') + local + lst: like cc_addresses + do + lst := cc_addresses + if lst = Void then + create lst.make (1) + cc_addresses := lst + end + lst.force (add) + end + + add_bcc_address (add: READABLE_STRING_8) + require + well_formed_address: add.has ('@') + local + lst: like bcc_addresses + do + lst := bcc_addresses + if lst = Void then + create lst.make (1) + bcc_addresses := lst + end + lst.force (add) + end + + add_header_line (a_line: READABLE_STRING_8) + require + well_formed_header_line: a_line.has (':') + local + lst: like additional_header_lines + do + lst := additional_header_lines + if lst = Void then + create lst.make (1) + additional_header_lines := lst + end + lst.force (a_line) + end + +feature -- Reset + + reset + do + reset_addresses + additional_header_lines := Void + end + + reset_addresses + -- Reset all addresses. + do + to_addresses.wipe_out + cc_addresses := Void + bcc_addresses := Void + end + feature -- Conversion message: STRING_8 do Result := header Result.append_character ('%N') - Result.append (body) + Result.append (content) Result.append_character ('%N') Result.append_character ('%N') end @@ -66,13 +170,14 @@ feature -- Conversion hdate: HTTP_DATE do create Result.make (20) + if attached reply_to_address as l_reply_to then + Result.append ("Reply-To: ") + Result.append (l_reply_to) + Result.append_character ('%N') + end Result.append ("From: ") Result.append (from_address) Result.append_character ('%N') - Result.append ("Date: ") - create hdate.make_from_date_time (date) - hdate.append_to_rfc1123_string (Result) - Result.append (" GMT%N") Result.append ("To: ") across to_addresses as c @@ -81,18 +186,67 @@ feature -- Conversion Result.append_character (';') end Result.append_character ('%N') + if + attached cc_addresses as l_cc and then + not l_cc.is_empty + then + Result.append ("Cc: ") + across + l_cc as c + loop + Result.append (c.item) + Result.append_character (';') + end + Result.append_character ('%N') + end + if + attached bcc_addresses as l_bcc and then + not l_bcc.is_empty + then + Result.append ("Bcc: ") + across + l_bcc as c + loop + Result.append (c.item) + Result.append_character (';') + end + Result.append_character ('%N') + end Result.append ("Subject: ") Result.append (subject) Result.append_character ('%N') + Result.append ("Date: ") + create hdate.make_from_date_time (date) + hdate.append_to_rfc1123_string (Result) + Result.append_character ('%N') + if attached additional_header_lines as l_lines and then + not l_lines.is_empty + then + across + l_lines as ic + loop + Result.append (ic.item) + Result.append_character ('%N') + end + end ensure Result.ends_with ("%N") end +feature -- Helpers + + is_valid_address (add: READABLE_STRING_8): BOOLEAN + -- Is `add' a valid email address? + do + -- FIXME: improve email validation + Result := add.has ('@') + end + invariant -- invariant_clause: True note - copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/runtime/process/notification_email/notification_email.ecf b/library/runtime/process/notification_email/notification_email.ecf index 80db0f22..353b3b42 100644 --- a/library/runtime/process/notification_email/notification_email.ecf +++ b/library/runtime/process/notification_email/notification_email.ecf @@ -11,8 +11,20 @@ + + + + + - + + + + + + + + diff --git a/library/runtime/process/notification_email/notification_mailer.e b/library/runtime/process/notification_email/notification_mailer.e index 01a16b2e..36be1ba2 100644 --- a/library/runtime/process/notification_email/notification_mailer.e +++ b/library/runtime/process/notification_email/notification_mailer.e @@ -2,9 +2,9 @@ note description: "[ Component responsible to send email ]" - author: "$Author$" - date: "$Date$" - revision: "$Revision$" + author: "$Author: jfiat $" + date: "$Date: 2015-06-30 11:07:17 +0200 (mar., 30 juin 2015) $" + revision: "$Revision: 97586 $" deferred class NOTIFICATION_MAILER @@ -45,8 +45,40 @@ feature -- Basic operation deferred end +feature -- Error + + has_error: BOOLEAN + -- Previous operation reported error? + -- Use `reset_errors', to reset this state. + do + Result := attached last_errors as lst and then not lst.is_empty + end + + reset_errors + -- Reset last errors. + do + last_errors := Void + end + + last_errors: detachable ARRAYED_LIST [READABLE_STRING_32] + -- Last reported errors since previous `reset_errors' call. + + report_error (a_msg: READABLE_STRING_GENERAL) + -- Report error message `a_msg'. + local + lst: like last_errors + do + lst := last_errors + if lst = Void then + create lst.make (1) + last_errors := lst + end + lst.force (a_msg.to_string_32) + end + + note - copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/runtime/process/notification_email/notification_null_mailer.e b/library/runtime/process/notification_email/notification_null_mailer.e new file mode 100644 index 00000000..9707200d --- /dev/null +++ b/library/runtime/process/notification_email/notification_null_mailer.e @@ -0,0 +1,34 @@ +note + description: "Mailer that does nothing." + date: "$Date: 2015-06-30 15:49:56 +0200 (mar., 30 juin 2015) $" + revision: "$Revision: 97588 $" + +class + NOTIFICATION_NULL_MAILER + +inherit + NOTIFICATION_MAILER + +feature -- Status + + is_available: BOOLEAN = True + -- + +feature -- Basic operation + + process_email (a_email: NOTIFICATION_EMAIL) + -- + do + end + +note + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" + source: "[ + Eiffel Software + 5949 Hollister Ave., Goleta, CA 93117 USA + Telephone 805-685-1006, Fax 805-685-6869 + Website http://www.eiffel.com + Customer support http://support.eiffel.com + ]" +end diff --git a/library/runtime/process/notification_email/notification_sendmail_mailer.e b/library/runtime/process/notification_email/notification_sendmail_mailer.e index 2ea09397..e61f06de 100644 --- a/library/runtime/process/notification_email/notification_sendmail_mailer.e +++ b/library/runtime/process/notification_email/notification_sendmail_mailer.e @@ -2,9 +2,9 @@ note description : "[ NOTIFICATION_MAILER using sendmail as mailtool ]" - author: "$Author$" - date: "$Date$" - revision: "$Revision$" + author: "$Author: jfiat $" + date: "$Date: 2015-06-30 15:49:56 +0200 (mar., 30 juin 2015) $" + revision: "$Revision: 97588 $" class NOTIFICATION_SENDMAIL_MAILER @@ -16,23 +16,29 @@ inherit end create - default_create + default_create, + make_with_location feature {NONE} -- Initialization + make_with_location (a_path: READABLE_STRING_GENERAL) + do + make (a_path, <<"-t">>) + set_stdin_mode (True, "%N.%N%N") + end + default_create do Precursor - make ("/usr/sbin/sendmail", <<"-t">>) + make_with_location ("/usr/sbin/sendmail") if not is_available then - make ("/usr/bin/sendmail", <<"-t">>) + make_with_location ("/usr/bin/sendmail") end - set_stdin_mode (True, "%N.%N%N") end note - copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/runtime/process/notification_email/notification_storage_mailer.e b/library/runtime/process/notification_email/notification_storage_mailer.e new file mode 100644 index 00000000..89f28de2 --- /dev/null +++ b/library/runtime/process/notification_email/notification_storage_mailer.e @@ -0,0 +1,54 @@ +note + description: "Summary description for {NOTIFICATION_STORAGE_MAILER}." + author: "" + date: "$Date: 2015-06-30 15:49:56 +0200 (mar., 30 juin 2015) $" + revision: "$Revision: 97588 $" + +class + NOTIFICATION_STORAGE_MAILER + +inherit + NOTIFICATION_MAILER + +create + make + +feature {NONE} -- Initialization + + make (a_storage: NOTIFICATION_EMAIL_STORAGE) + do + storage := a_storage + end + + storage: NOTIFICATION_EMAIL_STORAGE + +feature -- Status report + + is_available: BOOLEAN + -- + do + Result := storage.is_available + end + +feature -- Basic operation + + process_email (a_email: NOTIFICATION_EMAIL) + -- + do + storage.put (a_email) + if storage.has_error then + report_error ("Issue storing email.") + end + end + +note + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" + source: "[ + Eiffel Software + 5949 Hollister Ave., Goleta, CA 93117 USA + Telephone 805-685-1006, Fax 805-685-6869 + Website http://www.eiffel.com + Customer support http://support.eiffel.com + ]" +end diff --git a/library/runtime/process/notification_email/smtp/notification_smtp_mailer.e b/library/runtime/process/notification_email/smtp/notification_smtp_mailer.e new file mode 100644 index 00000000..21f8b99d --- /dev/null +++ b/library/runtime/process/notification_email/smtp/notification_smtp_mailer.e @@ -0,0 +1,181 @@ +note + description: "[ + Notification mailer based on STMP protocol. + + Note: it is based on EiffelNet {SMTP_PROTOCOL} implementation, and may not be complete. + ]" + author: "$Author: jfiat $" + date: "$Date: 2015-06-30 11:07:17 +0200 (mar., 30 juin 2015) $" + revision: "$Revision: 97586 $" + +class + NOTIFICATION_SMTP_MAILER + +inherit + NOTIFICATION_MAILER + +create + make, + make_with_user + +feature {NONE} -- Initialization + + make (a_smtp_server: READABLE_STRING_8) + do + make_with_user (a_smtp_server, Void, Void) + end + + make_with_user (a_smtp_server: READABLE_STRING_8; a_user: detachable READABLE_STRING_8; a_password: detachable READABLE_STRING_8) + -- Initialize `Current'. + local + i: INTEGER + do + i := a_smtp_server.index_of (':', 1) + if i > 0 then + smtp_host := a_smtp_server.substring (1, i - 1) + smtp_port := a_smtp_server.substring (i + 1, a_smtp_server.count).to_integer + else + smtp_host := a_smtp_server + smtp_port := 0 + end + username := a_user + initialize + end + + initialize + -- Initialize service. + local + l_address_factory: INET_ADDRESS_FACTORY + do + if attached username as u then + create smtp_protocol.make (smtp_host, u) + else + -- Get local host name needed in creation of SMTP_PROTOCOL. + create l_address_factory + create smtp_protocol.make (smtp_host, l_address_factory.create_localhost.host_name) + end + if smtp_port > 0 then + smtp_protocol.set_default_port (smtp_port) + end + reset_errors + end + + smtp_protocol: SMTP_PROTOCOL + -- SMTP protocol. + +feature -- Access + + smtp_host: READABLE_STRING_8 + + smtp_port: INTEGER + + username: detachable READABLE_STRING_8 + +feature -- Status + + is_available: BOOLEAN + do + Result := True + end + +feature -- Basic operation + + process_email (a_email: NOTIFICATION_EMAIL) + -- Process the sending of `a_email' + local + l_email: EMAIL + h: STRING + k,v: STRING + i: INTEGER + hdate: HTTP_DATE + do + create l_email.make_with_entry (a_email.from_address, addresses_to_header_line_value (a_email.to_addresses)) + if attached a_email.reply_to_address as l_reply_to then + l_email.add_header_entry ({EMAIL_CONSTANTS}.h_reply_to, l_reply_to) + end + + if attached a_email.cc_addresses as lst then + l_email.add_header_entry ({EMAIL_CONSTANTS}.h_cc, addresses_to_header_line_value (lst)) + end + if attached a_email.bcc_addresses as lst then + l_email.add_header_entry ({EMAIL_CONSTANTS}.h_bcc, addresses_to_header_line_value (lst)) + end + l_email.set_message (a_email.content) + l_email.add_header_entry ({EMAIL_CONSTANTS}.H_subject, a_email.subject) + + create h.make_empty + create hdate.make_from_date_time (a_email.date) + hdate.append_to_rfc1123_string (h) + l_email.add_header_entry ("Date", h) + + if attached a_email.additional_header_lines as lst then + across + lst as ic + loop + h := ic.item + i := h.index_of (':', 1) + if i > 0 then + k := h.head (i - 1) + v := h.substring (i + 1, h.count) + l_email.add_header_entry (k, v) + else + check is_header_line: False end + end + end + end + + smtp_send_email (l_email) + end + +feature {NONE} -- Implementation + + addresses_to_header_line_value (lst: ITERABLE [READABLE_STRING_8]): STRING + local + l_need_separator: BOOLEAN + do + create Result.make (10) + l_need_separator := False + across + lst as ic + loop + if l_need_separator then + Result.append_character (',') + Result.append_character (' ') + else + l_need_separator := True + end + Result.append (ic.item) + end + end + + smtp_send_email (a_email: EMAIL) + -- Send the email represented by `a_email'. + local + retried: BOOLEAN + do + if not retried then + smtp_protocol.initiate_protocol + smtp_protocol.transfer (a_email) + smtp_protocol.close_protocol + if smtp_protocol.error then + report_error ("smtp_protocol reported an error.") + end + end + rescue + report_error ("smtp_protocol raised an exception.") + retried := True + retry + end + +note + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" + source: "[ + Eiffel Software + 5949 Hollister Ave., Goleta, CA 93117 USA + Telephone 805-685-1006, Fax 805-685-6869 + Website http://www.eiffel.com + Customer support http://support.eiffel.com + ]" +end + diff --git a/library/runtime/process/notification_email/storage/notification_email_file_storage.e b/library/runtime/process/notification_email/storage/notification_email_file_storage.e new file mode 100644 index 00000000..0923b622 --- /dev/null +++ b/library/runtime/process/notification_email/storage/notification_email_file_storage.e @@ -0,0 +1,74 @@ +note + description: "Store email in specific file (could also be stderr, ...)." + date: "$Date: 2015-06-30 15:49:56 +0200 (mar., 30 juin 2015) $" + revision: "$Revision: 97588 $" + +class + NOTIFICATION_EMAIL_FILE_STORAGE + +inherit + NOTIFICATION_EMAIL_STORAGE + +create + make + +feature {NONE} -- Initialization + + make (a_output_file: FILE) + require + a_output_file_valid: a_output_file.exists + do + output := a_output_file + end + + output: FILE + +feature -- Status report + + is_available: BOOLEAN + -- Is associated storage available? + do + Result := output.exists and output.is_access_writable + end + + has_error: BOOLEAN + -- Last operation reported an error? + +feature -- Storage + + put (a_email: NOTIFICATION_EMAIL) + -- Store `a_email'. + local + retried: BOOLEAN + l_close_needed: BOOLEAN + do + if not retried then + has_error := False + if not output.is_open_write then + output.open_write + l_close_needed := True + end + output.put_string ("%N----%N" + a_email.message) + if l_close_needed then + output.close + else + output.flush + end + end + rescue + retried := True + has_error := True + retry + end + +;note + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" + source: "[ + Eiffel Software + 5949 Hollister Ave., Goleta, CA 93117 USA + Telephone 805-685-1006, Fax 805-685-6869 + Website http://www.eiffel.com + Customer support http://support.eiffel.com + ]" +end diff --git a/library/runtime/process/notification_email/storage/notification_email_storage.e b/library/runtime/process/notification_email/storage/notification_email_storage.e new file mode 100644 index 00000000..60e02a06 --- /dev/null +++ b/library/runtime/process/notification_email/storage/notification_email_storage.e @@ -0,0 +1,38 @@ +note + description: "Abtract interface of email storage." + date: "$Date: 2015-06-30 15:49:56 +0200 (mar., 30 juin 2015) $" + revision: "$Revision: 97588 $" + +deferred class + NOTIFICATION_EMAIL_STORAGE + +feature -- Status report + + is_available: BOOLEAN + -- Is associated storage available? + deferred + end + + has_error: BOOLEAN + -- Last operation reported an error? + deferred + end + +feature -- Storage + + put (a_email: NOTIFICATION_EMAIL) + -- Store `a_email'. + deferred + end + +note + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" + source: "[ + Eiffel Software + 5949 Hollister Ave., Goleta, CA 93117 USA + Telephone 805-685-1006, Fax 805-685-6869 + Website http://www.eiffel.com + Customer support http://support.eiffel.com + ]" +end