359 lines
12 KiB
Ruby
Executable File
359 lines
12 KiB
Ruby
Executable File
#
|
|
# File:: email.rb
|
|
# Description:: Email sending functions.
|
|
#
|
|
# Author:: David Muir <david.muir@rockstarnorth.com>
|
|
# Date:: 13 September 2008
|
|
#
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Uses
|
|
#-----------------------------------------------------------------------------
|
|
require 'pipeline/os/path'
|
|
require 'pipeline/util/string'
|
|
require 'base64'
|
|
require 'net/smtp'
|
|
require 'tmail'
|
|
|
|
# Monkey-patching for debugging.
|
|
module TMail
|
|
class Mail
|
|
TO_TREE_INSPECT_DEFAULT = proc { |node| "#<Mime content_type=#{node.content_type.inspect}>" }
|
|
|
|
def to_tree io='', &inspect
|
|
inspect ||= TO_TREE_INSPECT_DEFAULT
|
|
io << "- #{inspect[self]}\n"
|
|
recurse = proc do |node, prefix|
|
|
if node.multipart?
|
|
node.parts.each_with_index do |child, i|
|
|
a, b = i == parts.length - 1 ? ["\\", ' '] : ['|', '| ']
|
|
io << "#{prefix + a}- #{inspect[child]}\n"
|
|
recurse.call child, prefix + b
|
|
end
|
|
end
|
|
end
|
|
recurse.call self, ' '
|
|
io
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Implementation
|
|
#-----------------------------------------------------------------------------
|
|
module Pipeline
|
|
module Util
|
|
|
|
#
|
|
# == Description
|
|
# Object representing an email address, including the address String and
|
|
# an (optional) alias String.
|
|
#
|
|
class EmailAddress
|
|
attr_reader :address
|
|
attr_reader :alias
|
|
|
|
# Class constructor.
|
|
def initialize( address, friendly = nil )
|
|
@address = address
|
|
@alias = friendly
|
|
end
|
|
|
|
# Return String representation of object.
|
|
def to_s( )
|
|
if ( @alias.nil? ) then
|
|
"#{@alias} <#{@address}>"
|
|
else
|
|
"#{@address}"
|
|
end
|
|
end
|
|
|
|
#--------------------------------------------------------------------
|
|
# Class Methods
|
|
#--------------------------------------------------------------------
|
|
|
|
# Constructor from REXML::Element (expects 'alias' and 'address' attributes).
|
|
def EmailAddress::from_xml( xml_node )
|
|
address = xml_node.attributes['address']
|
|
friendly = xml_node.attributes['alias']
|
|
EmailAddress.new( address, friendly )
|
|
end
|
|
|
|
# Constructor from REXML::Element (child email elements as above).
|
|
def EmailAddress::from_xml_list( xml_node )
|
|
emails = []
|
|
xml_node.each_element do |address_node|
|
|
emails << EmailAddress::from_xml( address_node )
|
|
end
|
|
emails
|
|
end
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Email sending utility class. The class can be used in two ways depending
|
|
# on the required use case.
|
|
#
|
|
# If multiple emails are to be sent then you can create an instance of the
|
|
# Email object with the relevant server and port settings. If only a
|
|
# single email is required then the Email::send class method may be simpler
|
|
# to use.
|
|
#
|
|
# See also Pipeline::Util::EmailFactory.
|
|
#
|
|
# == Example Usage
|
|
# See individual function examples and unit test cases.
|
|
#
|
|
class Email
|
|
DEFAULT_SMTP_PORT = 25
|
|
DEFAULT_AUTH_MODE = :plain
|
|
|
|
attr_reader :server
|
|
attr_reader :port
|
|
attr_reader :domain
|
|
# We deliberately do not allow access to the username and password.
|
|
|
|
#
|
|
# Constructor. The +options+ argument is a Hash containing the
|
|
# following keys:
|
|
# :server
|
|
# :port
|
|
# :domain
|
|
# :username
|
|
# :password
|
|
# :auth_mode
|
|
#
|
|
def initialize( options )
|
|
throw ArgumentError.new( "Invalid options, must be Hash object." ) \
|
|
unless ( options.is_a?( Hash ) )
|
|
|
|
@server = options[:server]
|
|
@port = options[:port]
|
|
@domain = options[:domain]
|
|
@username = options[:username]
|
|
@password = options[:password]
|
|
@auth_mode = options[:auth_mode]
|
|
end
|
|
|
|
#
|
|
# Send an email message to a set of recipients. The +from+, +to+ and
|
|
# optional +reply_to+ arguments must be hashes using :address and :alias
|
|
# keys to supply the email address string and aliases for users.
|
|
#
|
|
# To send an HTML email set the +content_type+ argument to 'text/html'.
|
|
#
|
|
def send( from, to, subject, body, content_type = 'text/plain', reply_to = nil )
|
|
|
|
Email::send( @server, @port, @username, @password, @domain, @auth_mode,
|
|
from, to, subject, body, content_type, reply_to )
|
|
end
|
|
|
|
#
|
|
# Send a multipart email message to a set of recipients. See
|
|
# documentation for the +send+ method.
|
|
#
|
|
# The +parts+ argument must be an TMail::Mail structure. See
|
|
# http://rubyforge.org/pipermail/tmail-talk/2008-July/000086.html for
|
|
# an example of constructing such multipart TMail::Mail object
|
|
# hierarchies.
|
|
#
|
|
# MIME type strings can be found at:
|
|
# http://www.webmaster-toolkit.com/mime-types.shtml
|
|
#
|
|
def send_multipart( from, to, subject, parts, reply_to = nil )
|
|
|
|
Email::send_multipart( @server, @port, @username, @password, @domain, @auth_mode,
|
|
from, to, subject, parts, reply_to )
|
|
end
|
|
|
|
#
|
|
# Send an email message to a set of recipients. The +from+, +to+ and
|
|
# optional +reply_to+ arguments must be Hash objects using :address
|
|
# and :alias keys to supply the email address string and aliases for
|
|
# users.
|
|
#
|
|
# To send an HTML email set the +content_type+ argument to 'text/html'.
|
|
#
|
|
# +auth_mode+ should be set to one of :plain, :login or :cram_md5.
|
|
#
|
|
# == Example Usage
|
|
# Email::send( 'mailrelay', 25, 'username', 'password', 'domain.com', :plain,
|
|
# { :address => 'test@rockstarnorth.com', :alias => 'Email Test' },
|
|
# { :address => 'david.muir@rockstarnorth.com', :alias => 'David Muir' },
|
|
# 'Testing Email',
|
|
# "Testing body plain text (#{Time.now})" )
|
|
#
|
|
def Email::send( server, port, username, password, domain, auth_mode,
|
|
from, to, subject, body, content_type = 'text/plain', reply_to = nil )
|
|
throw ArgumentError.new( 'from argument must be a Hash' ) \
|
|
unless ( from.is_a?( Hash ) )
|
|
throw ArgumentError.new( 'to argument must be a Hash or Array' ) \
|
|
unless ( ( to.is_a?( Hash ) ) or ( to.is_a?( Array ) ) )
|
|
throw ArgumentError.new( 'port must be an Integer' ) \
|
|
unless ( port.is_a?( Integer ) )
|
|
|
|
mail = TMail::Mail.new()
|
|
mail.mime_version = '1.0'
|
|
to_addresses = []
|
|
if ( to.is_a?( Hash ) ) then
|
|
to_addresses << to[:address]
|
|
mail.to = "#{to[:alias]} <#{to[:address]}>"
|
|
elsif ( to.is_a?( Array ) ) then
|
|
mailto = ""
|
|
to.each_with_index do |recipient, index|
|
|
throw ArgumentError.new( 'to argument array contains a non-Hash object.' ) \
|
|
unless ( recipient.is_a?( Hash ) )
|
|
to_addresses << recipient[:address]
|
|
if ( index == ( to.size - 1 ) ) then
|
|
mailto += "#{recipient[:alias]} <#{recipient[:address]}>"
|
|
else
|
|
mailto += "#{recipient[:alias]} <#{recipient[:address]}>, "
|
|
end
|
|
end
|
|
mail.to = mailto
|
|
end
|
|
mail.from = "#{from[:alias]} <#{from[:address]}>"
|
|
mail.reply_to = "#{reply_to[:alias]} <#{reply_to[:address]}>" unless ( reply_to.nil? )
|
|
mail.subject = subject
|
|
mail.content_type = content_type
|
|
mail.body = body
|
|
|
|
if ( username.nil? and password.nil? and domain.nil? and auth_mode.nil? ) then
|
|
|
|
Net::SMTP.start( server, port ) do |smtp|
|
|
smtp.send_message( mail.to_s, from[:address], to_addresses )
|
|
end
|
|
else
|
|
|
|
Net::SMTP.start( server, port, domain, username, password, auth_mode ) do |smtp|
|
|
smtp.send_message( mail.to_s, from[:address], to_addresses )
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Send a multipart email message to a set of recipients. The +from+,
|
|
# +to+ and optional +reply_to+ arguments must be Hash objects using
|
|
# :address and :alias keys to suplpy the email address string and
|
|
# aliases for users.
|
|
#
|
|
# The +parts+ argument must be an TMail::Mail structure. See
|
|
# http://rubyforge.org/pipermail/tmail-talk/2008-July/000086.html for
|
|
# an example of constructing such multipart TMail::Mail object
|
|
# hierarchies.
|
|
#
|
|
# MIME type strings can be found at:
|
|
# http://www.webmaster-toolkit.com/mime-types.shtml
|
|
#
|
|
# == Example Usage
|
|
# DHM TODO
|
|
#
|
|
def Email::send_multipart( server, port, username, password, domain, auth_mode,
|
|
from, to, subject, parts, reply_to = nil )
|
|
throw ArgumentError.new( 'from argument must be a Hash' ) \
|
|
unless ( from.is_a?( Hash ) )
|
|
throw ArgumentError.new( 'to argument must be a Hash or Array' ) \
|
|
unless ( ( to.is_a?( Hash ) ) or ( to.is_a?( Array ) ) )
|
|
throw ArgumentError.new( 'port must be an Integer' ) \
|
|
unless ( port.is_a?( Integer ) )
|
|
throw ArgumentError.new( 'parts must be an Array' ) \
|
|
unless ( parts.is_a?( Array ) )
|
|
|
|
mail = TMail::Mail.new( )
|
|
mail.mime_version = '1.0'
|
|
if ( to.is_a?( Hash ) ) then
|
|
mail.to = "#{to[:alias]} <#{to[:address]}>"
|
|
elsif ( to.is_a?( Array ) ) then
|
|
mail.to = ''
|
|
to.each do |recipient|
|
|
throw ArgumentError.new( 'to argument array contains a non-Hash object.' ) \
|
|
unless ( recipient.is_a?( Hash ) )
|
|
mail.to = "#{recipient[:alias]} <#{recipient[:address]}>"
|
|
end
|
|
end
|
|
mail.from = "#{from[:alias]} <#{from[:address]}>"
|
|
mail.reply_to = "#{reply_to[:alias]} <#{reply_to[:address]}>" unless ( reply_to.nil? )
|
|
mail.subject = subject
|
|
mail.set_content_type( 'multipart', 'mixed', { 'boundary' => TMail::new_boundary() } )
|
|
parts.each do |part| mail.parts << part; end
|
|
|
|
puts mail.to_tree
|
|
|
|
Net::SMTP.start( server, port, domain, username, password, auth_mode ) do |smtp|
|
|
smtp.send_message( mail.to_s, from[:address], [ to[:address] ] )
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# The EmailFactory can be used to create TMail::Mail objects for use with
|
|
# the Email::send_multipart function. Factory functions are available for
|
|
# creating some common types of TMail::Mail objects (e.g. plain text and
|
|
# HTML messages, picture attachments etc).
|
|
#
|
|
class EmailFactory
|
|
|
|
#
|
|
# Create a TMail::Mail object given a HTML message body string. The
|
|
# string is expected to be HTML formatted, starting with a <html> tag,
|
|
# ending with a </html> tag.
|
|
#
|
|
def EmailFactory::create_html_mail( body )
|
|
throw ArgumentError.new( 'Invalid body argument, String expected.' ) \
|
|
unless ( body.is_a?( String ) )
|
|
#throw ArgumentError.new( 'body does not start with <html>.' ) \
|
|
# unless ( body.starts_with( '<html>' ) )
|
|
#throw ArgumentError.new( 'body does not end with </html>.' ) \
|
|
# unless ( body.ends_with( '</html>' ) )
|
|
|
|
html = TMail::Mail.new( )
|
|
html.mime_version = '1.0'
|
|
html.content_type = 'text/html'
|
|
html.body = body
|
|
|
|
html
|
|
end
|
|
|
|
#
|
|
# Create a TMail::Mail object given a message body string. The string
|
|
# is expected to be plain text.
|
|
#
|
|
def EmailFactory::create_text_mail( body )
|
|
throw ArgumentError.new( 'Invalid body argument, String expected.' ) \
|
|
unless ( body.is_a?( String ) )
|
|
|
|
text = TMail::Mail.new( )
|
|
text.mime_version = '1.0'
|
|
text.content_type = 'text/plain'
|
|
text.body = body
|
|
|
|
text
|
|
end
|
|
|
|
def EmailFactory::create_attachment_from_file( filename )
|
|
throw IOError.new( 'File does not exist' ) unless ( ::File::exists?( filename ) )
|
|
|
|
# Read file data.
|
|
data = nil
|
|
File.open( filename, 'rb' ) do |fp|
|
|
data = fp.read( )
|
|
end
|
|
|
|
attachment = TMail::Mail.new( )
|
|
attachment.mime_version = '1.0'
|
|
attachment.body = Base64::encode64( data )
|
|
attachment.transfer_encoding = 'Base64'
|
|
attachment.content_type = 'application/octet-stream'
|
|
attachment['Content-Disposition'] = "attachment; filename=#{OS::Path::get_filename(filename)}"
|
|
|
|
attachment
|
|
end
|
|
end
|
|
|
|
end # Util module
|
|
end # Pipeline module
|
|
|
|
# email.rb
|