mail 2.6.4 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +111 -118
  3. data/lib/mail/attachments_list.rb +11 -10
  4. data/lib/mail/body.rb +73 -84
  5. data/lib/mail/check_delivery_params.rb +54 -10
  6. data/lib/mail/configuration.rb +2 -0
  7. data/lib/mail/constants.rb +27 -5
  8. data/lib/mail/elements/address.rb +61 -50
  9. data/lib/mail/elements/address_list.rb +11 -19
  10. data/lib/mail/elements/content_disposition_element.rb +9 -16
  11. data/lib/mail/elements/content_location_element.rb +6 -11
  12. data/lib/mail/elements/content_transfer_encoding_element.rb +6 -11
  13. data/lib/mail/elements/content_type_element.rb +16 -23
  14. data/lib/mail/elements/date_time_element.rb +7 -15
  15. data/lib/mail/elements/envelope_from_element.rb +22 -23
  16. data/lib/mail/elements/message_ids_element.rb +18 -13
  17. data/lib/mail/elements/mime_version_element.rb +7 -15
  18. data/lib/mail/elements/phrase_list.rb +12 -10
  19. data/lib/mail/elements/received_element.rb +27 -19
  20. data/lib/mail/encodings/7bit.rb +9 -14
  21. data/lib/mail/encodings/8bit.rb +2 -21
  22. data/lib/mail/encodings/base64.rb +11 -12
  23. data/lib/mail/encodings/binary.rb +3 -22
  24. data/lib/mail/encodings/identity.rb +24 -0
  25. data/lib/mail/encodings/quoted_printable.rb +6 -6
  26. data/lib/mail/encodings/transfer_encoding.rb +38 -29
  27. data/lib/mail/encodings/unix_to_unix.rb +3 -1
  28. data/lib/mail/encodings.rb +81 -54
  29. data/lib/mail/envelope.rb +11 -14
  30. data/lib/mail/field.rb +119 -98
  31. data/lib/mail/field_list.rb +60 -7
  32. data/lib/mail/fields/bcc_field.rb +34 -52
  33. data/lib/mail/fields/cc_field.rb +28 -49
  34. data/lib/mail/fields/comments_field.rb +27 -37
  35. data/lib/mail/fields/common_address_field.rb +170 -0
  36. data/lib/mail/fields/common_date_field.rb +58 -0
  37. data/lib/mail/fields/common_field.rb +77 -0
  38. data/lib/mail/fields/common_message_id_field.rb +42 -0
  39. data/lib/mail/fields/content_description_field.rb +7 -14
  40. data/lib/mail/fields/content_disposition_field.rb +13 -38
  41. data/lib/mail/fields/content_id_field.rb +24 -51
  42. data/lib/mail/fields/content_location_field.rb +11 -25
  43. data/lib/mail/fields/content_transfer_encoding_field.rb +31 -31
  44. data/lib/mail/fields/content_type_field.rb +50 -80
  45. data/lib/mail/fields/date_field.rb +23 -52
  46. data/lib/mail/fields/from_field.rb +28 -49
  47. data/lib/mail/fields/in_reply_to_field.rb +38 -49
  48. data/lib/mail/fields/keywords_field.rb +18 -31
  49. data/lib/mail/fields/message_id_field.rb +25 -71
  50. data/lib/mail/fields/mime_version_field.rb +19 -30
  51. data/lib/mail/fields/named_structured_field.rb +11 -0
  52. data/lib/mail/fields/named_unstructured_field.rb +11 -0
  53. data/lib/mail/fields/optional_field.rb +9 -7
  54. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +13 -11
  55. data/lib/mail/fields/received_field.rb +43 -57
  56. data/lib/mail/fields/references_field.rb +35 -49
  57. data/lib/mail/fields/reply_to_field.rb +28 -49
  58. data/lib/mail/fields/resent_bcc_field.rb +28 -49
  59. data/lib/mail/fields/resent_cc_field.rb +28 -49
  60. data/lib/mail/fields/resent_date_field.rb +5 -30
  61. data/lib/mail/fields/resent_from_field.rb +28 -49
  62. data/lib/mail/fields/resent_message_id_field.rb +5 -29
  63. data/lib/mail/fields/resent_sender_field.rb +27 -56
  64. data/lib/mail/fields/resent_to_field.rb +28 -49
  65. data/lib/mail/fields/return_path_field.rb +50 -54
  66. data/lib/mail/fields/sender_field.rb +34 -55
  67. data/lib/mail/fields/structured_field.rb +3 -30
  68. data/lib/mail/fields/subject_field.rb +9 -11
  69. data/lib/mail/fields/to_field.rb +28 -49
  70. data/lib/mail/fields/unstructured_field.rb +32 -47
  71. data/lib/mail/header.rb +71 -110
  72. data/lib/mail/mail.rb +2 -10
  73. data/lib/mail/matchers/attachment_matchers.rb +15 -0
  74. data/lib/mail/matchers/has_sent_mail.rb +21 -1
  75. data/lib/mail/message.rb +113 -117
  76. data/lib/mail/multibyte/chars.rb +23 -180
  77. data/lib/mail/multibyte/unicode.rb +10 -10
  78. data/lib/mail/multibyte/utils.rb +26 -43
  79. data/lib/mail/multibyte.rb +55 -16
  80. data/lib/mail/network/delivery_methods/exim.rb +8 -11
  81. data/lib/mail/network/delivery_methods/file_delivery.rb +13 -16
  82. data/lib/mail/network/delivery_methods/logger_delivery.rb +34 -0
  83. data/lib/mail/network/delivery_methods/sendmail.rb +32 -35
  84. data/lib/mail/network/delivery_methods/smtp.rb +76 -54
  85. data/lib/mail/network/delivery_methods/smtp_connection.rb +4 -9
  86. data/lib/mail/network/delivery_methods/test_mailer.rb +8 -9
  87. data/lib/mail/network/retriever_methods/base.rb +8 -8
  88. data/lib/mail/network/retriever_methods/imap.rb +20 -7
  89. data/lib/mail/network/retriever_methods/pop3.rb +5 -3
  90. data/lib/mail/network/retriever_methods/test_retriever.rb +3 -2
  91. data/lib/mail/network.rb +1 -0
  92. data/lib/mail/parser_tools.rb +15 -0
  93. data/lib/mail/parsers/address_lists_parser.rb +33225 -116
  94. data/lib/mail/parsers/address_lists_parser.rl +179 -0
  95. data/lib/mail/parsers/content_disposition_parser.rb +882 -49
  96. data/lib/mail/parsers/content_disposition_parser.rl +89 -0
  97. data/lib/mail/parsers/content_location_parser.rb +809 -23
  98. data/lib/mail/parsers/content_location_parser.rl +78 -0
  99. data/lib/mail/parsers/content_transfer_encoding_parser.rb +509 -21
  100. data/lib/mail/parsers/content_transfer_encoding_parser.rl +71 -0
  101. data/lib/mail/parsers/content_type_parser.rb +1037 -56
  102. data/lib/mail/parsers/content_type_parser.rl +90 -0
  103. data/lib/mail/parsers/date_time_parser.rb +877 -25
  104. data/lib/mail/parsers/date_time_parser.rl +69 -0
  105. data/lib/mail/parsers/envelope_from_parser.rb +3669 -40
  106. data/lib/mail/parsers/envelope_from_parser.rl +89 -0
  107. data/lib/mail/parsers/message_ids_parser.rb +5146 -25
  108. data/lib/mail/parsers/message_ids_parser.rl +93 -0
  109. data/lib/mail/parsers/mime_version_parser.rb +497 -26
  110. data/lib/mail/parsers/mime_version_parser.rl +68 -0
  111. data/lib/mail/parsers/phrase_lists_parser.rb +870 -22
  112. data/lib/mail/parsers/phrase_lists_parser.rl +90 -0
  113. data/lib/mail/parsers/received_parser.rb +8776 -43
  114. data/lib/mail/parsers/received_parser.rl +91 -0
  115. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  116. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  117. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  118. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  119. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  120. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  121. data/lib/mail/parsers/rfc5322.rl +74 -0
  122. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  123. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  124. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  125. data/lib/mail/parsers.rb +11 -25
  126. data/lib/mail/part.rb +6 -10
  127. data/lib/mail/parts_list.rb +62 -6
  128. data/lib/mail/smtp_envelope.rb +57 -0
  129. data/lib/mail/utilities.rb +357 -74
  130. data/lib/mail/version.rb +2 -2
  131. data/lib/mail/yaml.rb +30 -0
  132. data/lib/mail.rb +5 -35
  133. metadata +111 -66
  134. data/CHANGELOG.rdoc +0 -787
  135. data/CONTRIBUTING.md +0 -60
  136. data/Dependencies.txt +0 -2
  137. data/Gemfile +0 -11
  138. data/Rakefile +0 -29
  139. data/TODO.rdoc +0 -9
  140. data/lib/mail/core_extensions/smtp.rb +0 -25
  141. data/lib/mail/core_extensions/string/access.rb +0 -146
  142. data/lib/mail/core_extensions/string/multibyte.rb +0 -79
  143. data/lib/mail/core_extensions/string.rb +0 -21
  144. data/lib/mail/fields/common/address_container.rb +0 -17
  145. data/lib/mail/fields/common/common_address.rb +0 -136
  146. data/lib/mail/fields/common/common_date.rb +0 -36
  147. data/lib/mail/fields/common/common_field.rb +0 -61
  148. data/lib/mail/fields/common/common_message_id.rb +0 -49
  149. data/lib/mail/multibyte/exceptions.rb +0 -9
  150. data/lib/mail/parsers/ragel/common.rl +0 -185
  151. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  152. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  153. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  154. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  155. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  156. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  157. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  158. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  159. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  160. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  161. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  162. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  163. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  164. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2149
  165. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  166. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  167. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  168. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  169. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  170. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  171. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  172. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  173. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  174. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  175. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  176. data/lib/mail/parsers/ragel/ruby.rb +0 -40
  177. data/lib/mail/parsers/ragel.rb +0 -18
  178. data/lib/mail/version_specific/ruby_1_8.rb +0 -126
  179. data/lib/mail/version_specific/ruby_1_9.rb +0 -223
data/lib/mail/body.rb CHANGED
@@ -32,24 +32,29 @@ module Mail
32
32
  @preamble = nil
33
33
  @epilogue = nil
34
34
  @charset = nil
35
- @part_sort_order = [ "text/plain", "text/enriched", "text/html" ]
35
+ @part_sort_order = [ "text/plain", "text/enriched", "text/html", "multipart/alternative" ]
36
36
  @parts = Mail::PartsList.new
37
37
  if Utilities.blank?(string)
38
38
  @raw_source = ''
39
39
  else
40
40
  # Do join first incase we have been given an Array in Ruby 1.9
41
41
  if string.respond_to?(:join)
42
- @raw_source = string.join('')
42
+ @raw_source = ::Mail::Utilities.to_crlf(string.join(''))
43
43
  elsif string.respond_to?(:to_s)
44
- @raw_source = string.to_s
44
+ @raw_source = ::Mail::Utilities.to_crlf(string.to_s)
45
45
  else
46
46
  raise "You can only assign a string or an object that responds_to? :join or :to_s to a body."
47
47
  end
48
48
  end
49
- @encoding = (only_us_ascii? ? '7bit' : '8bit')
49
+ @encoding = default_encoding
50
50
  set_charset
51
51
  end
52
-
52
+
53
+ def init_with(coder)
54
+ coder.map.each { |k, v| instance_variable_set(:"@#{k}", v) }
55
+ @parts = Mail::PartsList.new(coder['parts'])
56
+ end
57
+
53
58
  # Matches this body with another body. Also matches the decoded value of this
54
59
  # body with a string.
55
60
  #
@@ -115,8 +120,8 @@ module Mail
115
120
  end
116
121
 
117
122
  # Allows you to set the sort order of the parts, overriding the default sort order.
118
- # Defaults to 'text/plain', then 'text/enriched', then 'text/html' with any other content
119
- # type coming after.
123
+ # Defaults to 'text/plain', then 'text/enriched', then 'text/html', then 'multipart/alternative'
124
+ # with any other content type coming after.
120
125
  def set_sort_order(order)
121
126
  @part_sort_order = order
122
127
  end
@@ -133,46 +138,44 @@ module Mail
133
138
  @parts.sort!(@part_sort_order)
134
139
  end
135
140
 
136
- # Returns the raw source that the body was initialized with, without
137
- # any tampering
138
- def raw_source
139
- @raw_source
140
- end
141
-
142
- def get_best_encoding(target)
143
- target_encoding = Mail::Encodings.get_encoding(target)
144
- target_encoding.get_best_compatible(encoding, raw_source)
141
+ def negotiate_best_encoding(message_encoding, allowed_encodings = nil)
142
+ Mail::Encodings::TransferEncoding.negotiate(message_encoding, encoding, raw_source, allowed_encodings)
145
143
  end
146
-
144
+
147
145
  # Returns a body encoded using transfer_encoding. Multipart always uses an
148
146
  # identiy encoding (i.e. no encoding).
149
147
  # Calling this directly is not a good idea, but supported for compatibility
150
148
  # TODO: Validate that preamble and epilogue are valid for requested encoding
151
- def encoded(transfer_encoding = '8bit')
149
+ def encoded(transfer_encoding = nil)
152
150
  if multipart?
153
151
  self.sort_parts!
154
152
  encoded_parts = parts.map { |p| p.encoded }
155
153
  ([preamble] + encoded_parts).join(crlf_boundary) + end_boundary + epilogue.to_s
156
154
  else
157
- be = get_best_encoding(transfer_encoding)
158
- dec = Mail::Encodings::get_encoding(encoding)
159
- enc = Mail::Encodings::get_encoding(be)
155
+ dec = Mail::Encodings.get_encoding(encoding)
156
+ enc =
157
+ if Utilities.blank?(transfer_encoding)
158
+ dec
159
+ else
160
+ negotiate_best_encoding(transfer_encoding)
161
+ end
162
+
160
163
  if dec.nil?
161
- # Cannot decode, so skip normalization
162
- raw_source
164
+ # Cannot decode, so skip normalization
165
+ raw_source
163
166
  else
164
- # Decode then encode to normalize and allow transforming
165
- # from base64 to Q-P and vice versa
166
- decoded = dec.decode(raw_source)
167
- if defined?(Encoding) && charset && charset != "US-ASCII"
168
- decoded.encode!(charset)
169
- decoded.force_encoding('BINARY') unless Encoding.find(charset).ascii_compatible?
170
- end
171
- enc.encode(decoded)
167
+ # Decode then encode to normalize and allow transforming
168
+ # from base64 to Q-P and vice versa
169
+ decoded = dec.decode(raw_source)
170
+ if defined?(Encoding) && charset && charset != "US-ASCII"
171
+ decoded = decoded.encode(charset)
172
+ decoded.force_encoding('BINARY') unless Encoding.find(charset).ascii_compatible?
173
+ end
174
+ enc.encode(decoded)
172
175
  end
173
176
  end
174
177
  end
175
-
178
+
176
179
  def decoded
177
180
  if !Encodings.defined?(encoding)
178
181
  raise UnknownEncodingType, "Don't know how to decode #{encoding}, please call #encoded and decode it yourself."
@@ -184,14 +187,6 @@ module Mail
184
187
  def to_s
185
188
  decoded
186
189
  end
187
-
188
- def charset
189
- @charset
190
- end
191
-
192
- def charset=( val )
193
- @charset = val
194
- end
195
190
 
196
191
  def encoding(val = nil)
197
192
  if val
@@ -200,54 +195,41 @@ module Mail
200
195
  @encoding
201
196
  end
202
197
  end
203
-
198
+
204
199
  def encoding=( val )
205
- @encoding = if val == "text" || Utilities.blank?(val)
206
- (only_us_ascii? ? '7bit' : '8bit')
207
- else
200
+ @encoding =
201
+ if val == "text" || Utilities.blank?(val)
202
+ default_encoding
203
+ else
208
204
  val
209
- end
205
+ end
210
206
  end
211
207
 
212
- # Returns the preamble (any text that is before the first MIME boundary)
213
- def preamble
214
- @preamble
215
- end
208
+ # Returns the raw source that the body was initialized with, without
209
+ # any tampering
210
+ attr_reader :raw_source
211
+
212
+ # Returns parts of the body
213
+ attr_reader :parts
214
+
215
+ # Returns and sets the original character encoding
216
+ attr_accessor :charset
217
+
218
+ # Returns and sets the preamble as a string (any text that is before the first MIME boundary)
219
+ attr_accessor :preamble
220
+
221
+ # Returns and sets the epilogue as a string (any text that is after the last MIME boundary)
222
+ attr_accessor :epilogue
223
+
224
+ # Returns and sets the boundary used by the body
225
+ # Allows you to change the boundary of this Body object
226
+ attr_accessor :boundary
216
227
 
217
- # Sets the preamble to a string (adds text before the first MIME boundary)
218
- def preamble=( val )
219
- @preamble = val
220
- end
221
-
222
- # Returns the epilogue (any text that is after the last MIME boundary)
223
- def epilogue
224
- @epilogue
225
- end
226
-
227
- # Sets the epilogue to a string (adds text after the last MIME boundary)
228
- def epilogue=( val )
229
- @epilogue = val
230
- end
231
-
232
228
  # Returns true if there are parts defined in the body
233
229
  def multipart?
234
230
  true unless parts.empty?
235
231
  end
236
-
237
- # Returns the boundary used by the body
238
- def boundary
239
- @boundary
240
- end
241
-
242
- # Allows you to change the boundary of this Body object
243
- def boundary=( val )
244
- @boundary = val
245
- end
246
232
 
247
- def parts
248
- @parts
249
- end
250
-
251
233
  def <<( val )
252
234
  if @parts
253
235
  @parts << val
@@ -268,14 +250,21 @@ module Mail
268
250
  self
269
251
  end
270
252
 
271
- def only_us_ascii?
272
- !(raw_source =~ /[^\x01-\x7f]/)
253
+ def ascii_only?
254
+ unless defined? @ascii_only
255
+ @ascii_only = raw_source.ascii_only?
256
+ end
257
+ @ascii_only
273
258
  end
274
-
259
+
275
260
  def empty?
276
261
  !!raw_source.to_s.empty?
277
262
  end
278
-
263
+
264
+ def default_encoding
265
+ ascii_only? ? '7bit' : '8bit'
266
+ end
267
+
279
268
  private
280
269
 
281
270
  # split parts by boundary, ignore first part if empty, append final part when closing boundary was missing
@@ -308,9 +297,9 @@ module Mail
308
297
  def end_boundary
309
298
  "\r\n--#{boundary}--\r\n"
310
299
  end
311
-
300
+
312
301
  def set_charset
313
- only_us_ascii? ? @charset = 'US-ASCII' : @charset = nil
302
+ @charset = ascii_only? ? 'US-ASCII' : nil
314
303
  end
315
304
  end
316
305
  end
@@ -1,21 +1,65 @@
1
1
  # frozen_string_literal: true
2
+ #
3
+ # This whole class and associated specs is deprecated and will go away in the version 3 release of mail.
2
4
  module Mail
3
- module CheckDeliveryParams
4
- def check_delivery_params(mail)
5
- if Utilities.blank?(mail.smtp_envelope_from)
6
- raise ArgumentError.new('An SMTP From address is required to send a message. Set the message smtp_envelope_from, return_path, sender, or from address.')
5
+ module CheckDeliveryParams #:nodoc:
6
+ class << self
7
+
8
+ extend Gem::Deprecate
9
+
10
+ def check(mail)
11
+ envelope = Mail::SmtpEnvelope.new(mail)
12
+ [ envelope.from,
13
+ envelope.to,
14
+ envelope.message ]
15
+ end
16
+ deprecate :check, 'Mail::SmtpEnvelope.new created in commit c106bebea066782a72e4f24dd37b532d95773df7', 2023, 6
17
+
18
+ def check_from(addr)
19
+ mail = Mail.new(from: 'deprecated@example.com', to: 'deprecated@example.com')
20
+ Mail::SmtpEnvelope.new(mail).send(:validate_addr, 'From', addr)
7
21
  end
22
+ deprecate :check_from, :none, 2023, 6
8
23
 
9
- if Utilities.blank?(mail.smtp_envelope_to)
10
- raise ArgumentError.new('An SMTP To address is required to send a message. Set the message smtp_envelope_to, to, cc, or bcc address.')
24
+ def check_to(addrs)
25
+ mail = Mail.new(from: 'deprecated@example.com', to: 'deprecated@example.com')
26
+ Array(addrs).map do |addr|
27
+ Mail::SmtpEnvelope.new(mail).send(:validate_addr, 'To', addr)
28
+ end
11
29
  end
30
+ deprecate :check_to, :none, 2023, 6
12
31
 
13
- message = mail.encoded if mail.respond_to?(:encoded)
14
- if Utilities.blank?(message)
15
- raise ArgumentError.new('An encoded message is required to send an email')
32
+ def check_addr(addr_name, addr)
33
+ mail = Mail.new(from: 'deprecated@example.com', to: 'deprecated@example.com')
34
+ Mail::SmtpEnvelope.new(mail).send(:validate_addr, addr_name, addr)
16
35
  end
36
+ deprecate :check_addr, :none, 2023, 6
37
+
38
+ def validate_smtp_addr(addr)
39
+ if addr
40
+ if addr.bytesize > 2048
41
+ yield 'may not exceed 2kB'
42
+ end
43
+
44
+ if /[\r\n]/ =~ addr
45
+ yield 'may not contain CR or LF line breaks'
46
+ end
47
+ end
17
48
 
18
- [mail.smtp_envelope_from, mail.smtp_envelope_to, message]
49
+ addr
50
+ end
51
+ deprecate :validate_smtp_addr, :none, 2023, 6
52
+
53
+ def check_message(message)
54
+ message = message.encoded if message.respond_to?(:encoded)
55
+
56
+ if Utilities.blank?(message)
57
+ raise ArgumentError, 'An encoded message is required to send an email'
58
+ end
59
+
60
+ message
61
+ end
62
+ deprecate :check_message, :none, 2023, 6
19
63
  end
20
64
  end
21
65
  end
@@ -42,6 +42,8 @@ module Mail
42
42
  Mail::SMTPConnection
43
43
  when :test
44
44
  Mail::TestMailer
45
+ when :logger
46
+ Mail::LoggerDelivery
45
47
  else
46
48
  method
47
49
  end
@@ -16,9 +16,10 @@ module Mail
16
16
  control = control.dup.force_encoding(Encoding::BINARY)
17
17
  end
18
18
 
19
- CRLF = /\r\n/
19
+ LAX_CRLF = /\r?\n/
20
20
  WSP = /[#{white_space}]/
21
- FWS = /#{CRLF}#{WSP}*/
21
+ FWS = /#{LAX_CRLF}#{WSP}*/
22
+ UNFOLD_WS = /#{LAX_CRLF}(#{WSP})/m
22
23
  TEXT = /[#{text}]/ # + obs-text
23
24
  FIELD_NAME = /[#{field_name}]+/
24
25
  FIELD_PREFIX = /\A(#{FIELD_NAME})/
@@ -26,7 +27,7 @@ module Mail
26
27
  FIELD_LINE = /^[#{field_name}]+:\s*.+$/
27
28
  FIELD_SPLIT = /^(#{FIELD_NAME})\s*:\s*(#{FIELD_BODY})?$/
28
29
  HEADER_LINE = /^([#{field_name}]+:\s*.+)$/
29
- HEADER_SPLIT = /#{CRLF}(?!#{WSP})/
30
+ HEADER_SPLIT = /#{LAX_CRLF}(?!#{WSP})/
30
31
 
31
32
  QP_UNSAFE = /[^#{qp_safe}]/
32
33
  QP_SAFE = /[#{qp_safe}]/
@@ -34,8 +35,28 @@ module Mail
34
35
  ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{sp}]/n
35
36
  PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
36
37
  TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{sp}]/n
37
- ENCODED_VALUE = /\=\?([^?]+)\?([QB])\?[^?]*?\?\=/mi
38
- FULL_ENCODED_VALUE = /(\=\?[^?]+\?[QB]\?[^?]*?\?\=)/mi
38
+
39
+ ENCODED_VALUE = %r{
40
+ \=\? # literal =?
41
+ ([^?]+) #
42
+ \? # literal ?
43
+ ([QB]) # either a "Q" or a "B"
44
+ \? # literal ?
45
+ .*? # lazily match all characters
46
+ \?\= # literal ?=
47
+ }mix # m is multi-line, i is case-insensitive, x is free-spacing
48
+
49
+ FULL_ENCODED_VALUE = %r{ # Identical to ENCODED_VALUE but captures the whole rather than components of
50
+ (
51
+ \=\? # literal =?
52
+ [^?]+ #
53
+ \? # literal ?
54
+ [QB] # either a "Q" or a "B"
55
+ \? # literal ?
56
+ .*? # lazily match all characters
57
+ \?\= # literal ?=
58
+ )
59
+ }mix # m is multi-line, i is case-insensitive, x is free-spacing
39
60
 
40
61
  EMPTY = ''
41
62
  SPACE = ' '
@@ -43,6 +64,7 @@ module Mail
43
64
  HYPHEN = '-'
44
65
  COLON = ':'
45
66
  ASTERISK = '*'
67
+ CRLF = "\r\n"
46
68
  CR = "\r"
47
69
  LF = "\n"
48
70
  CR_ENCODED = "=0D"
@@ -1,28 +1,28 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+ require 'mail/parsers/address_lists_parser'
4
+ require 'mail/constants'
5
+ require 'mail/utilities'
6
+
3
7
  module Mail
8
+ # Mail::Address handles all email addresses in Mail. It takes an email address string
9
+ # and parses it, breaking it down into its component parts and allowing you to get the
10
+ # address, comments, display name, name, local part, domain part and fully formatted
11
+ # address.
12
+ #
13
+ # Mail::Address requires a correctly formatted email address per RFC2822 or RFC822. It
14
+ # handles all obsolete versions including obsolete domain routing on the local part.
15
+ #
16
+ # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
17
+ # a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
18
+ # a.address #=> 'mikel@test.lindsaar.net'
19
+ # a.display_name #=> 'Mikel Lindsaar'
20
+ # a.local #=> 'mikel'
21
+ # a.domain #=> 'test.lindsaar.net'
22
+ # a.comments #=> ['My email address']
23
+ # a.to_s #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
4
24
  class Address
5
-
6
- include Mail::Utilities
7
-
8
- # Mail::Address handles all email addresses in Mail. It takes an email address string
9
- # and parses it, breaking it down into its component parts and allowing you to get the
10
- # address, comments, display name, name, local part, domain part and fully formatted
11
- # address.
12
- #
13
- # Mail::Address requires a correctly formatted email address per RFC2822 or RFC822. It
14
- # handles all obsolete versions including obsolete domain routing on the local part.
15
- #
16
- # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
17
- # a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
18
- # a.address #=> 'mikel@test.lindsaar.net'
19
- # a.display_name #=> 'Mikel Lindsaar'
20
- # a.local #=> 'mikel'
21
- # a.domain #=> 'test.lindsaar.net'
22
- # a.comments #=> ['My email address']
23
- # a.to_s #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
24
25
  def initialize(value = nil)
25
- @output_type = :decode
26
26
  if value.nil?
27
27
  @parsed = false
28
28
  @data = nil
@@ -44,14 +44,14 @@ module Mail
44
44
  #
45
45
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
46
46
  # a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
47
- def format
47
+ def format(output_type = :decode)
48
48
  parse unless @parsed
49
49
  if @data.nil?
50
- EMPTY
51
- elsif display_name
52
- [quote_phrase(display_name), "<#{address}>", format_comments].compact.join(SPACE)
53
- elsif address
54
- [address, format_comments].compact.join(SPACE)
50
+ Constants::EMPTY
51
+ elsif name = display_name(output_type)
52
+ [Utilities.quote_phrase(name), "<#{address(output_type)}>", format_comments].compact.join(Constants::SPACE)
53
+ elsif a = address(output_type)
54
+ [a, format_comments].compact.join(Constants::SPACE)
55
55
  else
56
56
  raw
57
57
  end
@@ -62,9 +62,13 @@ module Mail
62
62
  #
63
63
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
64
64
  # a.address #=> 'mikel@test.lindsaar.net'
65
- def address
65
+ def address(output_type = :decode)
66
66
  parse unless @parsed
67
- domain ? "#{local}@#{domain}" : local
67
+ if d = domain(output_type)
68
+ "#{local(output_type)}@#{d}"
69
+ else
70
+ local(output_type)
71
+ end
68
72
  end
69
73
 
70
74
  # Provides a way to assign an address to an already made Mail::Address object.
@@ -80,10 +84,10 @@ module Mail
80
84
  #
81
85
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
82
86
  # a.display_name #=> 'Mikel Lindsaar'
83
- def display_name
87
+ def display_name(output_type = :decode)
84
88
  parse unless @parsed
85
89
  @display_name ||= get_display_name
86
- Encodings.decode_encode(@display_name.to_s, @output_type) if @display_name
90
+ Encodings.decode_encode(@display_name.to_s, output_type) if @display_name
87
91
  end
88
92
 
89
93
  # Provides a way to assign a display name to an already made Mail::Address object.
@@ -93,7 +97,7 @@ module Mail
93
97
  # a.display_name = 'Mikel Lindsaar'
94
98
  # a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>'
95
99
  def display_name=( str )
96
- @display_name = str.dup # in case frozen
100
+ @display_name = str.nil? ? nil : str.dup # in case frozen
97
101
  end
98
102
 
99
103
  # Returns the local part (the left hand side of the @ sign in the email address) of
@@ -101,9 +105,9 @@ module Mail
101
105
  #
102
106
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
103
107
  # a.local #=> 'mikel'
104
- def local
108
+ def local(output_type = :decode)
105
109
  parse unless @parsed
106
- Encodings.decode_encode("#{@data.obs_domain_list}#{get_local.strip}", @output_type) if get_local
110
+ Encodings.decode_encode("#{@data.obs_domain_list}#{get_local.strip}", output_type) if get_local
107
111
  end
108
112
 
109
113
  # Returns the domain part (the right hand side of the @ sign in the email address) of
@@ -111,19 +115,28 @@ module Mail
111
115
  #
112
116
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
113
117
  # a.domain #=> 'test.lindsaar.net'
114
- def domain
118
+ def domain(output_type = :decode)
115
119
  parse unless @parsed
116
- Encodings.decode_encode(strip_all_comments(get_domain), @output_type) if get_domain
120
+ Encodings.decode_encode(strip_all_comments(get_domain), output_type) if get_domain
117
121
  end
118
122
 
119
- # Returns an array of comments that are in the email, or an empty array if there
123
+ # Returns an array of comments that are in the email, or nil if there
120
124
  # are no comments
121
125
  #
122
126
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
123
127
  # a.comments #=> ['My email address']
128
+ #
129
+ # b = Address.new('Mikel Lindsaar <mikel@test.lindsaar.net>')
130
+ # b.comments #=> nil
131
+
124
132
  def comments
125
133
  parse unless @parsed
126
- get_comments.map { |c| c.squeeze(SPACE) } unless get_comments.empty?
134
+ comments = get_comments
135
+ if comments.nil? || comments.none?
136
+ nil
137
+ else
138
+ comments.map { |c| c.squeeze(Constants::SPACE) }
139
+ end
127
140
  end
128
141
 
129
142
  # Sometimes an address will not have a display name, but might have the name
@@ -154,13 +167,11 @@ module Mail
154
167
  end
155
168
 
156
169
  def encoded
157
- @output_type = :encode
158
- format
170
+ format :encode
159
171
  end
160
172
 
161
173
  def decoded
162
- @output_type = :decode
163
- format
174
+ format :decode
164
175
  end
165
176
 
166
177
  def group
@@ -174,11 +185,11 @@ module Mail
174
185
  @data = nil
175
186
 
176
187
  case value
177
- when Mail::Parsers::AddressStruct
188
+ when Mail::Parsers::AddressListsParser::AddressStruct
178
189
  @data = value
179
190
  when String
180
191
  unless Utilities.blank?(value)
181
- address_list = Mail::Parsers::AddressListsParser.new.parse(value)
192
+ address_list = Mail::Parsers::AddressListsParser.parse(value)
182
193
  @data = address_list.addresses.first
183
194
  end
184
195
  end
@@ -187,7 +198,7 @@ module Mail
187
198
  def strip_all_comments(string)
188
199
  unless Utilities.blank?(comments)
189
200
  comments.each do |comment|
190
- string = string.gsub("(#{comment})", EMPTY)
201
+ string = string.gsub("(#{comment})", Constants::EMPTY)
191
202
  end
192
203
  end
193
204
  string.strip
@@ -197,7 +208,7 @@ module Mail
197
208
  unless Utilities.blank?(comments)
198
209
  comments.each do |comment|
199
210
  if @data.domain && @data.domain.include?("(#{comment})")
200
- value = value.gsub("(#{comment})", EMPTY)
211
+ value = value.gsub("(#{comment})", Constants::EMPTY)
201
212
  end
202
213
  end
203
214
  end
@@ -205,9 +216,9 @@ module Mail
205
216
  end
206
217
 
207
218
  def get_display_name
208
- if @data.display_name
219
+ if @data && @data.display_name
209
220
  str = strip_all_comments(@data.display_name.to_s)
210
- elsif @data.comments && @data.domain
221
+ elsif @data && @data.comments && @data.domain
211
222
  str = strip_domain_comments(format_comments)
212
223
  end
213
224
  str unless Utilities.blank?(str)
@@ -217,15 +228,15 @@ module Mail
217
228
  if display_name
218
229
  str = display_name
219
230
  elsif comments
220
- str = "(#{comments.join(SPACE).squeeze(SPACE)})"
231
+ str = "(#{comments.join(Constants::SPACE).squeeze(Constants::SPACE)})"
221
232
  end
222
233
 
223
- unparen(str) unless Utilities.blank?(str)
234
+ Utilities.unparen(str) unless Utilities.blank?(str)
224
235
  end
225
236
 
226
237
  def format_comments
227
238
  if comments
228
- comment_text = comments.map {|c| escape_paren(c) }.join(SPACE).squeeze(SPACE)
239
+ comment_text = comments.map {|c| Utilities.escape_paren(c) }.join(Constants::SPACE).squeeze(Constants::SPACE)
229
240
  @format_comments ||= "(#{comment_text})"
230
241
  else
231
242
  nil
@@ -1,42 +1,34 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+ require 'mail/parsers/address_lists_parser'
4
+
3
5
  module Mail
4
- class AddressList # :nodoc:
6
+ class AddressList #:nodoc:
7
+ attr_reader :addresses, :group_names
5
8
 
6
9
  # Mail::AddressList is the class that parses To, From and other address fields from
7
10
  # emails passed into Mail.
8
- #
11
+ #
9
12
  # AddressList provides a way to query the groups and mailbox lists of the passed in
10
13
  # string.
11
- #
14
+ #
12
15
  # It can supply all addresses in an array, or return each address as an address object.
13
- #
16
+ #
14
17
  # Mail::AddressList requires a correctly formatted group or mailbox list per RFC2822 or
15
18
  # RFC822. It also handles all obsolete versions in those RFCs.
16
- #
19
+ #
17
20
  # list = 'ada@test.lindsaar.net, My Group: mikel@test.lindsaar.net, Bob <bob@test.lindsaar.net>;'
18
21
  # a = AddressList.new(list)
19
22
  # a.addresses #=> [#<Mail::Address:14943130 Address: |ada@test.lindsaar.net...
20
23
  # a.group_names #=> ["My Group"]
21
24
  def initialize(string)
22
- @addresses_grouped_by_group = nil
23
- @address_list = Parsers::AddressListsParser.new.parse(string)
24
- end
25
-
26
- # Returns a list of address objects from the parsed line
27
- def addresses
28
- @addresses ||= @address_list.addresses.map do |address_data|
29
- Mail::Address.new(address_data)
30
- end
25
+ address_list = Parsers::AddressListsParser.parse(string)
26
+ @addresses = address_list.addresses.map { |a| Address.new(a) }
27
+ @group_names = address_list.group_names
31
28
  end
32
29
 
33
30
  def addresses_grouped_by_group
34
31
  addresses.select(&:group).group_by(&:group)
35
32
  end
36
-
37
- # Returns the names as an array of strings of all groups
38
- def group_names # :nodoc:
39
- @address_list.group_names
40
- end
41
33
  end
42
34
  end