kbaum-mail 2.1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. data/CHANGELOG.rdoc +289 -0
  2. data/README.rdoc +575 -0
  3. data/Rakefile +72 -0
  4. data/TODO.rdoc +19 -0
  5. data/lib/mail.rb +113 -0
  6. data/lib/mail/attachments_list.rb +76 -0
  7. data/lib/mail/body.rb +243 -0
  8. data/lib/mail/configuration.rb +69 -0
  9. data/lib/mail/core_extensions/nil.rb +11 -0
  10. data/lib/mail/core_extensions/string.rb +19 -0
  11. data/lib/mail/elements/address.rb +306 -0
  12. data/lib/mail/elements/address_list.rb +74 -0
  13. data/lib/mail/elements/content_disposition_element.rb +30 -0
  14. data/lib/mail/elements/content_location_element.rb +25 -0
  15. data/lib/mail/elements/content_transfer_encoding_element.rb +21 -0
  16. data/lib/mail/elements/content_type_element.rb +35 -0
  17. data/lib/mail/elements/date_time_element.rb +26 -0
  18. data/lib/mail/elements/envelope_from_element.rb +34 -0
  19. data/lib/mail/elements/message_ids_element.rb +29 -0
  20. data/lib/mail/elements/mime_version_element.rb +26 -0
  21. data/lib/mail/elements/phrase_list.rb +21 -0
  22. data/lib/mail/elements/received_element.rb +30 -0
  23. data/lib/mail/encodings/base64.rb +18 -0
  24. data/lib/mail/encodings/encodings.rb +201 -0
  25. data/lib/mail/encodings/quoted_printable.rb +26 -0
  26. data/lib/mail/envelope.rb +35 -0
  27. data/lib/mail/field.rb +219 -0
  28. data/lib/mail/field_list.rb +33 -0
  29. data/lib/mail/fields/bcc_field.rb +53 -0
  30. data/lib/mail/fields/cc_field.rb +52 -0
  31. data/lib/mail/fields/comments_field.rb +41 -0
  32. data/lib/mail/fields/common/address_container.rb +16 -0
  33. data/lib/mail/fields/common/common_address.rb +128 -0
  34. data/lib/mail/fields/common/common_date.rb +51 -0
  35. data/lib/mail/fields/common/common_field.rb +64 -0
  36. data/lib/mail/fields/common/common_message_id.rb +57 -0
  37. data/lib/mail/fields/common/parameter_hash.rb +39 -0
  38. data/lib/mail/fields/content_description_field.rb +19 -0
  39. data/lib/mail/fields/content_disposition_field.rb +60 -0
  40. data/lib/mail/fields/content_id_field.rb +63 -0
  41. data/lib/mail/fields/content_location_field.rb +42 -0
  42. data/lib/mail/fields/content_transfer_encoding_field.rb +45 -0
  43. data/lib/mail/fields/content_type_field.rb +175 -0
  44. data/lib/mail/fields/date_field.rb +53 -0
  45. data/lib/mail/fields/from_field.rb +53 -0
  46. data/lib/mail/fields/in_reply_to_field.rb +52 -0
  47. data/lib/mail/fields/keywords_field.rb +43 -0
  48. data/lib/mail/fields/message_id_field.rb +80 -0
  49. data/lib/mail/fields/mime_version_field.rb +54 -0
  50. data/lib/mail/fields/optional_field.rb +11 -0
  51. data/lib/mail/fields/received_field.rb +62 -0
  52. data/lib/mail/fields/references_field.rb +53 -0
  53. data/lib/mail/fields/reply_to_field.rb +53 -0
  54. data/lib/mail/fields/resent_bcc_field.rb +53 -0
  55. data/lib/mail/fields/resent_cc_field.rb +53 -0
  56. data/lib/mail/fields/resent_date_field.rb +33 -0
  57. data/lib/mail/fields/resent_from_field.rb +53 -0
  58. data/lib/mail/fields/resent_message_id_field.rb +32 -0
  59. data/lib/mail/fields/resent_sender_field.rb +60 -0
  60. data/lib/mail/fields/resent_to_field.rb +53 -0
  61. data/lib/mail/fields/return_path_field.rb +62 -0
  62. data/lib/mail/fields/sender_field.rb +65 -0
  63. data/lib/mail/fields/structured_field.rb +36 -0
  64. data/lib/mail/fields/subject_field.rb +15 -0
  65. data/lib/mail/fields/to_field.rb +53 -0
  66. data/lib/mail/fields/unstructured_field.rb +117 -0
  67. data/lib/mail/header.rb +235 -0
  68. data/lib/mail/mail.rb +194 -0
  69. data/lib/mail/message.rb +1780 -0
  70. data/lib/mail/network/delivery_methods/file_delivery.rb +40 -0
  71. data/lib/mail/network/delivery_methods/sendmail.rb +62 -0
  72. data/lib/mail/network/delivery_methods/smtp.rb +110 -0
  73. data/lib/mail/network/delivery_methods/test_mailer.rb +40 -0
  74. data/lib/mail/network/retriever_methods/imap.rb +31 -0
  75. data/lib/mail/network/retriever_methods/pop3.rb +149 -0
  76. data/lib/mail/parsers/address_lists.rb +61 -0
  77. data/lib/mail/parsers/address_lists.treetop +19 -0
  78. data/lib/mail/parsers/content_disposition.rb +369 -0
  79. data/lib/mail/parsers/content_disposition.treetop +46 -0
  80. data/lib/mail/parsers/content_location.rb +133 -0
  81. data/lib/mail/parsers/content_location.treetop +20 -0
  82. data/lib/mail/parsers/content_transfer_encoding.rb +179 -0
  83. data/lib/mail/parsers/content_transfer_encoding.treetop +25 -0
  84. data/lib/mail/parsers/content_type.rb +512 -0
  85. data/lib/mail/parsers/content_type.treetop +58 -0
  86. data/lib/mail/parsers/date_time.rb +111 -0
  87. data/lib/mail/parsers/date_time.treetop +11 -0
  88. data/lib/mail/parsers/envelope_from.rb +188 -0
  89. data/lib/mail/parsers/envelope_from.treetop +32 -0
  90. data/lib/mail/parsers/message_ids.rb +42 -0
  91. data/lib/mail/parsers/message_ids.treetop +15 -0
  92. data/lib/mail/parsers/mime_version.rb +141 -0
  93. data/lib/mail/parsers/mime_version.treetop +19 -0
  94. data/lib/mail/parsers/phrase_lists.rb +42 -0
  95. data/lib/mail/parsers/phrase_lists.treetop +15 -0
  96. data/lib/mail/parsers/received.rb +68 -0
  97. data/lib/mail/parsers/received.treetop +11 -0
  98. data/lib/mail/parsers/rfc2045.rb +406 -0
  99. data/lib/mail/parsers/rfc2045.treetop +35 -0
  100. data/lib/mail/parsers/rfc2822.rb +5081 -0
  101. data/lib/mail/parsers/rfc2822.treetop +410 -0
  102. data/lib/mail/parsers/rfc2822_obsolete.rb +3607 -0
  103. data/lib/mail/parsers/rfc2822_obsolete.treetop +241 -0
  104. data/lib/mail/part.rb +82 -0
  105. data/lib/mail/parts_list.rb +34 -0
  106. data/lib/mail/patterns.rb +43 -0
  107. data/lib/mail/utilities.rb +163 -0
  108. data/lib/mail/vendor/treetop.rb +4 -0
  109. data/lib/mail/version.rb +10 -0
  110. data/lib/mail/version_specific/ruby_1_8.rb +84 -0
  111. data/lib/mail/version_specific/ruby_1_9.rb +77 -0
  112. data/lib/tasks/corpus.rake +125 -0
  113. data/lib/tasks/treetop.rake +10 -0
  114. metadata +188 -0
@@ -0,0 +1,235 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+
4
+ # Provides access to a header object.
5
+ #
6
+ # ===Per RFC2822
7
+ #
8
+ # 2.2. Header Fields
9
+ #
10
+ # Header fields are lines composed of a field name, followed by a colon
11
+ # (":"), followed by a field body, and terminated by CRLF. A field
12
+ # name MUST be composed of printable US-ASCII characters (i.e.,
13
+ # characters that have values between 33 and 126, inclusive), except
14
+ # colon. A field body may be composed of any US-ASCII characters,
15
+ # except for CR and LF. However, a field body may contain CRLF when
16
+ # used in header "folding" and "unfolding" as described in section
17
+ # 2.2.3. All field bodies MUST conform to the syntax described in
18
+ # sections 3 and 4 of this standard.
19
+ class Header
20
+ include Patterns
21
+ include Utilities
22
+ include Enumerable
23
+
24
+ # Creates a new header object.
25
+ #
26
+ # Accepts raw text or nothing. If given raw text will attempt to parse
27
+ # it and split it into the various fields, instantiating each field as
28
+ # it goes.
29
+ #
30
+ # If it finds a field that should be a structured field (such as content
31
+ # type), but it fails to parse it, it will simply make it an unstructured
32
+ # field and leave it alone. This will mean that the data is preserved but
33
+ # no automatic processing of that field will happen. If you find one of
34
+ # these cases, please make a patch and send it in, or at the least, send
35
+ # me the example so we can fix it.
36
+ def initialize(header_text = nil)
37
+ self.raw_source = header_text.to_crlf
38
+ split_header if header_text
39
+ end
40
+
41
+ # The preserved raw source of the header as you passed it in, untouched
42
+ # for your Regexing glory.
43
+ def raw_source
44
+ @raw_source
45
+ end
46
+
47
+ # Returns an array of all the fields in the header in order that they
48
+ # were read in.
49
+ def fields
50
+ @fields ||= FieldList.new
51
+ end
52
+
53
+ # 3.6. Field definitions
54
+ #
55
+ # It is important to note that the header fields are not guaranteed to
56
+ # be in a particular order. They may appear in any order, and they
57
+ # have been known to be reordered occasionally when transported over
58
+ # the Internet. However, for the purposes of this standard, header
59
+ # fields SHOULD NOT be reordered when a message is transported or
60
+ # transformed. More importantly, the trace header fields and resent
61
+ # header fields MUST NOT be reordered, and SHOULD be kept in blocks
62
+ # prepended to the message. See sections 3.6.6 and 3.6.7 for more
63
+ # information.
64
+ #
65
+ # Populates the fields container with Field objects in the order it
66
+ # receives them in.
67
+ #
68
+ # Acceps an array of field string values, for example:
69
+ #
70
+ # h = Header.new
71
+ # h.fields = ['From: mikel@me.com', 'To: bob@you.com']
72
+ def fields=(unfolded_fields)
73
+ @fields = Mail::FieldList.new
74
+ unfolded_fields.each do |field|
75
+
76
+ field = Field.new(field)
77
+ selected = select_field_for(field.name)
78
+
79
+ if selected.any? && limited_field?(field.name)
80
+ selected.first.update(field.name, field.value)
81
+ else
82
+ @fields << field
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ # 3.6. Field definitions
89
+ #
90
+ # The following table indicates limits on the number of times each
91
+ # field may occur in a message header as well as any special
92
+ # limitations on the use of those fields. An asterisk next to a value
93
+ # in the minimum or maximum column indicates that a special restriction
94
+ # appears in the Notes column.
95
+ #
96
+ # <snip table from 3.6>
97
+ #
98
+ # As per RFC, many fields can appear more than once, we will return a string
99
+ # of the value if there is only one header, or if there is more than one
100
+ # matching header, will return an array of values in order that they appear
101
+ # in the header ordered from top to bottom.
102
+ #
103
+ # Example:
104
+ #
105
+ # h = Header.new
106
+ # h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
107
+ # h['To'] #=> 'mikel@me.com'
108
+ # h['X-Mail-SPAM'] #=> ['15', '20']
109
+ def [](name)
110
+ name = dasherize(name)
111
+ selected = select_field_for(name)
112
+ case
113
+ when selected.length > 1
114
+ selected.map { |f| f }
115
+ when !selected.blank?
116
+ selected.first
117
+ else
118
+ nil
119
+ end
120
+ end
121
+
122
+ # Sets the FIRST matching field in the header to passed value, or deletes
123
+ # the FIRST field matched from the header if passed nil
124
+ #
125
+ # Example:
126
+ #
127
+ # h = Header.new
128
+ # h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
129
+ # h['To'] = 'bob@you.com'
130
+ # h['To'] #=> 'bob@you.com'
131
+ # h['X-Mail-SPAM'] = '10000'
132
+ # h['X-Mail-SPAM'] # => ['15', '20', '10000']
133
+ # h['X-Mail-SPAM'] = nil
134
+ # h['X-Mail-SPAM'] # => nil
135
+ def []=(name, value)
136
+ name = dasherize(name)
137
+ selected = select_field_for(name)
138
+
139
+ case
140
+ # User wants to delete the field
141
+ when !selected.blank? && value == nil
142
+ fields.delete_if { |f| selected.include?(f) }
143
+
144
+ # User wants to change the field
145
+ when !selected.blank? && limited_field?(name)
146
+ selected.first.update(name, value)
147
+
148
+ # User wants to create the field
149
+ else
150
+ # Need to insert in correct order for trace fields
151
+ self.fields << Field.new(name.to_s, value)
152
+ end
153
+ end
154
+
155
+ LIMITED_FIELDS = %w[ orig-date from sender reply-to to cc bcc
156
+ message-id in-reply-to references subject
157
+ return-path content-type mime-version
158
+ content-transfer-encoding content-description
159
+ content-id content-disposition content-location]
160
+
161
+ def encoded
162
+ buffer = ''
163
+ fields.each do |field|
164
+ buffer << field.encoded
165
+ end
166
+ buffer
167
+ end
168
+
169
+ def to_s
170
+ encoded
171
+ end
172
+
173
+ def decoded
174
+ raise NoMethodError, 'Can not decode an entire header as there could be character set conflicts, try calling #decoded on the various fields.'
175
+ end
176
+
177
+ # Returns true if the header has a Message-ID defined (empty or not)
178
+ def has_message_id?
179
+ !fields.select { |f| f.responsible_for?('Message-ID') }.empty?
180
+ end
181
+
182
+ # Returns true if the header has a Content-ID defined (empty or not)
183
+ def has_content_id?
184
+ !fields.select { |f| f.responsible_for?('Content-ID') }.empty?
185
+ end
186
+
187
+ # Returns true if the header has a Date defined (empty or not)
188
+ def has_date?
189
+ !fields.select { |f| f.responsible_for?('Date') }.empty?
190
+ end
191
+
192
+ # Returns true if the header has a message_id defined (empty or not)
193
+ def has_mime_version?
194
+ !fields.select { |f| f.responsible_for?('Mime-Version') }.empty?
195
+ end
196
+
197
+ private
198
+
199
+ def raw_source=(val)
200
+ @raw_source = val
201
+ end
202
+
203
+ # 2.2.3. Long Header Fields
204
+ #
205
+ # The process of moving from this folded multiple-line representation
206
+ # of a header field to its single line representation is called
207
+ # "unfolding". Unfolding is accomplished by simply removing any CRLF
208
+ # that is immediately followed by WSP. Each header field should be
209
+ # treated in its unfolded form for further syntactic and semantic
210
+ # evaluation.
211
+ def unfold(string)
212
+ string.gsub(/#{CRLF}#{WSP}+/, ' ').gsub(/#{WSP}+/, ' ')
213
+ end
214
+
215
+ # Returns the header with all the folds removed
216
+ def unfolded_header
217
+ @unfolded_header ||= unfold(raw_source)
218
+ end
219
+
220
+ # Splits an unfolded and line break cleaned header into individual field
221
+ # strings.
222
+ def split_header
223
+ self.fields = unfolded_header.split(CRLF)
224
+ end
225
+
226
+ def select_field_for(name)
227
+ fields.select { |f| f.responsible_for?(name.to_s) }
228
+ end
229
+
230
+ def limited_field?(name)
231
+ LIMITED_FIELDS.include?(name.to_s.downcase)
232
+ end
233
+
234
+ end
235
+ end
@@ -0,0 +1,194 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+
4
+ # Allows you to create a new Mail::Message object.
5
+ #
6
+ # You can make an email via passing a string or passing a block.
7
+ #
8
+ # For example, the following two examples will create the same email
9
+ # message:
10
+ #
11
+ # Creating via a string:
12
+ #
13
+ # string = 'To: mikel@test.lindsaar.net\r\n'
14
+ # string << 'From: bob@test.lindsaar.net\r\n\r\n'
15
+ # string << 'Subject: This is an email\r\n'
16
+ # string << '\r\n'
17
+ # string << 'This is the body'
18
+ # Mail.new(string)
19
+ #
20
+ # Or creating via a block:
21
+ #
22
+ # message = Mail.new do
23
+ # to 'mikel@test.lindsaar.net'
24
+ # from 'bob@test.lindsaar.net'
25
+ # subject 'This is an email'
26
+ # body 'This is the body'
27
+ # end
28
+ #
29
+ # Or creating via a hash (or hash like object):
30
+ #
31
+ # message = Mail.new({:to => 'mikel@test.lindsaar.net',
32
+ # 'from' => 'bob@test.lindsaar.net',
33
+ # :subject 'This is an email',
34
+ # :body 'This is the body' })
35
+ #
36
+ # Note, the hash keys can be strings or symbols, the passed in object
37
+ # does not need to be a hash, it just needs to respond to :each_pair
38
+ # and yield each key value pair.
39
+ #
40
+ # As a side note, you can also create a new email through creating
41
+ # a Mail::Message object directly and then passing in values via string,
42
+ # symbol or direct method calls. See Mail::Message for more information.
43
+ #
44
+ # mail = Mail.new
45
+ # mail.to = 'mikel@test.lindsaar.net'
46
+ # mail[:from] = 'bob@test.lindsaar.net'
47
+ # mail['subject'] = 'This is an email'
48
+ # mail.body = 'This is the body'
49
+ def Mail.new(*args, &block)
50
+ Mail::Message.new(args, &block)
51
+ end
52
+
53
+ # Sets the default delivery method and retriever method for all new Mail objects.
54
+ # The delivery_method and retriever_method default to :smtp and :pop3, with defaults
55
+ # set.
56
+ #
57
+ # So sending a new email, if you have an SMTP server running on localhost is
58
+ # as easy as:
59
+ #
60
+ # Mail.deliver do
61
+ # to 'mikel@test.lindsaar.net'
62
+ # from 'bob@test.lindsaar.net'
63
+ # subject 'hi there!'
64
+ # body 'this is a body'
65
+ # end
66
+ #
67
+ # If you do not specify anything, you will get the following equivalent code set in
68
+ # every new mail object:
69
+ #
70
+ # Mail.defaults do
71
+ # delivery_method :smtp, { :address => "localhost",
72
+ # :port => 25,
73
+ # :domain => 'localhost.localdomain',
74
+ # :user_name => nil,
75
+ # :password => nil,
76
+ # :authentication => nil,
77
+ # :enable_starttls_auto => true }
78
+ #
79
+ # retriever_method :pop3, { :address => "localhost",
80
+ # :port => 995,
81
+ # :user_name => nil,
82
+ # :password => nil,
83
+ # :enable_ssl => true }
84
+ # end
85
+ #
86
+ # Mail.delivery_method.new #=> Mail::SMTP instance
87
+ # Mail.retriever_method.new #=> Mail::POP3 instance
88
+ #
89
+ # Each mail object inherits the default set in Mail.delivery_method, however, on
90
+ # a per email basis, you can override the method:
91
+ #
92
+ # mail.delivery_method :sendmail
93
+ #
94
+ # Or you can override the method and pass in settings:
95
+ #
96
+ # mail.delivery_method :sendmail, { :address => 'some.host' }
97
+ #
98
+ # You can also just modify the settings:
99
+ #
100
+ # mail.delivery_settings = { :address => 'some.host' }
101
+ #
102
+ # The passed in hash is just merged against the defaults with +merge!+ and the result
103
+ # assigned the mail object. So the above example will change only the :address value
104
+ # of the global smtp_settings to be 'some.host', keeping all other values
105
+ def Mail.defaults(&block)
106
+ Mail::Configuration.instance.instance_eval(&block)
107
+ end
108
+
109
+ # Returns the delivery method selected, defaults to an instance of Mail::SMTP
110
+ def Mail.delivery_method
111
+ Mail::Configuration.instance.delivery_method
112
+ end
113
+
114
+ # Returns the retriever method selected, defaults to an instance of Mail::POP3
115
+ def Mail.retriever_method
116
+ Mail::Configuration.instance.retriever_method
117
+ end
118
+
119
+ # Send an email using the default configuration. You do need to set a default
120
+ # configuration first before you use Mail.deliver, if you don't, an appropriate
121
+ # error will be raised telling you to.
122
+ #
123
+ # If you do not specify a delivery type, SMTP will be used.
124
+ #
125
+ # Mail.deliver do
126
+ # to 'mikel@test.lindsaar.net'
127
+ # from 'ada@test.lindsaar.net'
128
+ # subject 'This is a test email'
129
+ # body 'Not much to say here'
130
+ # end
131
+ #
132
+ # You can also do:
133
+ #
134
+ # mail = Mail.read('email.eml')
135
+ # mail.deliver!
136
+ #
137
+ # And your email object will be created and sent.
138
+ def Mail.deliver(*args, &block)
139
+ mail = Mail.new(args, &block)
140
+ mail.deliver
141
+ mail
142
+ end
143
+
144
+ # Find emails in a POP3 server.
145
+ # See Mail::POP3 for a complete documentation.
146
+ def Mail.find(*args, &block)
147
+ retriever_method.find(*args, &block)
148
+ end
149
+
150
+ # Receive the first email(s) from a Pop3 server.
151
+ # See Mail::POP3 for a complete documentation.
152
+ def Mail.first(*args, &block)
153
+ retriever_method.first(*args, &block)
154
+ end
155
+
156
+ # Receive the first email(s) from a Pop3 server.
157
+ # See Mail::POP3 for a complete documentation.
158
+ def Mail.last(*args, &block)
159
+ retriever_method.last(*args, &block)
160
+ end
161
+
162
+ # Receive all emails from a POP3 server.
163
+ # See Mail::POP3 for a complete documentation.
164
+ def Mail.all(*args, &block)
165
+ retriever_method.all(*args, &block)
166
+ end
167
+
168
+ # Reads in an email message from a path and instantiates it as a new Mail::Message
169
+ def Mail.read(filename)
170
+ Mail.new(File.read(filename))
171
+ end
172
+
173
+ protected
174
+
175
+ def Mail.random_tag
176
+ t = Time.now
177
+ sprintf('%x%x_%x%x%d%x',
178
+ t.to_i, t.tv_usec,
179
+ $$, Thread.current.object_id.abs, Mail.uniq, rand(255))
180
+ end
181
+
182
+ private
183
+
184
+ def Mail.something_random
185
+ (Thread.current.object_id * rand(255) / Time.now.to_f).to_s.slice(-3..-1).to_i
186
+ end
187
+
188
+ def Mail.uniq
189
+ @@uniq += 1
190
+ end
191
+
192
+ @@uniq = Mail.something_random
193
+
194
+ end
@@ -0,0 +1,1780 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ # The Message class provides a single point of access to all things to do with an
4
+ # email message.
5
+ #
6
+ # You create a new email message by calling the Mail::Message.new method, or just
7
+ # Mail.new
8
+ #
9
+ # A Message object by default has the following objects inside it:
10
+ #
11
+ # * A Header object which contians all information and settings of the header of the email
12
+ # * Body object which contains all parts of the email that are not part of the header, this
13
+ # includes any attachments, body text, mime parts etc.
14
+ #
15
+ # ==Per RFC2822
16
+ #
17
+ # 2.1. General Description
18
+ #
19
+ # At the most basic level, a message is a series of characters. A
20
+ # message that is conformant with this standard is comprised of
21
+ # characters with values in the range 1 through 127 and interpreted as
22
+ # US-ASCII characters [ASCII]. For brevity, this document sometimes
23
+ # refers to this range of characters as simply "US-ASCII characters".
24
+ #
25
+ # Note: This standard specifies that messages are made up of characters
26
+ # in the US-ASCII range of 1 through 127. There are other documents,
27
+ # specifically the MIME document series [RFC2045, RFC2046, RFC2047,
28
+ # RFC2048, RFC2049], that extend this standard to allow for values
29
+ # outside of that range. Discussion of those mechanisms is not within
30
+ # the scope of this standard.
31
+ #
32
+ # Messages are divided into lines of characters. A line is a series of
33
+ # characters that is delimited with the two characters carriage-return
34
+ # and line-feed; that is, the carriage return (CR) character (ASCII
35
+ # value 13) followed immediately by the line feed (LF) character (ASCII
36
+ # value 10). (The carriage-return/line-feed pair is usually written in
37
+ # this document as "CRLF".)
38
+ #
39
+ # A message consists of header fields (collectively called "the header
40
+ # of the message") followed, optionally, by a body. The header is a
41
+ # sequence of lines of characters with special syntax as defined in
42
+ # this standard. The body is simply a sequence of characters that
43
+ # follows the header and is separated from the header by an empty line
44
+ # (i.e., a line with nothing preceding the CRLF).
45
+ class Message
46
+
47
+ include Patterns
48
+ include Utilities
49
+
50
+ # ==Making an email
51
+ #
52
+ # You can make an new mail object via a block, passing a string, file or direct assignment.
53
+ #
54
+ # ===Making an email via a block
55
+ #
56
+ # mail = Mail.new do
57
+ # from 'mikel@test.lindsaar.net'
58
+ # to 'you@test.lindsaar.net'
59
+ # subject 'This is a test email'
60
+ # body File.read('body.txt')
61
+ # end
62
+ #
63
+ # mail.to_s #=> "From: mikel@test.lindsaar.net\r\nTo: you@...
64
+ #
65
+ # ===Making an email via passing a string
66
+ #
67
+ # mail = Mail.new("To: mikel@test.lindsaar.net\r\nSubject: Hello\r\n\r\nHi there!")
68
+ # mail.body.to_s #=> 'Hi there!'
69
+ # mail.subject #=> 'Hello'
70
+ # mail.to #=> 'mikel@test.lindsaar.net'
71
+ #
72
+ # ===Making an email from a file
73
+ #
74
+ # mail = Mail.read('path/to/file.eml')
75
+ # mail.body.to_s #=> 'Hi there!'
76
+ # mail.subject #=> 'Hello'
77
+ # mail.to #=> 'mikel@test.lindsaar.net'
78
+ #
79
+ # ===Making an email via assignment
80
+ #
81
+ # You can assign values to a mail object via four approaches:
82
+ #
83
+ # * Message#field_name=(value)
84
+ # * Message#field_name(value)
85
+ # * Message#['field_name']=(value)
86
+ # * Message#[:field_name]=(value)
87
+ #
88
+ # Examples:
89
+ #
90
+ # mail = Mail.new
91
+ # mail['from'] = 'mikel@test.lindsaar.net'
92
+ # mail[:to] = 'you@test.lindsaar.net'
93
+ # mail.subject 'This is a test email'
94
+ # mail.body = 'This is a body'
95
+ #
96
+ # mail.to_s #=> "From: mikel@test.lindsaar.net\r\nTo: you@...
97
+ #
98
+ def initialize(*args, &block)
99
+ @body = nil
100
+ @text_part = nil
101
+ @html_part = nil
102
+
103
+ @perform_deliveries = true
104
+ @raise_delivery_errors = true
105
+
106
+ @delivery_handler = nil
107
+
108
+ @delivery_method = Mail.delivery_method.dup
109
+ @delivery_notification_observers = []
110
+
111
+ if args.flatten.first.respond_to?(:each_pair)
112
+ init_with_hash(args.flatten.first)
113
+ else
114
+ init_with_string(args.flatten[0].to_s.strip)
115
+ end
116
+
117
+ if block_given?
118
+ instance_eval(&block)
119
+ end
120
+
121
+ self
122
+ end
123
+
124
+ # If you assign a delivery handler, mail will call :deliver_mail on the
125
+ # object you assign to delivery_handler, it will pass itself as the
126
+ # single argument.
127
+ #
128
+ # If you define a delivery_handler, then you are responsible for the
129
+ # following actions in the delivery cycle:
130
+ #
131
+ # * Appending the mail object to Mail.deliveries as you see fit.
132
+ # * Checking the mail.perform_deliveries flag to decide if you should
133
+ # actually call :deliver! the mail object or not.
134
+ # * Checking the mail.raise_delivery_errors flag to decide if you
135
+ # should raise delivery errors if they occur.
136
+ # * Actually calling :deliver! (with the bang) on the mail object to
137
+ # get it to deliver itself.
138
+ #
139
+ # A simplest implementation of a delivery_handler would be
140
+ #
141
+ # class MyObject
142
+ #
143
+ # def initialize
144
+ # @mail = Mail.new('To: mikel@test.lindsaar.net')
145
+ # @mail.delivery_handler = self
146
+ # end
147
+ #
148
+ # attr_accessor :mail
149
+ #
150
+ # def deliver_mail(mail)
151
+ # yield
152
+ # end
153
+ # end
154
+ #
155
+ # Then doing:
156
+ #
157
+ # obj = MyObject.new
158
+ # obj.mail.deliver
159
+ #
160
+ # Would cause Mail to call obj.deliver_mail passing itself as a parameter,
161
+ # which then can just yield and let Mail do it's own private do_delivery
162
+ # method.
163
+ attr_accessor :delivery_handler
164
+
165
+ # If set to false, mail will go through the motions of doing a delivery,
166
+ # but not actually call the delivery method or append the mail object to
167
+ # the Mail.deliveries collection. Useful for testing.
168
+ #
169
+ # Mail.deliveries.size #=> 0
170
+ # mail.delivery_method :smtp
171
+ # mail.perform_deliveries = false
172
+ # mail.deliver # Mail::SMTP not called here
173
+ # Mail.deliveries.size #=> 0
174
+ #
175
+ # If you want to test and query the Mail.deliveries collection to see what
176
+ # mail you sent, you should set perform_deliveries to true and use
177
+ # the :test mail delivery_method:
178
+ #
179
+ # Mail.deliveries.size #=> 0
180
+ # mail.delivery_method :test
181
+ # mail.perform_deliveries = true
182
+ # mail.deliver
183
+ # Mail.deliveries.size #=> 1
184
+ #
185
+ # This setting is ignored by mail (though still available as a flag) if you
186
+ # define a delivery_handler
187
+ attr_accessor :perform_deliveries
188
+
189
+ # If set to false, mail will silently catch and ignore any exceptions
190
+ # raised through attempting to deliver an email.
191
+ #
192
+ # This setting is ignored by mail (though still available as a flag) if you
193
+ # define a delivery_handler
194
+ attr_accessor :raise_delivery_errors
195
+
196
+ def register_for_delivery_notification(observer)
197
+ unless @delivery_notification_observers.include?(observer)
198
+ @delivery_notification_observers << observer
199
+ end
200
+ end
201
+
202
+ def inform_observers
203
+ @delivery_notification_observers.each do |observer|
204
+ observer.delivered_email(self)
205
+ end
206
+ end
207
+
208
+ # Delivers an mail object.
209
+ #
210
+ # Examples:
211
+ #
212
+ # mail = Mail.read('file.eml')
213
+ # mail.deliver
214
+ def deliver
215
+ if delivery_handler
216
+ delivery_handler.deliver_mail(self) { do_delivery }
217
+ else
218
+ do_delivery
219
+ inform_observers
220
+ end
221
+ self
222
+ end
223
+
224
+ # This method bypasses checking perform_deliveries and raise_delivery_errors,
225
+ # so use with caution.
226
+ #
227
+ # It still however fires callbacks to the observers if they are defined.
228
+ #
229
+ # Returns self
230
+ def deliver!
231
+ delivery_method.deliver!(self)
232
+ inform_observers
233
+ self
234
+ end
235
+
236
+ def delivery_method(method = nil, settings = {})
237
+ unless method
238
+ @delivery_method
239
+ else
240
+ @delivery_method = Mail::Configuration.instance.lookup_delivery_method(method).new(settings)
241
+ end
242
+ end
243
+
244
+ # Provides the operator needed for sort et al.
245
+ #
246
+ # Compares this mail object with another mail object, this is done by date, so an
247
+ # email that is older than another will appear first.
248
+ #
249
+ # Example:
250
+ #
251
+ # mail1 = Mail.new do
252
+ # date(Time.now)
253
+ # end
254
+ # mail2 = Mail.new do
255
+ # date(Time.now - 86400) # 1 day older
256
+ # end
257
+ # [mail2, mail1].sort #=> [mail2, mail1]
258
+ def <=>(other)
259
+ if other.nil?
260
+ 1
261
+ else
262
+ self.date <=> other.date
263
+ end
264
+ end
265
+
266
+ # Two emails are the same if they have the same fields and body contents. One
267
+ # gotcha here is that Mail will insert Message-IDs when calling encoded, so doing
268
+ # mail1.encoded == mail2.encoded is most probably not going to return what you think
269
+ # as the assigned Message-IDs by Mail (if not already defined as the same) will ensure
270
+ # that the two objects are unique, and this comparison will ALWAYS return false.
271
+ #
272
+ # So the == operator has been defined like so: Two messages are the same if they have
273
+ # the same content, ignoring the Message-ID field, unless BOTH emails have a defined and
274
+ # different Message-ID value, then they are false.
275
+ #
276
+ # So, in practice the == operator works like this:
277
+ #
278
+ # m1 = Mail.new("Subject: Hello\r\n\r\nHello")
279
+ # m2 = Mail.new("Subject: Hello\r\n\r\nHello")
280
+ # m1 == m2 #=> true
281
+ #
282
+ # m1 = Mail.new("Subject: Hello\r\n\r\nHello")
283
+ # m2 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
284
+ # m1 == m2 #=> true
285
+ #
286
+ # m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
287
+ # m2 = Mail.new("Subject: Hello\r\n\r\nHello")
288
+ # m1 == m2 #=> true
289
+ #
290
+ # m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
291
+ # m2 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
292
+ # m1 == m2 #=> true
293
+ #
294
+ # m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
295
+ # m2 = Mail.new("Message-ID: <DIFFERENT@test>\r\nSubject: Hello\r\n\r\nHello")
296
+ # m1 == m2 #=> false
297
+ def ==(other)
298
+ return false unless other.respond_to?(:encoded)
299
+
300
+ if self.message_id && other.message_id
301
+ result = (self.encoded == other.encoded)
302
+ else
303
+ self_message_id, other_message_id = self.message_id, other.message_id
304
+ self.message_id, other.message_id = '<temp@test>', '<temp@test>'
305
+ result = self.encoded == other.encoded
306
+ self.message_id, other.message_id = "<#{self_message_id}>", "<#{other_message_id}>"
307
+ result
308
+ end
309
+ end
310
+
311
+ # Provides access to the raw source of the message as it was when it
312
+ # was instantiated. This is set at initialization and so is untouched
313
+ # by the parsers or decoder / encoders
314
+ #
315
+ # Example:
316
+ #
317
+ # mail = Mail.new('This is an invalid email message')
318
+ # mail.raw_source #=> "This is an invalid email message"
319
+ def raw_source
320
+ @raw_source
321
+ end
322
+
323
+ # Sets the envelope from for the email
324
+ def set_envelope( val )
325
+ @raw_envelope = val
326
+ @envelope = Mail::Envelope.new( val )
327
+ end
328
+
329
+ # The raw_envelope is the From mikel@test.lindsaar.net Mon May 2 16:07:05 2009
330
+ # type field that you can see at the top of any email that has come
331
+ # from a mailbox
332
+ def raw_envelope
333
+ @raw_envelope
334
+ end
335
+
336
+ def envelope_from
337
+ @envelope ? @envelope.from : nil
338
+ end
339
+
340
+ def envelope_date
341
+ @envelope ? @envelope.date : nil
342
+ end
343
+
344
+ # Sets the header of the message object.
345
+ #
346
+ # Example:
347
+ #
348
+ # mail.header = 'To: mikel@test.lindsaar.net\r\nFrom: Bob@bob.com'
349
+ # mail.header #=> <#Mail::Header
350
+ def header=(value)
351
+ @header = Mail::Header.new(value)
352
+ end
353
+
354
+ # Returns the header object of the message object. Or, if passed
355
+ # a parameter sets the value.
356
+ #
357
+ # Example:
358
+ #
359
+ # mail = Mail::Message.new('To: mikel\r\nFrom: you')
360
+ # mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr...
361
+ #
362
+ # mail.header #=> nil
363
+ # mail.header 'To: mikel\r\nFrom: you'
364
+ # mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr...
365
+ def header(value = nil)
366
+ value ? self.header = value : @header
367
+ end
368
+
369
+ # Provides a way to set custom headers, by passing in a hash
370
+ def headers(hash = {})
371
+ hash.each_pair do |k,v|
372
+ header[k] = v
373
+ end
374
+ end
375
+
376
+ # Returns the Bcc value of the mail object as an array of strings of
377
+ # address specs.
378
+ #
379
+ # Example:
380
+ #
381
+ # mail.bcc = 'Mikel <mikel@test.lindsaar.net>'
382
+ # mail.bcc #=> ['mikel@test.lindsaar.net']
383
+ # mail.bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
384
+ # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
385
+ #
386
+ # Also allows you to set the value by passing a value as a parameter
387
+ #
388
+ # Example:
389
+ #
390
+ # mail.bcc 'Mikel <mikel@test.lindsaar.net>'
391
+ # mail.bcc #=> ['mikel@test.lindsaar.net']
392
+ #
393
+ # Additionally, you can append new addresses to the returned Array like
394
+ # object.
395
+ #
396
+ # Example:
397
+ #
398
+ # mail.bcc 'Mikel <mikel@test.lindsaar.net>'
399
+ # mail.bcc << 'ada@test.lindsaar.net'
400
+ # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
401
+ def bcc( val = nil )
402
+ default :bcc, val
403
+ end
404
+
405
+ # Sets the Bcc value of the mail object, pass in a string of the field
406
+ #
407
+ # Example:
408
+ #
409
+ # mail.bcc = 'Mikel <mikel@test.lindsaar.net>'
410
+ # mail.bcc #=> ['mikel@test.lindsaar.net']
411
+ # mail.bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
412
+ # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
413
+ def bcc=( val )
414
+ header[:bcc] = val
415
+ end
416
+
417
+ # Returns the Cc value of the mail object as an array of strings of
418
+ # address specs.
419
+ #
420
+ # Example:
421
+ #
422
+ # mail.cc = 'Mikel <mikel@test.lindsaar.net>'
423
+ # mail.cc #=> ['mikel@test.lindsaar.net']
424
+ # mail.cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
425
+ # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
426
+ #
427
+ # Also allows you to set the value by passing a value as a parameter
428
+ #
429
+ # Example:
430
+ #
431
+ # mail.cc 'Mikel <mikel@test.lindsaar.net>'
432
+ # mail.cc #=> ['mikel@test.lindsaar.net']
433
+ #
434
+ # Additionally, you can append new addresses to the returned Array like
435
+ # object.
436
+ #
437
+ # Example:
438
+ #
439
+ # mail.cc 'Mikel <mikel@test.lindsaar.net>'
440
+ # mail.cc << 'ada@test.lindsaar.net'
441
+ # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
442
+ def cc( val = nil )
443
+ default :cc, val
444
+ end
445
+
446
+ # Sets the Cc value of the mail object, pass in a string of the field
447
+ #
448
+ # Example:
449
+ #
450
+ # mail.cc = 'Mikel <mikel@test.lindsaar.net>'
451
+ # mail.cc #=> ['mikel@test.lindsaar.net']
452
+ # mail.cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
453
+ # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
454
+ def cc=( val )
455
+ header[:cc] = val
456
+ end
457
+
458
+ def comments( val = nil )
459
+ default :comments, val
460
+ end
461
+
462
+ def comments=( val )
463
+ header[:comments] = val
464
+ end
465
+
466
+ def content_description( val = nil )
467
+ default :content_description, val
468
+ end
469
+
470
+ def content_description=( val )
471
+ header[:content_description] = val
472
+ end
473
+
474
+ def content_disposition( val = nil )
475
+ default :content_disposition, val
476
+ end
477
+
478
+ def content_disposition=( val )
479
+ header[:content_disposition] = val
480
+ end
481
+
482
+ def content_id( val = nil )
483
+ default :content_id, val
484
+ end
485
+
486
+ def content_id=( val )
487
+ header[:content_id] = val
488
+ end
489
+
490
+ def content_location( val = nil )
491
+ default :content_location, val
492
+ end
493
+
494
+ def content_location=( val )
495
+ header[:content_location] = val
496
+ end
497
+
498
+ def content_transfer_encoding( val = nil )
499
+ default :content_transfer_encoding, val
500
+ end
501
+
502
+ def content_transfer_encoding=( val )
503
+ header[:content_transfer_encoding] = val
504
+ end
505
+
506
+ def content_type( val = nil )
507
+ default :content_type, val
508
+ end
509
+
510
+ def content_type=( val )
511
+ header[:content_type] = val
512
+ end
513
+
514
+ def date( val = nil )
515
+ default :date, val
516
+ end
517
+
518
+ def date=( val )
519
+ header[:date] = val
520
+ end
521
+
522
+ # Returns the From value of the mail object as an array of strings of
523
+ # address specs.
524
+ #
525
+ # Example:
526
+ #
527
+ # mail.from = 'Mikel <mikel@test.lindsaar.net>'
528
+ # mail.from #=> ['mikel@test.lindsaar.net']
529
+ # mail.from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
530
+ # mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
531
+ #
532
+ # Also allows you to set the value by passing a value as a parameter
533
+ #
534
+ # Example:
535
+ #
536
+ # mail.from 'Mikel <mikel@test.lindsaar.net>'
537
+ # mail.from #=> ['mikel@test.lindsaar.net']
538
+ #
539
+ # Additionally, you can append new addresses to the returned Array like
540
+ # object.
541
+ #
542
+ # Example:
543
+ #
544
+ # mail.from 'Mikel <mikel@test.lindsaar.net>'
545
+ # mail.from << 'ada@test.lindsaar.net'
546
+ # mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
547
+ def from( val = nil )
548
+ default :from, val
549
+ end
550
+
551
+ # Sets the From value of the mail object, pass in a string of the field
552
+ #
553
+ # Example:
554
+ #
555
+ # mail.from = 'Mikel <mikel@test.lindsaar.net>'
556
+ # mail.from #=> ['mikel@test.lindsaar.net']
557
+ # mail.from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
558
+ # mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
559
+ def from=( val )
560
+ header[:from] = val
561
+ end
562
+
563
+ def in_reply_to( val = nil )
564
+ default :in_reply_to, val
565
+ end
566
+
567
+ def in_reply_to=( val )
568
+ header[:in_reply_to] = val
569
+ end
570
+
571
+ def keywords( val = nil )
572
+ default :keywords, val
573
+ end
574
+
575
+ def keywords=( val )
576
+ header[:keywords] = val
577
+ end
578
+
579
+ # Returns the Message-ID of the mail object. Note, per RFC 2822 the Message ID
580
+ # consists of what is INSIDE the < > usually seen in the mail header, so this method
581
+ # will return only what is inside.
582
+ #
583
+ # Example:
584
+ #
585
+ # mail.message_id = '<1234@message.id>'
586
+ # mail.message_id #=> '1234@message.id'
587
+ #
588
+ # Also allows you to set the Message-ID by passing a string as a parameter
589
+ #
590
+ # mail.message_id '<1234@message.id>'
591
+ # mail.message_id #=> '1234@message.id'
592
+ def message_id( val = nil )
593
+ default :message_id, val
594
+ end
595
+
596
+ # Sets the Message-ID. Note, per RFC 2822 the Message ID consists of what is INSIDE
597
+ # the < > usually seen in the mail header, so this method will return only what is inside.
598
+ #
599
+ # mail.message_id = '<1234@message.id>'
600
+ # mail.message_id #=> '1234@message.id'
601
+ def message_id=( val )
602
+ header[:message_id] = val
603
+ end
604
+
605
+ # Returns the mime version of the email as a string
606
+ #
607
+ # Example:
608
+ #
609
+ # mail.mime_version = '1.0'
610
+ # mail.mime_version #=> '1.0'
611
+ #
612
+ # Also allows you to set the mime version by passing a string as a parameter.
613
+ #
614
+ # Example:
615
+ #
616
+ # mail.mime_version '1.0'
617
+ # mail.mime_version #=> '1.0'
618
+ def mime_version( val = nil )
619
+ default :mime_version, val
620
+ end
621
+
622
+ # Sets the mime version of the email by accepting a string
623
+ #
624
+ # Example:
625
+ #
626
+ # mail.mime_version = '1.0'
627
+ # mail.mime_version #=> '1.0'
628
+ def mime_version=( val )
629
+ header[:mime_version] = val
630
+ end
631
+
632
+ def received( val = nil )
633
+ if val
634
+ header[:received] = val
635
+ else
636
+ header[:received]
637
+ end
638
+ end
639
+
640
+ def received=( val )
641
+ header[:received] = val
642
+ end
643
+
644
+ def references( val = nil )
645
+ default :references, val
646
+ end
647
+
648
+ def references=( val )
649
+ header[:references] = val
650
+ end
651
+
652
+ # Returns the Reply-To value of the mail object as an array of strings of
653
+ # address specs.
654
+ #
655
+ # Example:
656
+ #
657
+ # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>'
658
+ # mail.reply_to #=> ['mikel@test.lindsaar.net']
659
+ # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
660
+ # mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
661
+ #
662
+ # Also allows you to set the value by passing a value as a parameter
663
+ #
664
+ # Example:
665
+ #
666
+ # mail.reply_to 'Mikel <mikel@test.lindsaar.net>'
667
+ # mail.reply_to #=> ['mikel@test.lindsaar.net']
668
+ #
669
+ # Additionally, you can append new addresses to the returned Array like
670
+ # object.
671
+ #
672
+ # Example:
673
+ #
674
+ # mail.reply_to 'Mikel <mikel@test.lindsaar.net>'
675
+ # mail.reply_to << 'ada@test.lindsaar.net'
676
+ # mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
677
+ def reply_to( val = nil )
678
+ default :reply_to, val
679
+ end
680
+
681
+ # Sets the Reply-To value of the mail object, pass in a string of the field
682
+ #
683
+ # Example:
684
+ #
685
+ # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>'
686
+ # mail.reply_to #=> ['mikel@test.lindsaar.net']
687
+ # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
688
+ # mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
689
+ def reply_to=( val )
690
+ header[:reply_to] = val
691
+ end
692
+
693
+ # Returns the Resent-Bcc value of the mail object as an array of strings of
694
+ # address specs.
695
+ #
696
+ # Example:
697
+ #
698
+ # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>'
699
+ # mail.resent_bcc #=> ['mikel@test.lindsaar.net']
700
+ # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
701
+ # mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
702
+ #
703
+ # Also allows you to set the value by passing a value as a parameter
704
+ #
705
+ # Example:
706
+ #
707
+ # mail.resent_bcc 'Mikel <mikel@test.lindsaar.net>'
708
+ # mail.resent_bcc #=> ['mikel@test.lindsaar.net']
709
+ #
710
+ # Additionally, you can append new addresses to the returned Array like
711
+ # object.
712
+ #
713
+ # Example:
714
+ #
715
+ # mail.resent_bcc 'Mikel <mikel@test.lindsaar.net>'
716
+ # mail.resent_bcc << 'ada@test.lindsaar.net'
717
+ # mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
718
+ def resent_bcc( val = nil )
719
+ default :resent_bcc, val
720
+ end
721
+
722
+ # Sets the Resent-Bcc value of the mail object, pass in a string of the field
723
+ #
724
+ # Example:
725
+ #
726
+ # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>'
727
+ # mail.resent_bcc #=> ['mikel@test.lindsaar.net']
728
+ # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
729
+ # mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
730
+ def resent_bcc=( val )
731
+ header[:resent_bcc] = val
732
+ end
733
+
734
+ # Returns the Resent-Cc value of the mail object as an array of strings of
735
+ # address specs.
736
+ #
737
+ # Example:
738
+ #
739
+ # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>'
740
+ # mail.resent_cc #=> ['mikel@test.lindsaar.net']
741
+ # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
742
+ # mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
743
+ #
744
+ # Also allows you to set the value by passing a value as a parameter
745
+ #
746
+ # Example:
747
+ #
748
+ # mail.resent_cc 'Mikel <mikel@test.lindsaar.net>'
749
+ # mail.resent_cc #=> ['mikel@test.lindsaar.net']
750
+ #
751
+ # Additionally, you can append new addresses to the returned Array like
752
+ # object.
753
+ #
754
+ # Example:
755
+ #
756
+ # mail.resent_cc 'Mikel <mikel@test.lindsaar.net>'
757
+ # mail.resent_cc << 'ada@test.lindsaar.net'
758
+ # mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
759
+ def resent_cc( val = nil )
760
+ default :resent_cc, val
761
+ end
762
+
763
+ # Sets the Resent-Cc value of the mail object, pass in a string of the field
764
+ #
765
+ # Example:
766
+ #
767
+ # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>'
768
+ # mail.resent_cc #=> ['mikel@test.lindsaar.net']
769
+ # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
770
+ # mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
771
+ def resent_cc=( val )
772
+ header[:resent_cc] = val
773
+ end
774
+
775
+ def resent_date( val = nil )
776
+ default :resent_date, val
777
+ end
778
+
779
+ def resent_date=( val )
780
+ header[:resent_date] = val
781
+ end
782
+
783
+ # Returns the Resent-From value of the mail object as an array of strings of
784
+ # address specs.
785
+ #
786
+ # Example:
787
+ #
788
+ # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>'
789
+ # mail.resent_from #=> ['mikel@test.lindsaar.net']
790
+ # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
791
+ # mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
792
+ #
793
+ # Also allows you to set the value by passing a value as a parameter
794
+ #
795
+ # Example:
796
+ #
797
+ # mail.resent_from ['Mikel <mikel@test.lindsaar.net>']
798
+ # mail.resent_from #=> 'mikel@test.lindsaar.net'
799
+ #
800
+ # Additionally, you can append new addresses to the returned Array like
801
+ # object.
802
+ #
803
+ # Example:
804
+ #
805
+ # mail.resent_from 'Mikel <mikel@test.lindsaar.net>'
806
+ # mail.resent_from << 'ada@test.lindsaar.net'
807
+ # mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
808
+ def resent_from( val = nil )
809
+ default :resent_from, val
810
+ end
811
+
812
+ # Sets the Resent-From value of the mail object, pass in a string of the field
813
+ #
814
+ # Example:
815
+ #
816
+ # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>'
817
+ # mail.resent_from #=> ['mikel@test.lindsaar.net']
818
+ # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
819
+ # mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
820
+ def resent_from=( val )
821
+ header[:resent_from] = val
822
+ end
823
+
824
+ def resent_message_id( val = nil )
825
+ default :resent_message_id, val
826
+ end
827
+
828
+ def resent_message_id=( val )
829
+ header[:resent_message_id] = val
830
+ end
831
+
832
+ # Returns the Resent-Sender value of the mail object, as a single string of an address
833
+ # spec. A sender per RFC 2822 must be a single address, so you can not append to
834
+ # this address.
835
+ #
836
+ # Example:
837
+ #
838
+ # mail.resent_sender = 'Mikel <mikel@test.lindsaar.net>'
839
+ # mail.resent_sender #=> 'mikel@test.lindsaar.net'
840
+ #
841
+ # Also allows you to set the value by passing a value as a parameter
842
+ #
843
+ # Example:
844
+ #
845
+ # mail.resent_sender 'Mikel <mikel@test.lindsaar.net>'
846
+ # mail.resent_sender #=> 'mikel@test.lindsaar.net'
847
+ def resent_sender( val = nil )
848
+ default :resent_sender, val
849
+ end
850
+
851
+ # Sets the Resent-Sender value of the mail object, pass in a string of the field
852
+ #
853
+ # Example:
854
+ #
855
+ # mail.sender = 'Mikel <mikel@test.lindsaar.net>'
856
+ # mail.sender #=> 'mikel@test.lindsaar.net'
857
+ def resent_sender=( val )
858
+ header[:resent_sender] = val
859
+ end
860
+
861
+ # Returns the Resent-To value of the mail object as an array of strings of
862
+ # address specs.
863
+ #
864
+ # Example:
865
+ #
866
+ # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>'
867
+ # mail.resent_to #=> ['mikel@test.lindsaar.net']
868
+ # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
869
+ # mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
870
+ #
871
+ # Also allows you to set the value by passing a value as a parameter
872
+ #
873
+ # Example:
874
+ #
875
+ # mail.resent_to 'Mikel <mikel@test.lindsaar.net>'
876
+ # mail.resent_to #=> ['mikel@test.lindsaar.net']
877
+ #
878
+ # Additionally, you can append new addresses to the returned Array like
879
+ # object.
880
+ #
881
+ # Example:
882
+ #
883
+ # mail.resent_to 'Mikel <mikel@test.lindsaar.net>'
884
+ # mail.resent_to << 'ada@test.lindsaar.net'
885
+ # mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
886
+ def resent_to( val = nil )
887
+ default :resent_to, val
888
+ end
889
+
890
+ # Sets the Resent-To value of the mail object, pass in a string of the field
891
+ #
892
+ # Example:
893
+ #
894
+ # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>'
895
+ # mail.resent_to #=> ['mikel@test.lindsaar.net']
896
+ # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
897
+ # mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
898
+ def resent_to=( val )
899
+ header[:resent_to] = val
900
+ end
901
+
902
+ # Returns the return path of the mail object, or sets it if you pass a string
903
+ def return_path( val = nil )
904
+ default :return_path, val
905
+ end
906
+
907
+ # Sets the return path of the object
908
+ def return_path=( val )
909
+ header[:return_path] = val
910
+ end
911
+
912
+ # Returns the Sender value of the mail object, as a single string of an address
913
+ # spec. A sender per RFC 2822 must be a single address.
914
+ #
915
+ # Example:
916
+ #
917
+ # mail.sender = 'Mikel <mikel@test.lindsaar.net>'
918
+ # mail.sender #=> 'mikel@test.lindsaar.net'
919
+ #
920
+ # Also allows you to set the value by passing a value as a parameter
921
+ #
922
+ # Example:
923
+ #
924
+ # mail.sender 'Mikel <mikel@test.lindsaar.net>'
925
+ # mail.sender #=> 'mikel@test.lindsaar.net'
926
+ def sender( val = nil )
927
+ default :sender, val
928
+ end
929
+
930
+ # Sets the Sender value of the mail object, pass in a string of the field
931
+ #
932
+ # Example:
933
+ #
934
+ # mail.sender = 'Mikel <mikel@test.lindsaar.net>'
935
+ # mail.sender #=> 'mikel@test.lindsaar.net'
936
+ def sender=( val )
937
+ header[:sender] = val
938
+ end
939
+
940
+ # Returns the decoded value of the subject field, as a single string.
941
+ #
942
+ # Example:
943
+ #
944
+ # mail.subject = "G'Day mate"
945
+ # mail.subject #=> "G'Day mate"
946
+ # mail.subject = '=?UTF-8?Q?This_is_=E3=81=82_string?='
947
+ # mail.subject #=> "This is あ string"
948
+ #
949
+ # Also allows you to set the value by passing a value as a parameter
950
+ #
951
+ # Example:
952
+ #
953
+ # mail.subject "G'Day mate"
954
+ # mail.subject #=> "G'Day mate"
955
+ def subject( val = nil )
956
+ default :subject, val
957
+ end
958
+
959
+ # Sets the Subject value of the mail object, pass in a string of the field
960
+ #
961
+ # Example:
962
+ #
963
+ # mail.subject = '=?UTF-8?Q?This_is_=E3=81=82_string?='
964
+ # mail.subject #=> "This is あ string"
965
+ def subject=( val )
966
+ header[:subject] = val
967
+ end
968
+
969
+ # Returns the To value of the mail object as an array of strings of
970
+ # address specs.
971
+ #
972
+ # Example:
973
+ #
974
+ # mail.to = 'Mikel <mikel@test.lindsaar.net>'
975
+ # mail.to #=> ['mikel@test.lindsaar.net']
976
+ # mail.to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
977
+ # mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
978
+ #
979
+ # Also allows you to set the value by passing a value as a parameter
980
+ #
981
+ # Example:
982
+ #
983
+ # mail.to 'Mikel <mikel@test.lindsaar.net>'
984
+ # mail.to #=> ['mikel@test.lindsaar.net']
985
+ #
986
+ # Additionally, you can append new addresses to the returned Array like
987
+ # object.
988
+ #
989
+ # Example:
990
+ #
991
+ # mail.to 'Mikel <mikel@test.lindsaar.net>'
992
+ # mail.to << 'ada@test.lindsaar.net'
993
+ # mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
994
+ def to( val = nil )
995
+ default :to, val
996
+ end
997
+
998
+ # Sets the To value of the mail object, pass in a string of the field
999
+ #
1000
+ # Example:
1001
+ #
1002
+ # mail.to = 'Mikel <mikel@test.lindsaar.net>'
1003
+ # mail.to #=> ['mikel@test.lindsaar.net']
1004
+ # mail.to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
1005
+ # mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
1006
+ def to=( val )
1007
+ header[:to] = val
1008
+ end
1009
+
1010
+ # Returns the default value of the field requested as a symbol.
1011
+ #
1012
+ # Each header field has a :default method which returns the most common use case for
1013
+ # that field, for example, the date field types will return a DateTime object when
1014
+ # sent :default, the subject, or unstructured fields will return a decoded string of
1015
+ # their value, the address field types will return a single addr_spec or an array of
1016
+ # addr_specs if there is more than one.
1017
+ def default( sym, val = nil )
1018
+ if val
1019
+ header[sym] = val
1020
+ else
1021
+ header[sym].default if header[sym]
1022
+ end
1023
+ end
1024
+
1025
+ # Sets the body object of the message object.
1026
+ #
1027
+ # Example:
1028
+ #
1029
+ # mail.body = 'This is the body'
1030
+ # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo...
1031
+ #
1032
+ # You can also reset the body of an Message object by setting body to nil
1033
+ #
1034
+ # Example:
1035
+ #
1036
+ # mail.body = 'this is the body'
1037
+ # mail.body.encoded #=> 'this is the body'
1038
+ # mail.body = nil
1039
+ # mail.body.encoded #=> ''
1040
+ #
1041
+ # If you try and set the body of an email that is a multipart email, then instead
1042
+ # of deleting all the parts of your email, mail will add a text/plain part to
1043
+ # your email:
1044
+ #
1045
+ # mail.add_file 'somefilename.png'
1046
+ # mail.parts.length #=> 1
1047
+ # mail.body = "This is a body"
1048
+ # mail.parts.length #=> 2
1049
+ # mail.parts.last.content_type.content_type #=> 'This is a body'
1050
+ def body=(value)
1051
+ case
1052
+ when value == nil
1053
+ @body = Mail::Body.new('')
1054
+ when @body && !@body.parts.empty?
1055
+ @body << Mail::Part.new(value)
1056
+ else
1057
+ @body = Mail::Body.new(value)
1058
+ end
1059
+ add_encoding_to_body
1060
+ end
1061
+
1062
+ # Returns the body of the message object. Or, if passed
1063
+ # a parameter sets the value.
1064
+ #
1065
+ # Example:
1066
+ #
1067
+ # mail = Mail::Message.new('To: mikel\r\n\r\nThis is the body')
1068
+ # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo...
1069
+ #
1070
+ # mail.body 'This is another body'
1071
+ # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is anothe...
1072
+ def body(value = nil)
1073
+ if value
1074
+ self.body = value
1075
+ add_encoding_to_body
1076
+ else
1077
+ @body
1078
+ end
1079
+ end
1080
+
1081
+ # Returns the list of addresses this message should be sent to by
1082
+ # collecting the addresses off the to, cc and bcc fields.
1083
+ #
1084
+ # Example:
1085
+ #
1086
+ # mail.to = 'mikel@test.lindsaar.net'
1087
+ # mail.cc = 'sam@test.lindsaar.net'
1088
+ # mail.bcc = 'bob@test.lindsaar.net'
1089
+ # mail.destinations.length #=> 3
1090
+ # mail.destinations.first #=> 'mikel@test.lindsaar.net'
1091
+ def destinations
1092
+ [to_addrs, cc_addrs, bcc_addrs].compact.flatten
1093
+ end
1094
+
1095
+ # Returns an array of addresses (the encoded value) in the From field,
1096
+ # if no From field, returns an empty array
1097
+ def from_addrs
1098
+ from ? [from].flatten : []
1099
+ end
1100
+
1101
+ # Returns an array of addresses (the encoded value) in the To field,
1102
+ # if no To field, returns an empty array
1103
+ def to_addrs
1104
+ to ? [to].flatten : []
1105
+ end
1106
+
1107
+ # Returns an array of addresses (the encoded value) in the Cc field,
1108
+ # if no Cc field, returns an empty array
1109
+ def cc_addrs
1110
+ cc ? [cc].flatten : []
1111
+ end
1112
+
1113
+ # Returns an array of addresses (the encoded value) in the Bcc field,
1114
+ # if no Bcc field, returns an empty array
1115
+ def bcc_addrs
1116
+ bcc ? [bcc].flatten : []
1117
+ end
1118
+
1119
+ # Allows you to add an arbitrary header
1120
+ #
1121
+ # Example:
1122
+ #
1123
+ # mail['foo'] = '1234'
1124
+ # mail['foo'].to_s #=> '1234'
1125
+ def []=(name, value)
1126
+ if name.to_s == 'body'
1127
+ self.body = value
1128
+ elsif name.to_s =~ /content[-_]type/i
1129
+ header[underscoreize(name)] = value
1130
+ else
1131
+ header[underscoreize(name)] = value
1132
+ end
1133
+ end
1134
+
1135
+ # Allows you to read an arbitrary header
1136
+ #
1137
+ # Example:
1138
+ #
1139
+ # mail['foo'] = '1234'
1140
+ # mail['foo'].to_s #=> '1234'
1141
+ def [](name)
1142
+ header[underscoreize(name)]
1143
+ end
1144
+
1145
+ # Method Missing in this implementation allows you to set any of the
1146
+ # standard fields directly as you would the "to", "subject" etc.
1147
+ #
1148
+ # Those fields used most often (to, subject et al) are given their
1149
+ # own method for ease of documentation and also to avoid the hook
1150
+ # call to method missing.
1151
+ #
1152
+ # This will only catch the known fields listed in:
1153
+ #
1154
+ # Mail::Field::KNOWN_FIELDS
1155
+ #
1156
+ # as per RFC 2822, any ruby string or method name could pretty much
1157
+ # be a field name, so we don't want to just catch ANYTHING sent to
1158
+ # a message object and interpret it as a header.
1159
+ #
1160
+ # This method provides all three types of header call to set, read
1161
+ # and explicitly set with the = operator
1162
+ #
1163
+ # Examples:
1164
+ #
1165
+ # mail.comments = 'These are some comments'
1166
+ # mail.comments #=> 'These are some comments'
1167
+ #
1168
+ # mail.comments 'These are other comments'
1169
+ # mail.comments #=> 'These are other comments'
1170
+ #
1171
+ #
1172
+ # mail.date = 'Tue, 1 Jul 2003 10:52:37 +0200'
1173
+ # mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200'
1174
+ #
1175
+ # mail.date 'Tue, 1 Jul 2003 10:52:37 +0200'
1176
+ # mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200'
1177
+ #
1178
+ #
1179
+ # mail.resent_msg_id = '<1234@resent_msg_id.lindsaar.net>'
1180
+ # mail.resent_msg_id #=> '<1234@resent_msg_id.lindsaar.net>'
1181
+ #
1182
+ # mail.resent_msg_id '<4567@resent_msg_id.lindsaar.net>'
1183
+ # mail.resent_msg_id #=> '<4567@resent_msg_id.lindsaar.net>'
1184
+ def method_missing(name, *args, &block)
1185
+ #:nodoc:
1186
+ # Only take the structured fields, as we could take _anything_ really
1187
+ # as it could become an optional field... "but therin lies the dark side"
1188
+ field_name = underscoreize(name).chomp("=")
1189
+ if Mail::Field::KNOWN_FIELDS.include?(field_name)
1190
+ if args.empty?
1191
+ header[field_name]
1192
+ else
1193
+ header[field_name] = args.first
1194
+ end
1195
+ else
1196
+ super # otherwise pass it on
1197
+ end
1198
+ #:startdoc:
1199
+ end
1200
+
1201
+ # Returns an FieldList of all the fields in the header in the order that
1202
+ # they appear in the header
1203
+ def header_fields
1204
+ header.fields
1205
+ end
1206
+
1207
+ # Returns true if the message has a message ID field, the field may or may
1208
+ # not have a value, but the field exists or not.
1209
+ def has_message_id?
1210
+ header.has_message_id?
1211
+ end
1212
+
1213
+ # Returns true if the message has a Date field, the field may or may
1214
+ # not have a value, but the field exists or not.
1215
+ def has_date?
1216
+ header.has_date?
1217
+ end
1218
+
1219
+ # Returns true if the message has a Date field, the field may or may
1220
+ # not have a value, but the field exists or not.
1221
+ def has_mime_version?
1222
+ header.has_mime_version?
1223
+ end
1224
+
1225
+ def has_content_type?
1226
+ !!header[:content_type]
1227
+ end
1228
+
1229
+ def has_charset?
1230
+ !!charset
1231
+ end
1232
+
1233
+ def has_content_transfer_encoding?
1234
+ !!content_transfer_encoding
1235
+ end
1236
+
1237
+ def has_transfer_encoding? # :nodoc:
1238
+ STDERR.puts(":has_transfer_encoding? is deprecated in Mail 1.4.3. Please use has_content_transfer_encoding?\n#{caller}")
1239
+ has_content_transfer_encoding?
1240
+ end
1241
+
1242
+ # Creates a new empty Message-ID field and inserts it in the correct order
1243
+ # into the Header. The MessageIdField object will automatically generate
1244
+ # a unique message ID if you try and encode it or output it to_s without
1245
+ # specifying a message id.
1246
+ #
1247
+ # It will preserve the message ID you specify if you do.
1248
+ def add_message_id(msg_id_val = '')
1249
+ header['message-id'] = msg_id_val
1250
+ end
1251
+
1252
+ # Creates a new empty Date field and inserts it in the correct order
1253
+ # into the Header. The DateField object will automatically generate
1254
+ # DateTime.now's date if you try and encode it or output it to_s without
1255
+ # specifying a date yourself.
1256
+ #
1257
+ # It will preserve any date you specify if you do.
1258
+ def add_date(date_val = '')
1259
+ header['date'] = date_val
1260
+ end
1261
+
1262
+ # Creates a new empty Mime Version field and inserts it in the correct order
1263
+ # into the Header. The MimeVersion object will automatically generate
1264
+ # DateTime.now's date if you try and encode it or output it to_s without
1265
+ # specifying a date yourself.
1266
+ #
1267
+ # It will preserve any date you specify if you do.
1268
+ def add_mime_version(ver_val = '')
1269
+ header['mime-version'] = ver_val
1270
+ end
1271
+
1272
+ # Adds a content type and charset if the body is US-ASCII
1273
+ #
1274
+ # Otherwise raises a warning
1275
+ def add_content_type
1276
+ header[:content_type] = 'text/plain'
1277
+ end
1278
+
1279
+ # Adds a content type and charset if the body is US-ASCII
1280
+ #
1281
+ # Otherwise raises a warning
1282
+ def add_charset
1283
+ if body.only_us_ascii?
1284
+ header[:content_type].parameters['charset'] = 'US-ASCII'
1285
+ else
1286
+ warning = "Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.\n"
1287
+ STDERR.puts(warning)
1288
+ header[:content_type].parameters['charset'] = 'UTF-8'
1289
+ end
1290
+ end
1291
+
1292
+ # Adds a content transfer encoding
1293
+ #
1294
+ # Otherwise raises a warning
1295
+ def add_content_transfer_encoding
1296
+ if body.only_us_ascii?
1297
+ header[:content_transfer_encoding] = '7bit'
1298
+ else
1299
+ warning = "Non US-ASCII detected and no content-transfer-encoding defined.\nDefaulting to 8bit, set your own if this is incorrect.\n"
1300
+ STDERR.puts(warning)
1301
+ header[:content_transfer_encoding] = '8bit'
1302
+ end
1303
+ end
1304
+
1305
+ def add_transfer_encoding # :nodoc:
1306
+ STDERR.puts(":add_transfer_encoding is deprecated in Mail 1.4.3. Please use add_content_transfer_encoding\n#{caller}")
1307
+ add_content_transfer_encoding
1308
+ end
1309
+
1310
+ def transfer_encoding # :nodoc:
1311
+ STDERR.puts(":transfer_encoding is deprecated in Mail 1.4.3. Please use content_transfer_encoding\n#{caller}")
1312
+ content_transfer_encoding
1313
+ end
1314
+
1315
+ # Returns the mime type of part we are on, this is taken from the content-type header
1316
+ def mime_type
1317
+ content_type ? header[:content_type].string : nil
1318
+ end
1319
+
1320
+ def message_content_type
1321
+ STDERR.puts(":message_content_type is deprecated in Mail 1.4.3. Please use mime_type\n#{caller}")
1322
+ mime_type
1323
+ end
1324
+
1325
+ # Returns the character set defined in the content type field
1326
+ def charset
1327
+ content_type ? content_type_parameters['charset'] : nil
1328
+ end
1329
+
1330
+ # Sets the charset to the supplied value. Will set the content type to text/plain if
1331
+ # it does not already exist
1332
+ def charset=(value)
1333
+ if content_type
1334
+ content_type_parameters['charset'] = value
1335
+ else
1336
+ self.content_type ['text', 'plain', {'charset' => value}]
1337
+ end
1338
+ end
1339
+
1340
+ # Returns the main content type
1341
+ def main_type
1342
+ has_content_type? ? header[:content_type].main_type : nil
1343
+ end
1344
+
1345
+ # Returns the sub content type
1346
+ def sub_type
1347
+ has_content_type? ? header[:content_type].sub_type : nil
1348
+ end
1349
+
1350
+ # Returns the content type parameters
1351
+ def mime_parameters
1352
+ STDERR.puts(':mime_parameters is deprecated in Mail 1.4.3, please use :content_type_parameters instead')
1353
+ content_type_parameters
1354
+ end
1355
+
1356
+ # Returns the content type parameters
1357
+ def content_type_parameters
1358
+ has_content_type? ? header[:content_type].parameters : nil
1359
+ end
1360
+
1361
+ # Returns true if the message is multipart
1362
+ def multipart?
1363
+ has_content_type? ? !!(main_type =~ /^multipart$/i) : false
1364
+ end
1365
+
1366
+ # Returns true if the message is a multipart/report
1367
+ def multipart_report?
1368
+ multipart? && sub_type =~ /^report$/i
1369
+ end
1370
+
1371
+ # Returns true if the message is a multipart/report; report-type=delivery-status;
1372
+ def delivery_status_report?
1373
+ multipart_report? && content_type_parameters['report-type'] =~ /^delivery-status$/i
1374
+ end
1375
+
1376
+ # returns the part in a multipart/report email that has the content-type delivery-status
1377
+ def delivery_status_part
1378
+ @delivery_stats_part ||= parts.select { |p| p.delivery_status_report_part? }.first
1379
+ end
1380
+
1381
+ def bounced?
1382
+ delivery_status_part and delivery_status_part.bounced?
1383
+ end
1384
+
1385
+ def action
1386
+ delivery_status_part and delivery_status_part.action
1387
+ end
1388
+
1389
+ def final_recipient
1390
+ delivery_status_part and delivery_status_part.final_recipient
1391
+ end
1392
+
1393
+ def error_status
1394
+ delivery_status_part and delivery_status_part.error_status
1395
+ end
1396
+
1397
+ def diagnostic_code
1398
+ delivery_status_part and delivery_status_part.diagnostic_code
1399
+ end
1400
+
1401
+ def remote_mta
1402
+ delivery_status_part and delivery_status_part.remote_mta
1403
+ end
1404
+
1405
+ def retryable?
1406
+ delivery_status_part and delivery_status_part.retryable?
1407
+ end
1408
+
1409
+ # Returns the current boundary for this message part
1410
+ def boundary
1411
+ content_type_parameters ? content_type_parameters['boundary'] : nil
1412
+ end
1413
+
1414
+ # Returns a parts list object of all the parts in the message
1415
+ def parts
1416
+ body.parts
1417
+ end
1418
+
1419
+ # Returns an AttachmentsList object, which holds all of the attachments in
1420
+ # the receiver object (either the entier email or a part within) and all
1421
+ # of it's descendants.
1422
+ #
1423
+ # It also allows you to add attachments to the mail object directly, like so:
1424
+ #
1425
+ # mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
1426
+ #
1427
+ # If you do this, then Mail will take the file name and work out the mime type
1428
+ # set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
1429
+ # base64 encode the contents of the attachment all for you.
1430
+ #
1431
+ # You can also specify overrides if you want by passing a hash instead of a string:
1432
+ #
1433
+ # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
1434
+ # :content => File.read('/path/to/filename.jpg')}
1435
+ #
1436
+ # If you want to use a different encoding than Base64, you can pass an encoding in,
1437
+ # but then it is up to you to pass in the content pre-encoded, and don't expect
1438
+ # Mail to know how to decode this data:
1439
+ #
1440
+ # file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
1441
+ # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
1442
+ # :encoding => 'SpecialEncoding',
1443
+ # :content => file_content }
1444
+ #
1445
+ # You can also search for specific attachments:
1446
+ #
1447
+ # # By Filename
1448
+ # mail.attachments['filename.jpg'] #=> Mail::Part object or nil
1449
+ #
1450
+ # # or by index
1451
+ # mail.attachments[0] #=> Mail::Part (first attachment)
1452
+ #
1453
+ def attachments
1454
+ parts.attachments
1455
+ end
1456
+
1457
+ def has_attachments?
1458
+ !attachments.empty?
1459
+ end
1460
+
1461
+ # Accessor for html_part
1462
+ def html_part(&block)
1463
+ if block_given?
1464
+ @html_part = Mail::Part.new(&block)
1465
+ add_multipart_alternate_header unless html_part.blank?
1466
+ add_part(@html_part)
1467
+ else
1468
+ @html_part || find_first_mime_type('text/html')
1469
+ end
1470
+ end
1471
+
1472
+ # Accessor for text_part
1473
+ def text_part(&block)
1474
+ if block_given?
1475
+ @text_part = Mail::Part.new(&block)
1476
+ add_multipart_alternate_header unless html_part.blank?
1477
+ add_part(@text_part)
1478
+ else
1479
+ @text_part || find_first_mime_type('text/plain')
1480
+ end
1481
+ end
1482
+
1483
+ # Helper to add a html part to a multipart/alternative email. If this and
1484
+ # text_part are both defined in a message, then it will be a multipart/alternative
1485
+ # message and set itself that way.
1486
+ def html_part=(msg = nil)
1487
+ if msg
1488
+ @html_part = msg
1489
+ else
1490
+ @html_part = Mail::Part.new('Content-Type: text/html;')
1491
+ end
1492
+ add_multipart_alternate_header unless text_part.blank?
1493
+ add_part(@html_part)
1494
+ end
1495
+
1496
+ # Helper to add a text part to a multipart/alternative email. If this and
1497
+ # html_part are both defined in a message, then it will be a multipart/alternative
1498
+ # message and set itself that way.
1499
+ def text_part=(msg = nil)
1500
+ if msg
1501
+ @text_part = msg
1502
+ else
1503
+ @text_part = Mail::Part.new('Content-Type: text/plain;')
1504
+ end
1505
+ add_multipart_alternate_header unless html_part.blank?
1506
+ add_part(@text_part)
1507
+ end
1508
+
1509
+ # Adds a part to the parts list or creates the part list
1510
+ def add_part(part)
1511
+ if body.parts.empty? && !self.body.decoded.blank?
1512
+ @text_part = Mail::Part.new('Content-Type: text/plain;')
1513
+ @text_part.body = body.decoded
1514
+ self.body << @text_part
1515
+ add_multipart_alternate_header
1516
+ end
1517
+ add_boundary
1518
+ self.body << part
1519
+ end
1520
+
1521
+ # Allows you to add a part in block form to an existing mail message object
1522
+ #
1523
+ # Example:
1524
+ #
1525
+ # mail = Mail.new do
1526
+ # part :content_type => "multipart/alternative", :content_disposition => "inline" do |p|
1527
+ # p.part :content_type => "text/plain", :body => "test text\nline #2"
1528
+ # p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2"
1529
+ # end
1530
+ # end
1531
+ def part(params = {})
1532
+ new_part = Part.new(params)
1533
+ yield new_part if block_given?
1534
+ add_part(new_part)
1535
+ end
1536
+
1537
+ # Adds a file to the message. You have two options with this method, you can
1538
+ # just pass in the absolute path to the file you want and Mail will read the file,
1539
+ # get the filename from the path you pass in and guess the mime type, or you
1540
+ # can pass in the filename as a string, and pass in the file content as a blob.
1541
+ #
1542
+ # Example:
1543
+ #
1544
+ # m = Mail.new
1545
+ # m.add_file('/path/to/filename.png')
1546
+ #
1547
+ # m = Mail.new
1548
+ # m.add_file(:filename => 'filename.png', :content => File.read('/path/to/file.jpg'))
1549
+ #
1550
+ # Note also that if you add a file to an existing message, Mail will convert that message
1551
+ # to a MIME multipart email, moving whatever plain text body you had into it's own text
1552
+ # plain part.
1553
+ #
1554
+ # Example:
1555
+ #
1556
+ # m = Mail.new do
1557
+ # body 'this is some text'
1558
+ # end
1559
+ # m.multipart? #=> false
1560
+ # m.add_file('/path/to/filename.png')
1561
+ # m.multipart? #=> true
1562
+ # m.parts.first.content_type.content_type #=> 'text/plain'
1563
+ # m.parts.last.content_type.content_type #=> 'image/png'
1564
+ #
1565
+ # See also #attachments
1566
+ def add_file(values)
1567
+ convert_to_multipart unless self.multipart? || self.body.decoded.blank?
1568
+ add_multipart_mixed_header
1569
+ if values.is_a?(String)
1570
+ basename = File.basename(values)
1571
+ filedata = File.read(values)
1572
+ else
1573
+ basename = values[:filename]
1574
+ filedata = values[:content] || File.read(values[:filename])
1575
+ end
1576
+ self.attachments[basename] = filedata
1577
+ end
1578
+
1579
+ def convert_to_multipart
1580
+ text = @body.decoded
1581
+ self.body = ''
1582
+ text_part = Mail::Part.new({:content_type => 'text/plain;',
1583
+ :body => text})
1584
+ self.body << text_part
1585
+ end
1586
+
1587
+ # Encodes the message, calls encode on all it's parts, gets an email message
1588
+ # ready to send
1589
+ def ready_to_send!
1590
+ parts.each { |part| part.ready_to_send! }
1591
+ add_required_fields
1592
+ end
1593
+
1594
+ def encode!
1595
+ STDERR.puts("Deprecated in 1.1.0 in favour of :ready_to_send! as it is less confusing with encoding and decoding.")
1596
+ ready_to_send!
1597
+ end
1598
+
1599
+ # Outputs an encoded string representation of the mail message including
1600
+ # all headers, attachments, etc. This is an encoded email in US-ASCII,
1601
+ # so it is able to be directly sent to an email server.
1602
+ def encoded
1603
+ ready_to_send!
1604
+ buffer = header.encoded
1605
+ buffer << "\r\n"
1606
+ buffer << body.encoded
1607
+ buffer
1608
+ end
1609
+
1610
+ def to_s
1611
+ encoded
1612
+ end
1613
+
1614
+ def decoded
1615
+ if self.attachment?
1616
+ decode_body
1617
+ else
1618
+ 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.'
1619
+ end
1620
+ end
1621
+
1622
+ def read
1623
+ if self.attachment?
1624
+ decode_body
1625
+ else
1626
+ raise NoMethodError, 'Can not call read on a part unless it is an attachment.'
1627
+ end
1628
+ end
1629
+
1630
+ def decode_body
1631
+ if Mail::Encodings.defined?(content_transfer_encoding)
1632
+ Mail::Encodings.get_encoding(content_transfer_encoding).decode(body.encoded)
1633
+ else
1634
+ raise UnknownEncodingType, "Don't know how to decode #{content_transfer_encoding}, please call #encoded and decode it yourself."
1635
+ end
1636
+ end
1637
+
1638
+ # Returns true if this part is an attachment
1639
+ def attachment?
1640
+ find_attachment
1641
+ end
1642
+
1643
+ # Returns the attachment data if there is any
1644
+ def attachment
1645
+ @attachment
1646
+ end
1647
+
1648
+ # Returns the filename of the attachment
1649
+ def filename
1650
+ find_attachment
1651
+ end
1652
+
1653
+ def all_parts
1654
+ parts.map { |p| [p, p.all_parts] }.flatten
1655
+ end
1656
+
1657
+ def find_first_mime_type(mt)
1658
+ all_parts.detect { |p| p.mime_type == mt }
1659
+ end
1660
+
1661
+ private
1662
+
1663
+ # 2.1. General Description
1664
+ # A message consists of header fields (collectively called "the header
1665
+ # of the message") followed, optionally, by a body. The header is a
1666
+ # sequence of lines of characters with special syntax as defined in
1667
+ # this standard. The body is simply a sequence of characters that
1668
+ # follows the header and is separated from the header by an empty line
1669
+ # (i.e., a line with nothing preceding the CRLF).
1670
+ #
1671
+ # Additionally, I allow for the case where someone might have put whitespace
1672
+ # on the "gap line"
1673
+ def parse_message
1674
+ header_part, body_part = raw_source.split(/#{CRLF}#{WSP}*#{CRLF}/m, 2)
1675
+ self.header = header_part
1676
+ self.body = body_part
1677
+ end
1678
+
1679
+ def raw_source=(value)
1680
+ @raw_source = value.to_crlf
1681
+ end
1682
+
1683
+ def set_envelope_header
1684
+ if match_data = raw_source.to_s.match(/\AFrom\s(#{TEXT}+)#{CRLF}(.*)/m)
1685
+ set_envelope(match_data[1])
1686
+ self.raw_source = match_data[2]
1687
+ end
1688
+ end
1689
+
1690
+ def separate_parts
1691
+ body.split!(boundary)
1692
+ end
1693
+
1694
+ def add_encoding_to_body
1695
+ unless content_transfer_encoding.blank?
1696
+ body.encoding = content_transfer_encoding
1697
+ end
1698
+ end
1699
+
1700
+ def add_required_fields
1701
+ add_multipart_mixed_header unless parts.empty?
1702
+ @body = Mail::Body.new('') if body.nil?
1703
+ add_message_id unless (has_message_id? || self.class == Mail::Part)
1704
+ add_date unless has_date?
1705
+ add_mime_version unless has_mime_version?
1706
+ add_content_type unless has_content_type?
1707
+ add_charset unless has_charset?
1708
+ add_content_transfer_encoding unless has_content_transfer_encoding?
1709
+ end
1710
+
1711
+ def add_multipart_alternate_header
1712
+ header['content-type'] = ContentTypeField.with_boundary('multipart/alternative').value
1713
+ body.boundary = boundary
1714
+ end
1715
+
1716
+ def add_boundary
1717
+ unless body.boundary && boundary
1718
+ header['content-type'] = 'multipart/mixed' unless header['content-type']
1719
+ header['content-type'].parameters[:boundary] = ContentTypeField.generate_boundary
1720
+ body.boundary = boundary
1721
+ end
1722
+ end
1723
+
1724
+ def add_multipart_mixed_header
1725
+ unless header['content-type']
1726
+ header['content-type'] = ContentTypeField.with_boundary('multipart/mixed').value
1727
+ body.boundary = boundary
1728
+ end
1729
+ end
1730
+
1731
+ def init_with_hash(hash)
1732
+ passed_in_options = hash.with_indifferent_access
1733
+ self.raw_source = ''
1734
+ @header = Mail::Header.new
1735
+ @body = Mail::Body.new
1736
+
1737
+ passed_in_options.each_pair do |k,v|
1738
+ k = underscoreize(k).to_sym if k.class == String
1739
+ if k == :headers
1740
+ self.headers(v)
1741
+ else
1742
+ self[k] = v
1743
+ end
1744
+ end
1745
+ end
1746
+
1747
+ def init_with_string(string)
1748
+ self.raw_source = string
1749
+ set_envelope_header
1750
+ parse_message
1751
+ separate_parts if multipart?
1752
+ end
1753
+
1754
+ # Returns the filename of the attachment (if it exists) or returns nil
1755
+ def find_attachment
1756
+ case
1757
+ when content_type && header[:content_type].filename
1758
+ filename = header[:content_type].filename
1759
+ when content_disposition && header[:content_disposition].filename
1760
+ filename = header[:content_disposition].filename
1761
+ when content_location && header[:content_location].location
1762
+ filename = header[:content_location].location
1763
+ else
1764
+ filename = nil
1765
+ end
1766
+ filename
1767
+ end
1768
+
1769
+ def do_delivery
1770
+ begin
1771
+ if perform_deliveries
1772
+ delivery_method.deliver!(self)
1773
+ end
1774
+ rescue Exception => e # Net::SMTP errors or sendmail pipe errors
1775
+ raise e if raise_delivery_errors
1776
+ end
1777
+ end
1778
+
1779
+ end
1780
+ end