# encoding: utf-8 module Mail # The Message class provides a single point of access to all things to do with an # email message. # # You create a new email message by calling the Mail::Message.new method, or just # Mail.new # # A Message object by default has the following objects inside it: # # * A Header object which contians all information and settings of the header of the email # * Body object which contains all parts of the email that are not part of the header, this # includes any attachments, body text, mime parts etc. # # ==Per RFC2822 # # 2.1. General Description # # At the most basic level, a message is a series of characters. A # message that is conformant with this standard is comprised of # characters with values in the range 1 through 127 and interpreted as # US-ASCII characters [ASCII]. For brevity, this document sometimes # refers to this range of characters as simply "US-ASCII characters". # # Note: This standard specifies that messages are made up of characters # in the US-ASCII range of 1 through 127. There are other documents, # specifically the MIME document series [RFC2045, RFC2046, RFC2047, # RFC2048, RFC2049], that extend this standard to allow for values # outside of that range. Discussion of those mechanisms is not within # the scope of this standard. # # Messages are divided into lines of characters. A line is a series of # characters that is delimited with the two characters carriage-return # and line-feed; that is, the carriage return (CR) character (ASCII # value 13) followed immediately by the line feed (LF) character (ASCII # value 10). (The carriage-return/line-feed pair is usually written in # this document as "CRLF".) # # A message consists of header fields (collectively called "the header # of the message") followed, optionally, by a body. The header is a # sequence of lines of characters with special syntax as defined in # this standard. The body is simply a sequence of characters that # follows the header and is separated from the header by an empty line # (i.e., a line with nothing preceding the CRLF). class Message include Patterns include Utilities # Creates a new Mail::Message object through .new def initialize(*args, &block) @body = nil if args.flatten.first.respond_to?(:each_pair) init_with_hash(args.flatten.first) else init_with_string(args.flatten[0].to_s.strip) end if block_given? instance_eval(&block) end self end def deliver! Deliverable.perform_delivery!(self) end def <=>(other) if other.nil? 1 else self.date <=> other.date end end def ==(other) unless other.respond_to?(:encoded) false else self.encoded == other.encoded end end # Provides access to the raw source of the message as it was when it # was instantiated. This is set at initialization and so is untouched # by the parsers or decoder / encoders # # Example: # # mail = Mail.new('This is an invalid email message') # mail.raw_source #=> "This is an invalid email message" def raw_source @raw_source end def raw_source=(value) @raw_source = value.to_crlf end def set_envelope( val ) @raw_envelope = val @envelope = Mail::Envelope.new( val ) end # The raw_envelope is the From mikel@test.lindsaar.net Mon May 2 16:07:05 2009 # type field that you can see at the top of any email that has come # from a mailbox def raw_envelope @raw_envelope end def envelope_from @envelope ? @envelope.from : nil end def envelope_date @envelope ? @envelope.date : nil end # Sets the header of the message object. # # Example: # # mail.header = 'To: mikel@test.lindsaar.net\r\nFrom: Bob@bob.com' # mail.header #=> <#Mail::Header def header=(value) @header = Mail::Header.new(value) end # Returns the header object of the message object. Or, if passed # a parameter sets the value. # # Example: # # mail = Mail::Message.new('To: mikel\r\nFrom: you') # mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr... # # mail.header #=> nil # mail.header 'To: mikel\r\nFrom: you' # mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr... def header(value = nil) value ? self.header = value : @header end # Provides a way to set custom headers, by passing in a hash def headers(hash = {}) hash.each_pair do |k,v| header[k] = v end end # Returns the Bcc value of the mail object, either a single string of an address # spec or an array of strings of address specs if there is more than one address # in the Bcc. # # Example: # # mail.bcc = 'Mikel <mikel@test.lindsaar.net>' # mail.bcc #=> 'mikel@test.lindsaar.net' # mail.bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.bcc 'Mikel <mikel@test.lindsaar.net>' # mail.bcc #=> 'mikel@test.lindsaar.net' def bcc( val = nil ) default :bcc, val end # Sets the Bcc value of the mail object, pass in a string of the field # # Example: # # mail.bcc = 'Mikel <mikel@test.lindsaar.net>' # mail.bcc #=> 'mikel@test.lindsaar.net' # mail.bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] def bcc=( val ) header[:bcc] = val end # Returns the Cc value of the mail object, either a single string of an address # spec or an array of strings of address specs if there is more than one address # in the Cc. # # Example: # # mail.cc = 'Mikel <mikel@test.lindsaar.net>' # mail.cc #=> 'mikel@test.lindsaar.net' # mail.cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.cc 'Mikel <mikel@test.lindsaar.net>' # mail.cc #=> 'mikel@test.lindsaar.net' def cc( val = nil ) default :cc, val end # Sets the Cc value of the mail object, pass in a string of the field # # Example: # # mail.cc = 'Mikel <mikel@test.lindsaar.net>' # mail.cc #=> 'mikel@test.lindsaar.net' # mail.cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] def cc=( val ) header[:cc] = val end def comments( val = nil ) default :comments, val end def comments=( val ) header[:comments] = val end def content_description( val = nil ) default :content_description, val end def content_description=( val ) header[:content_description] = val end def content_disposition( val = nil ) default :content_disposition, val end def content_disposition=( val ) header[:content_disposition] = val end def content_id( val = nil ) default :content_id, val end def content_id=( val ) header[:content_id] = val end def content_location( val = nil ) default :content_location, val end def content_location=( val ) header[:content_location] = val end def content_transfer_encoding( val = nil ) default :content_transfer_encoding, val end def content_transfer_encoding=( val ) header[:content_transfer_encoding] = val end def content_type( val = nil ) default :content_type, val end def content_type=( val ) header[:content_type] = val end def date( val = nil ) default :date, val end def date=( val ) header[:date] = val end # Returns the From value of the mail object, either a single string of an address # spec or an array of strings of address specs if there is more than one address # in the From. # # Example: # # mail.from = 'Mikel <mikel@test.lindsaar.net>' # mail.from #=> 'mikel@test.lindsaar.net' # mail.from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.from 'Mikel <mikel@test.lindsaar.net>' # mail.from #=> 'mikel@test.lindsaar.net' def from( val = nil ) default :from, val end # Sets the From value of the mail object, pass in a string of the field # # Example: # # mail.from = 'Mikel <mikel@test.lindsaar.net>' # mail.from #=> 'mikel@test.lindsaar.net' # mail.from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] def from=( val ) header[:from] = val end def in_reply_to( val = nil ) default :in_reply_to, val end def in_reply_to=( val ) header[:in_reply_to] = val end def keywords( val = nil ) default :keywords, val end def keywords=( val ) header[:keywords] = val end # Returns the Message-ID of the mail object. Note, per RFC 2822 the Message ID # consists of what is INSIDE the < > usually seen in the mail header, so this method # will return only what is inside. # # Example: # # mail.message_id = '<1234@message.id>' # mail.message_id #=> '1234@message.id' # # Also allows you to set the Message-ID by passing a string as a parameter # # mail.message_id '<1234@message.id>' # mail.message_id #=> '1234@message.id' def message_id( val = nil ) default :message_id, val end # Sets the Message-ID. Note, per RFC 2822 the Message ID consists of what is INSIDE # the < > usually seen in the mail header, so this method will return only what is inside. # # mail.message_id = '<1234@message.id>' # mail.message_id #=> '1234@message.id' def message_id=( val ) header[:message_id] = val end # Returns the mime version of the email as a string # # Example: # # mail.mime_version = '1.0' # mail.mime_version #=> '1.0' # # Also allows you to set the mime version by passing a string as a parameter. # # Example: # # mail.mime_version '1.0' # mail.mime_version #=> '1.0' def mime_version( val = nil ) default :mime_version, val end # Sets the mime version of the email by accepting a string # # Example: # # mail.mime_version = '1.0' # mail.mime_version #=> '1.0' def mime_version=( val ) header[:mime_version] = val end def received( val = nil ) if val header[:received] = val else header[:received] end end def received=( val ) header[:received] = val end def references( val = nil ) default :references, val end def references=( val ) header[:references] = val end # Returns the Reply-To value of the mail object, either a single string of an address # spec or an array of strings of address specs if there is more than one address # in the Reply-To. # # Example: # # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>' # mail.reply_to #=> 'mikel@test.lindsaar.net' # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.reply_to 'Mikel <mikel@test.lindsaar.net>' # mail.reply_to #=> 'mikel@test.lindsaar.net' def reply_to( val = nil ) default :reply_to, val end # Sets the Reply-To value of the mail object, pass in a string of the field # # Example: # # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>' # mail.reply_to #=> 'mikel@test.lindsaar.net' # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] def reply_to=( val ) header[:reply_to] = val end # Returns the Resent-Bcc value of the mail object, either a single string of an address # spec or an array of strings of address specs if there is more than one address # in the Resent-Bcc. # # Example: # # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>' # mail.resent_bcc #=> 'mikel@test.lindsaar.net' # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.resent_bcc 'Mikel <mikel@test.lindsaar.net>' # mail.resent_bcc #=> 'mikel@test.lindsaar.net' def resent_bcc( val = nil ) default :resent_bcc, val end # Sets the Resent-Bcc value of the mail object, pass in a string of the field # # Example: # # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>' # mail.resent_bcc #=> 'mikel@test.lindsaar.net' # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] def resent_bcc=( val ) header[:resent_bcc] = val end # Returns the Resent-Cc value of the mail object, either a single string of an address # spec or an array of strings of address specs if there is more than one address # in the Resent-Cc. # # Example: # # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>' # mail.resent_cc #=> 'mikel@test.lindsaar.net' # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.resent_cc 'Mikel <mikel@test.lindsaar.net>' # mail.resent_cc #=> 'mikel@test.lindsaar.net' def resent_cc( val = nil ) default :resent_cc, val end # Sets the Resent-Cc value of the mail object, pass in a string of the field # # Example: # # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>' # mail.resent_cc #=> 'mikel@test.lindsaar.net' # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] def resent_cc=( val ) header[:resent_cc] = val end def resent_date( val = nil ) default :resent_date, val end def resent_date=( val ) header[:resent_date] = val end # Returns the Resent-From value of the mail object, either a single string of an address # spec or an array of strings of address specs if there is more than one address # in the Resent-From. # # Example: # # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>' # mail.resent_from #=> 'mikel@test.lindsaar.net' # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.resent_from 'Mikel <mikel@test.lindsaar.net>' # mail.resent_from #=> 'mikel@test.lindsaar.net' def resent_from( val = nil ) default :resent_from, val end # Sets the Resent-From value of the mail object, pass in a string of the field # # Example: # # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>' # mail.resent_from #=> 'mikel@test.lindsaar.net' # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] def resent_from=( val ) header[:resent_from] = val end def resent_message_id( val = nil ) default :resent_message_id, val end def resent_message_id=( val ) header[:resent_message_id] = val end # Returns the Resent-Sender value of the mail object, as a single string of an address # spec. A sender per RFC 2822 must be a single address # # Example: # # mail.resent_sender = 'Mikel <mikel@test.lindsaar.net>' # mail.resent_sender #=> 'mikel@test.lindsaar.net' # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.resent_sender 'Mikel <mikel@test.lindsaar.net>' # mail.resent_sender #=> 'mikel@test.lindsaar.net' def resent_sender( val = nil ) default :resent_sender, val end # Sets the Resent-Sender value of the mail object, pass in a string of the field # # Example: # # mail.sender = 'Mikel <mikel@test.lindsaar.net>' # mail.sender #=> 'mikel@test.lindsaar.net' def resent_sender=( val ) header[:resent_sender] = val end # Returns the Resent-To value of the mail object, either a single string of an address # spec or an array of strings of address specs if there is more than one address # in the Resent-To. # # Example: # # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>' # mail.resent_to #=> 'mikel@test.lindsaar.net' # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.resent_to 'Mikel <mikel@test.lindsaar.net>' # mail.resent_to #=> 'mikel@test.lindsaar.net' def resent_to( val = nil ) default :resent_to, val end # Sets the Resent-To value of the mail object, pass in a string of the field # # Example: # # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>' # mail.resent_to #=> 'mikel@test.lindsaar.net' # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] def resent_to=( val ) header[:resent_to] = val end # Returns the return path of the mail object, or sets it if you pass a string def return_path( val = nil ) default :return_path, val end # Sets the return path of the object def return_path=( val ) header[:return_path] = val end # Returns the Sender value of the mail object, as a single string of an address # spec. A sender per RFC 2822 must be a single address # # Example: # # mail.sender = 'Mikel <mikel@test.lindsaar.net>' # mail.sender #=> 'mikel@test.lindsaar.net' # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.sender 'Mikel <mikel@test.lindsaar.net>' # mail.sender #=> 'mikel@test.lindsaar.net' def sender( val = nil ) default :sender, val end # Sets the Sender value of the mail object, pass in a string of the field # # Example: # # mail.sender = 'Mikel <mikel@test.lindsaar.net>' # mail.sender #=> 'mikel@test.lindsaar.net' def sender=( val ) header[:sender] = val end # Returns the decoded value of the subject field, as a single string. # # Example: # # mail.subject = "G'Day mate" # mail.subject #=> "G'Day mate" # mail.subject = '=?UTF-8?Q?This_is_=E3=81=82_string?=' # mail.subject #=> "This is あ string" # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.subject "G'Day mate" # mail.subject #=> "G'Day mate" def subject( val = nil ) default :subject, val end # Sets the Subject value of the mail object, pass in a string of the field # # Example: # # mail.subject = '=?UTF-8?Q?This_is_=E3=81=82_string?=' # mail.subject #=> "This is あ string" def subject=( val ) header[:subject] = val end # Returns the To value of the mail object, either a single string of an address # spec or an array of strings of address specs if there is more than one address # in the To. # # Example: # # mail.to = 'Mikel <mikel@test.lindsaar.net>' # mail.to #=> 'mikel@test.lindsaar.net' # mail.to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] # # Also allows you to set the value by passing a value as a parameter # # Example: # # mail.to 'Mikel <mikel@test.lindsaar.net>' # mail.to #=> 'mikel@test.lindsaar.net' def to( val = nil ) default :to, val end # Sets the To value of the mail object, pass in a string of the field # # Example: # # mail.to = 'Mikel <mikel@test.lindsaar.net>' # mail.to #=> 'mikel@test.lindsaar.net' # mail.to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net' # mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net'] def to=( val ) header[:to] = val end # Returns the default value of the field requested as a symbol. # # Each header field has a :default method which returns the most common use case for # that field, for example, the date field types will return a DateTime object when # sent :default, the subject, or unstructured fields will return a decoded string of # their value, the address field types will return a single addr_spec or an array of # addr_specs if there is more than one. def default( sym, val = nil ) if val header[sym] = val else header[sym].default if header[sym] end end # Sets the body object of the message object. # # Example: # # mail.body = 'This is the body' # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo... # # You can also reset the body of an Message object by setting body to nil # # Example: # # mail.body = 'this is the body' # mail.body.encoded #=> 'this is the body' # mail.body = nil # mail.body.encoded #=> '' # # If you try and set the body of an email that is a multipart email, then instead # of deleting all the parts of your email, mail will add a text/plain part to # your email: # # mail.add_file 'somefilename.png' # mail.parts.length #=> 1 # mail.body = "This is a body" # mail.parts.length #=> 2 # mail.parts.last.content_type.content_type #=> 'This is a body' def body=(value) case when value == nil @body = Mail::Body.new('') when @body && !@body.parts.empty? @body << Mail::Part.new(value) else @body = Mail::Body.new(value) end add_encoding_to_body end # Returns the body of the message object. Or, if passed # a parameter sets the value. # # Example: # # mail = Mail::Message.new('To: mikel\r\n\r\nThis is the body') # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo... # # mail.body 'This is another body' # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is anothe... def body(value = nil) if value self.body = value add_encoding_to_body else @body end end # Returns the list of addresses this message should be sent to by # collecting the addresses off the to, cc and bcc fields. # # Example: # # mail.to = 'mikel@test.lindsaar.net' # mail.cc = 'sam@test.lindsaar.net' # mail.bcc = 'bob@test.lindsaar.net' # mail.destinations.length #=> 3 # mail.destinations.first #=> 'mikel@test.lindsaar.net' def destinations [to_addrs, cc_addrs, bcc_addrs].compact.flatten end # Returns an array of addresses (the encoded value) in the From field, # if no From field, returns an empty array def from_addrs from ? [from].flatten : [] end # Returns an array of addresses (the encoded value) in the To field, # if no To field, returns an empty array def to_addrs to ? [to].flatten : [] end # Returns an array of addresses (the encoded value) in the Cc field, # if no Cc field, returns an empty array def cc_addrs cc ? [cc].flatten : [] end # Returns an array of addresses (the encoded value) in the Bcc field, # if no Bcc field, returns an empty array def bcc_addrs bcc ? [bcc].flatten : [] end # Allows you to add an arbitrary header # # Example: # # mail['foo'] = '1234' # mail['foo'].to_s #=> '1234' def []=(name, value) if name.to_s == 'body' self.body = value elsif name.to_s =~ /content[-_]type/i header[underscoreize(name)] = value else header[underscoreize(name)] = value end end # Allows you to read an arbitrary header # # Example: # # mail['foo'] = '1234' # mail['foo'].to_s #=> '1234' def [](name) header[underscoreize(name)] end # Method Missing in this implementation allows you to set any of the # standard fields directly as you would the "to", "subject" etc. # # Those fields used most often (to, subject et al) are given their # own method for ease of documentation and also to avoid the hook # call to method missing. # # This will only catch the known fields listed in: # # Mail::Field::KNOWN_FIELDS # # as per RFC 2822, any ruby string or method name could pretty much # be a field name, so we don't want to just catch ANYTHING sent to # a message object and interpret it as a header. # # This method provides all three types of header call to set, read # and explicitly set with the = operator # # Examples: # # mail.comments = 'These are some comments' # mail.comments #=> 'These are some comments' # # mail.comments 'These are other comments' # mail.comments #=> 'These are other comments' # # # mail.date = 'Tue, 1 Jul 2003 10:52:37 +0200' # mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200' # # mail.date 'Tue, 1 Jul 2003 10:52:37 +0200' # mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200' # # # mail.resent_msg_id = '<1234@resent_msg_id.lindsaar.net>' # mail.resent_msg_id #=> '<1234@resent_msg_id.lindsaar.net>' # # mail.resent_msg_id '<4567@resent_msg_id.lindsaar.net>' # mail.resent_msg_id #=> '<4567@resent_msg_id.lindsaar.net>' def method_missing(name, *args, &block) #:nodoc: # Only take the structured fields, as we could take _anything_ really # as it could become an optional field... "but therin lies the dark side" field_name = underscoreize(name).chomp("=") if Mail::Field::KNOWN_FIELDS.include?(field_name) if args.empty? header[field_name] else header[field_name] = args.first end else super # otherwise pass it on end #:startdoc: end # Returns an FieldList of all the fields in the header in the order that # they appear in the header def header_fields header.fields end # Returns true if the message has a message ID field, the field may or may # not have a value, but the field exists or not. def has_message_id? header.has_message_id? end # Returns true if the message has a Date field, the field may or may # not have a value, but the field exists or not. def has_date? header.has_date? end # Returns true if the message has a Date field, the field may or may # not have a value, but the field exists or not. def has_mime_version? header.has_mime_version? end def has_content_type? !!content_type end def has_charset? !!charset end def has_content_transfer_encoding? !!content_transfer_encoding end def has_transfer_encoding? # :nodoc: STDERR.puts(":has_transfer_encoding? is deprecated in Mail 1.4.3. Please use has_content_transfer_encoding?\n#{caller}") has_content_transfer_encoding? end # Creates a new empty Message-ID field and inserts it in the correct order # into the Header. The MessageIdField object will automatically generate # a unique message ID if you try and encode it or output it to_s without # specifying a message id. # # It will preserve the message ID you specify if you do. def add_message_id(msg_id_val = '') header['message-id'] = msg_id_val end # Creates a new empty Date field and inserts it in the correct order # into the Header. The DateField object will automatically generate # DateTime.now's date if you try and encode it or output it to_s without # specifying a date yourself. # # It will preserve any date you specify if you do. def add_date(date_val = '') header['date'] = date_val end # Creates a new empty Mime Version field and inserts it in the correct order # into the Header. The MimeVersion object will automatically generate # DateTime.now's date if you try and encode it or output it to_s without # specifying a date yourself. # # It will preserve any date you specify if you do. def add_mime_version(ver_val = '') header['mime-version'] = ver_val end # Adds a content type and charset if the body is US-ASCII # # Otherwise raises a warning def add_content_type header[:content_type] = 'text/plain' end # Adds a content type and charset if the body is US-ASCII # # Otherwise raises a warning def add_charset if body.only_us_ascii? header[:content_type].parameters['charset'] = 'US-ASCII' else warning = "Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.\nCalled from:\n#{caller.join("\n")}" STDERR.puts(warning) header[:content_type].parameters['charset'] = 'UTF-8' end end # Adds a content transfer encoding # # Otherwise raises a warning def add_content_transfer_encoding if body.only_us_ascii? header[:content_transfer_encoding] = '7bit' else warning = "Non US-ASCII detected and no content-transfer-encoding defined.\nDefaulting to 8bit, set your own if this is incorrect.\nCalled from:\n#{caller.join("\n")}" STDERR.puts(warning) header[:content_transfer_encoding] = '8bit' end end def add_transfer_encoding # :nodoc: STDERR.puts(":add_transfer_encoding is deprecated in Mail 1.4.3. Please use add_content_transfer_encoding\n#{caller}") add_content_transfer_encoding end def transfer_encoding # :nodoc: STDERR.puts(":transfer_encoding is deprecated in Mail 1.4.3. Please use content_transfer_encoding\n#{caller}") content_transfer_encoding end # Returns the mime type of part we are on, this is taken from the content-type header def mime_type content_type ? header[:content_type].string : nil end def message_content_type STDERR.puts(":message_content_type is deprecated in Mail 1.4.3. Please use mime_type\n#{caller}") mime_type end # Returns the character set defined in the content type field def charset content_type ? content_type_parameters['charset'] : nil end # Sets the charset to the supplied value. Will set the content type to text/plain if # it does not already exist def charset=(value) if content_type content_type_parameters['charset'] = value else self.content_type ['text', 'plain', {'charset' => value}] end end # Returns the main content type def main_type has_content_type? ? header[:content_type].main_type : nil end # Returns the sub content type def sub_type has_content_type? ? header[:content_type].sub_type : nil end # Returns the content type parameters def mime_parameters STDERR.puts(':mime_parameters is deprecated in Mail 1.4.3, please use :content_type_parameters instead') content_type_parameters end # Returns the content type parameters def content_type_parameters has_content_type? ? header[:content_type].parameters : nil end # Returns true if the message is multipart def multipart? !!(main_type =~ /^multipart$/i) end # Returns true if the message is a multipart/report def multipart_report? multipart? && sub_type =~ /^report$/i end # Returns true if the message is a multipart/report; report-type=delivery-status; def delivery_status_report? multipart_report? && content_type_parameters['report-type'] =~ /^delivery-status$/i end # returns the part in a multipart/report email that has the content-type delivery-status def delivery_status_part @delivery_stats_part ||= parts.select { |p| p.delivery_status_report_part? }.first end def bounced? delivery_status_part.bounced? end def action delivery_status_part.action end def final_recipient delivery_status_part.final_recipient end def error_status delivery_status_part.error_status end def diagnostic_code delivery_status_part.diagnostic_code end def remote_mta delivery_status_part.remote_mta end def retryable? delivery_status_part.retryable? end # Returns the current boundary for this message part def boundary content_type_parameters ? content_type_parameters['boundary'] : nil end # Returns an array of parts in the message def parts body.parts end # Returns an array of attachments in the email recursively def attachments body.parts.map do |p| if p.parts.empty? p.attachment if p.attachment? else p.attachments end end.compact.flatten end def has_attachments? !attachments.empty? end # Accessor for html_part def html_part(&block) if block_given? @html_part = Mail::Part.new(&block) add_multipart_alternate_header add_part(@html_part) else @html_part end end # Accessor for text_part def text_part(&block) if block_given? @text_part = Mail::Part.new(&block) add_multipart_alternate_header add_part(@text_part) else @text_part end end # Helper to add a html part to a multipart/alternative email. If this and # text_part are both defined in a message, then it will be a multipart/alternative # message and set itself that way. def html_part=(msg = nil) if msg @html_part = msg else @html_part = Mail::Part.new('Content-Type: text/html;') end add_multipart_alternate_header add_part(@html_part) end # Helper to add a text part to a multipart/alternative email. If this and # html_part are both defined in a message, then it will be a multipart/alternative # message and set itself that way. def text_part=(msg = nil) if msg @text_part = msg else @text_part = Mail::Part.new('Content-Type: text/plain;') end add_multipart_alternate_header add_part(@text_part) end # Adds a part to the parts list or creates the part list def add_part(part) if body.parts.empty? && !self.body.decoded.blank? @text_part = Mail::Part.new('Content-Type: text/plain;') @text_part.body = body.decoded self.body << @text_part add_multipart_alternate_header end add_boundary self.body << part end # Allows you to add a part in block form to an existing mail message object # # Example: # # mail = Mail.new do # part :content_type => "multipart/alternative", :content_disposition => "inline" do |p| # p.part :content_type => "text/plain", :body => "test text\nline #2" # p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2" # end # end def part(params = {}) new_part = Part.new(params) yield new_part if block_given? add_part(new_part) end # Adds a file to the message. You have two options with this method, you can # just pass in the absolute path to the file you want and Mail will read the file, # get the filename from the path you pass in and guess the mime type, or you # can pass in the filename as a string, and pass in the file data as a blob. # # Example: # # m = Mail.new # m.add_file('/path/to/filename.png') # # or # # m = Mail.new # m.add_file(:filename => 'filename.png', :data => File.read('/path/to/filename.png')) # # The above two alternatives will produce the same email message. # # Note also that if you add a file to an existing message, Mail will convert that message # to a MIME multipart email, moving whatever plain text body you had into it's own text # plain part. # # Example: # # m = Mail.new do # body 'this is some text' # end # m.multipart? #=> false # m.add_file('/path/to/filename.png') # m.multipart? #=> true # m.parts.first.content_type.content_type #=> 'text/plain' # m.parts.last.content_type.content_type #=> 'image/png' def add_file(options) convert_to_multipart unless self.multipart? || self.body.decoded.blank? add_multipart_mixed_header if options.is_a?(Hash) self.body << Mail::Part.new(options) else self.body << Mail::Part.new(:filename => options) end end def convert_to_multipart text = @body.decoded self.body = '' text_part = Mail::Part.new({:content_type => 'text/plain;', :body => text}) self.body << text_part end # Encodes the message, calls encode on all it's parts, gets an email message # ready to send def ready_to_send! parts.each { |part| part.ready_to_send! } add_required_fields end def encode! STDERR.puts("Deprecated in 1.1.0 in favour of :ready_to_send! as it is less confusing with encoding and decoding.") ready_to_send! end # Outputs an encoded string representation of the mail message including # all headers, attachments, etc. This is an encoded email in US-ASCII, # so it is able to be directly sent to an email server. def encoded ready_to_send! buffer = header.encoded buffer << "\r\n" buffer << body.encoded buffer end def to_s encoded end def decoded raise NoMethodError, 'Can not decode an entire message, try calling #decoded on the various fields and body or parts if it is a multipart message.' end # Returns true if this part is an attachment def attachment? find_attachment end # Returns the attachment data if there is any def attachment @attachment end # Returns the filename of the attachment def filename if attachment? attachment.filename else nil end end private # 2.1. General Description # A message consists of header fields (collectively called "the header # of the message") followed, optionally, by a body. The header is a # sequence of lines of characters with special syntax as defined in # this standard. The body is simply a sequence of characters that # follows the header and is separated from the header by an empty line # (i.e., a line with nothing preceding the CRLF). # # Additionally, I allow for the case where someone might have put whitespace # on the "gap line" def parse_message header_part, body_part = raw_source.split(/#{CRLF}#{WSP}*#{CRLF}/m, 2) self.header = header_part self.body = body_part end def set_envelope_header if match_data = raw_source.to_s.match(/^From\s(#{TEXT}+)#{CRLF}(.*)/m) set_envelope(match_data[1]) self.raw_source = match_data[2] end end def separate_parts body.split!(boundary) end def add_encoding_to_body unless content_transfer_encoding.blank? body.encoding = content_transfer_encoding end end def add_required_fields @body = Mail::Body.new('') if body.nil? add_message_id unless (has_message_id? || self.class == Mail::Part) add_date unless has_date? add_mime_version unless has_mime_version? add_content_type unless has_content_type? add_charset unless has_charset? add_content_transfer_encoding unless has_content_transfer_encoding? end def add_multipart_alternate_header header['content-type'] = ContentTypeField.with_boundary('multipart/alternative').value body.boundary = boundary end def add_boundary unless body.boundary && boundary header['content-type'] = 'multipart/mixed' unless header['content-type'] header['content-type'].parameters[:boundary] = ContentTypeField.generate_boundary body.boundary = boundary end end def add_multipart_mixed_header unless header['content-type'] header['content-type'] = ContentTypeField.with_boundary('multipart/mixed').value body.boundary = boundary end end def init_with_hash(hash) passed_in_options = hash.with_indifferent_access self.raw_source = '' @header = Mail::Header.new @body = Mail::Body.new # Strip out the attachment headers and make an attachment if passed_in_options.has_key?(:filename) add_attachment(passed_in_options) passed_in_options.delete(:content_disposition) passed_in_options.delete(:content_type) passed_in_options.delete(:mime_type) passed_in_options.delete(:filename) passed_in_options.delete(:data) end passed_in_options.each_pair do |k,v| k = underscoreize(k).to_sym if k.class == String if k == :headers self.headers(v) else self[k] = v end end end def add_attachment(options_hash) @attachment = Mail::Attachment.new(options_hash) mime_type = options_hash[:content_type] || attachment.mime_type self.content_type = "#{mime_type}; filename=\"#{attachment.filename}\"" self.content_transfer_encoding = "Base64" disposition = options_hash[:content_disposition] || "attachment" self.content_disposition = "#{disposition}; filename=\"#{attachment.filename}\"" add_boundary self.body = attachment.encoded end def init_with_string(string) self.raw_source = string set_envelope_header parse_message separate_parts if multipart? if filename = attachment? encoding = header[:content_transfer_encoding].encoding if content_transfer_encoding @attachment = Mail::Attachment.new(:filename => filename, :data => body.encoded, :encoding => encoding) end end # Returns the filename of the attachment (if it exists) or returns nil def find_attachment case when content_type && header[:content_type].filename filename = header[:content_type].filename when content_disposition && header[:content_disposition].filename filename = header[:content_disposition].filename when content_location && header[:content_location].location filename = header[:content_location].location else filename = nil end filename end end end