mail 2.6.6 → 2.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (180) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +134 -119
  4. data/lib/mail/attachments_list.rb +10 -9
  5. data/lib/mail/body.rb +73 -84
  6. data/lib/mail/check_delivery_params.rb +28 -21
  7. data/lib/mail/configuration.rb +2 -0
  8. data/lib/mail/constants.rb +27 -5
  9. data/lib/mail/elements/address.rb +53 -47
  10. data/lib/mail/elements/address_list.rb +11 -19
  11. data/lib/mail/elements/content_disposition_element.rb +9 -16
  12. data/lib/mail/elements/content_location_element.rb +6 -11
  13. data/lib/mail/elements/content_transfer_encoding_element.rb +6 -11
  14. data/lib/mail/elements/content_type_element.rb +16 -23
  15. data/lib/mail/elements/date_time_element.rb +7 -15
  16. data/lib/mail/elements/envelope_from_element.rb +22 -23
  17. data/lib/mail/elements/message_ids_element.rb +18 -13
  18. data/lib/mail/elements/mime_version_element.rb +7 -15
  19. data/lib/mail/elements/phrase_list.rb +12 -10
  20. data/lib/mail/elements/received_element.rb +27 -19
  21. data/lib/mail/encodings/7bit.rb +9 -14
  22. data/lib/mail/encodings/8bit.rb +2 -21
  23. data/lib/mail/encodings/base64.rb +11 -12
  24. data/lib/mail/encodings/binary.rb +3 -22
  25. data/lib/mail/encodings/identity.rb +24 -0
  26. data/lib/mail/encodings/quoted_printable.rb +6 -6
  27. data/lib/mail/encodings/transfer_encoding.rb +38 -29
  28. data/lib/mail/encodings/unix_to_unix.rb +3 -1
  29. data/lib/mail/encodings.rb +81 -54
  30. data/lib/mail/envelope.rb +11 -14
  31. data/lib/mail/field.rb +119 -98
  32. data/lib/mail/field_list.rb +60 -7
  33. data/lib/mail/fields/bcc_field.rb +34 -52
  34. data/lib/mail/fields/cc_field.rb +28 -49
  35. data/lib/mail/fields/comments_field.rb +27 -37
  36. data/lib/mail/fields/common_address_field.rb +170 -0
  37. data/lib/mail/fields/common_date_field.rb +58 -0
  38. data/lib/mail/fields/common_field.rb +77 -0
  39. data/lib/mail/fields/common_message_id_field.rb +42 -0
  40. data/lib/mail/fields/content_description_field.rb +7 -14
  41. data/lib/mail/fields/content_disposition_field.rb +13 -38
  42. data/lib/mail/fields/content_id_field.rb +24 -51
  43. data/lib/mail/fields/content_location_field.rb +11 -25
  44. data/lib/mail/fields/content_transfer_encoding_field.rb +31 -31
  45. data/lib/mail/fields/content_type_field.rb +50 -80
  46. data/lib/mail/fields/date_field.rb +23 -52
  47. data/lib/mail/fields/from_field.rb +28 -49
  48. data/lib/mail/fields/in_reply_to_field.rb +38 -49
  49. data/lib/mail/fields/keywords_field.rb +18 -31
  50. data/lib/mail/fields/message_id_field.rb +25 -71
  51. data/lib/mail/fields/mime_version_field.rb +19 -30
  52. data/lib/mail/fields/named_structured_field.rb +11 -0
  53. data/lib/mail/fields/named_unstructured_field.rb +11 -0
  54. data/lib/mail/fields/optional_field.rb +9 -7
  55. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +13 -11
  56. data/lib/mail/fields/received_field.rb +43 -57
  57. data/lib/mail/fields/references_field.rb +35 -49
  58. data/lib/mail/fields/reply_to_field.rb +28 -49
  59. data/lib/mail/fields/resent_bcc_field.rb +28 -49
  60. data/lib/mail/fields/resent_cc_field.rb +28 -49
  61. data/lib/mail/fields/resent_date_field.rb +5 -30
  62. data/lib/mail/fields/resent_from_field.rb +28 -49
  63. data/lib/mail/fields/resent_message_id_field.rb +5 -29
  64. data/lib/mail/fields/resent_sender_field.rb +27 -56
  65. data/lib/mail/fields/resent_to_field.rb +28 -49
  66. data/lib/mail/fields/return_path_field.rb +50 -54
  67. data/lib/mail/fields/sender_field.rb +34 -55
  68. data/lib/mail/fields/structured_field.rb +3 -30
  69. data/lib/mail/fields/subject_field.rb +9 -11
  70. data/lib/mail/fields/to_field.rb +28 -49
  71. data/lib/mail/fields/unstructured_field.rb +32 -47
  72. data/lib/mail/header.rb +71 -110
  73. data/lib/mail/mail.rb +2 -10
  74. data/lib/mail/matchers/attachment_matchers.rb +15 -0
  75. data/lib/mail/matchers/has_sent_mail.rb +21 -1
  76. data/lib/mail/message.rb +113 -117
  77. data/lib/mail/multibyte/chars.rb +21 -178
  78. data/lib/mail/multibyte/unicode.rb +10 -10
  79. data/lib/mail/multibyte/utils.rb +26 -43
  80. data/lib/mail/multibyte.rb +55 -16
  81. data/lib/mail/network/delivery_methods/exim.rb +5 -4
  82. data/lib/mail/network/delivery_methods/file_delivery.rb +11 -10
  83. data/lib/mail/network/delivery_methods/logger_delivery.rb +34 -0
  84. data/lib/mail/network/delivery_methods/sendmail.rb +62 -21
  85. data/lib/mail/network/delivery_methods/smtp.rb +75 -50
  86. data/lib/mail/network/delivery_methods/smtp_connection.rb +3 -4
  87. data/lib/mail/network/delivery_methods/test_mailer.rb +4 -2
  88. data/lib/mail/network/retriever_methods/base.rb +8 -8
  89. data/lib/mail/network/retriever_methods/imap.rb +20 -7
  90. data/lib/mail/network/retriever_methods/pop3.rb +5 -3
  91. data/lib/mail/network/retriever_methods/test_retriever.rb +2 -1
  92. data/lib/mail/network.rb +1 -0
  93. data/lib/mail/parser_tools.rb +15 -0
  94. data/lib/mail/parsers/address_lists_parser.rb +33225 -116
  95. data/lib/mail/parsers/address_lists_parser.rl +179 -0
  96. data/lib/mail/parsers/content_disposition_parser.rb +882 -49
  97. data/lib/mail/parsers/content_disposition_parser.rl +89 -0
  98. data/lib/mail/parsers/content_location_parser.rb +809 -23
  99. data/lib/mail/parsers/content_location_parser.rl +78 -0
  100. data/lib/mail/parsers/content_transfer_encoding_parser.rb +509 -21
  101. data/lib/mail/parsers/content_transfer_encoding_parser.rl +71 -0
  102. data/lib/mail/parsers/content_type_parser.rb +1037 -56
  103. data/lib/mail/parsers/content_type_parser.rl +90 -0
  104. data/lib/mail/parsers/date_time_parser.rb +877 -25
  105. data/lib/mail/parsers/date_time_parser.rl +69 -0
  106. data/lib/mail/parsers/envelope_from_parser.rb +3669 -40
  107. data/lib/mail/parsers/envelope_from_parser.rl +89 -0
  108. data/lib/mail/parsers/message_ids_parser.rb +5146 -25
  109. data/lib/mail/parsers/message_ids_parser.rl +93 -0
  110. data/lib/mail/parsers/mime_version_parser.rb +497 -26
  111. data/lib/mail/parsers/mime_version_parser.rl +68 -0
  112. data/lib/mail/parsers/phrase_lists_parser.rb +870 -22
  113. data/lib/mail/parsers/phrase_lists_parser.rl +90 -0
  114. data/lib/mail/parsers/received_parser.rb +8776 -43
  115. data/lib/mail/parsers/received_parser.rl +91 -0
  116. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  117. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  118. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  119. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  120. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  121. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  122. data/lib/mail/parsers/rfc5322.rl +74 -0
  123. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  124. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  125. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  126. data/lib/mail/parsers.rb +11 -25
  127. data/lib/mail/part.rb +6 -10
  128. data/lib/mail/parts_list.rb +62 -6
  129. data/lib/mail/smtp_envelope.rb +57 -0
  130. data/lib/mail/utilities.rb +343 -74
  131. data/lib/mail/version.rb +2 -2
  132. data/lib/mail/yaml.rb +30 -0
  133. data/lib/mail.rb +5 -35
  134. metadata +111 -66
  135. data/CHANGELOG.rdoc +0 -803
  136. data/CONTRIBUTING.md +0 -60
  137. data/Dependencies.txt +0 -2
  138. data/Gemfile +0 -14
  139. data/Rakefile +0 -29
  140. data/TODO.rdoc +0 -9
  141. data/lib/mail/core_extensions/smtp.rb +0 -25
  142. data/lib/mail/core_extensions/string/access.rb +0 -146
  143. data/lib/mail/core_extensions/string/multibyte.rb +0 -79
  144. data/lib/mail/core_extensions/string.rb +0 -21
  145. data/lib/mail/fields/common/address_container.rb +0 -17
  146. data/lib/mail/fields/common/common_address.rb +0 -136
  147. data/lib/mail/fields/common/common_date.rb +0 -36
  148. data/lib/mail/fields/common/common_field.rb +0 -61
  149. data/lib/mail/fields/common/common_message_id.rb +0 -49
  150. data/lib/mail/multibyte/exceptions.rb +0 -9
  151. data/lib/mail/parsers/ragel/common.rl +0 -185
  152. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  153. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  154. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  155. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  156. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  157. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  158. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  159. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  160. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  161. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  162. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  163. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  164. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  165. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2149
  166. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  167. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  168. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  169. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  170. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  171. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  172. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  173. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  174. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  175. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  176. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  177. data/lib/mail/parsers/ragel/ruby.rb +0 -40
  178. data/lib/mail/parsers/ragel.rb +0 -18
  179. data/lib/mail/version_specific/ruby_1_8.rb +0 -126
  180. data/lib/mail/version_specific/ruby_1_9.rb +0 -226
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,48 +1,54 @@
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
5
  module CheckDeliveryParams #:nodoc:
4
6
  class << self
7
+
8
+ extend Gem::Deprecate
9
+
5
10
  def check(mail)
6
- [ check_from(mail.smtp_envelope_from),
7
- check_to(mail.smtp_envelope_to),
8
- check_message(mail) ]
11
+ envelope = Mail::SmtpEnvelope.new(mail)
12
+ [ envelope.from,
13
+ envelope.to,
14
+ envelope.message ]
9
15
  end
16
+ deprecate :check, 'Mail::SmtpEnvelope.new created in commit c106bebea066782a72e4f24dd37b532d95773df7', 2023, 6
10
17
 
11
18
  def check_from(addr)
12
- if Utilities.blank?(addr)
13
- raise ArgumentError, "SMTP From address may not be blank: #{addr.inspect}"
14
- end
15
-
16
- check_addr 'From', addr
19
+ mail = Mail.new(from: 'deprecated@example.com', to: 'deprecated@example.com')
20
+ Mail::SmtpEnvelope.new(mail).send(:validate_addr, 'From', addr)
17
21
  end
22
+ deprecate :check_from, :none, 2023, 6
18
23
 
19
24
  def check_to(addrs)
20
- if Utilities.blank?(addrs)
21
- raise ArgumentError, "SMTP To address may not be blank: #{addrs.inspect}"
22
- end
23
-
25
+ mail = Mail.new(from: 'deprecated@example.com', to: 'deprecated@example.com')
24
26
  Array(addrs).map do |addr|
25
- check_addr 'To', addr
27
+ Mail::SmtpEnvelope.new(mail).send(:validate_addr, 'To', addr)
26
28
  end
27
29
  end
30
+ deprecate :check_to, :none, 2023, 6
28
31
 
29
32
  def check_addr(addr_name, addr)
30
- validate_smtp_addr addr do |error_message|
31
- raise ArgumentError, "SMTP #{addr_name} address #{error_message}: #{addr.inspect}"
32
- end
33
+ mail = Mail.new(from: 'deprecated@example.com', to: 'deprecated@example.com')
34
+ Mail::SmtpEnvelope.new(mail).send(:validate_addr, addr_name, addr)
33
35
  end
36
+ deprecate :check_addr, :none, 2023, 6
34
37
 
35
38
  def validate_smtp_addr(addr)
36
- if addr.bytesize > 2048
37
- yield 'may not exceed 2kB'
38
- end
39
+ if addr
40
+ if addr.bytesize > 2048
41
+ yield 'may not exceed 2kB'
42
+ end
39
43
 
40
- if /[\r\n]/ =~ addr
41
- yield 'may not contain CR or LF line breaks'
44
+ if /[\r\n]/ =~ addr
45
+ yield 'may not contain CR or LF line breaks'
46
+ end
42
47
  end
43
48
 
44
49
  addr
45
50
  end
51
+ deprecate :validate_smtp_addr, :none, 2023, 6
46
52
 
47
53
  def check_message(message)
48
54
  message = message.encoded if message.respond_to?(:encoded)
@@ -53,6 +59,7 @@ module Mail
53
59
 
54
60
  message
55
61
  end
62
+ deprecate :check_message, :none, 2023, 6
56
63
  end
57
64
  end
58
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.
@@ -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,23 +115,27 @@ 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
134
  comments = get_comments
127
135
  if comments.nil? || comments.none?
128
136
  nil
129
137
  else
130
- comments.map { |c| c.squeeze(SPACE) }
138
+ comments.map { |c| c.squeeze(Constants::SPACE) }
131
139
  end
132
140
  end
133
141
 
@@ -159,13 +167,11 @@ module Mail
159
167
  end
160
168
 
161
169
  def encoded
162
- @output_type = :encode
163
- format
170
+ format :encode
164
171
  end
165
172
 
166
173
  def decoded
167
- @output_type = :decode
168
- format
174
+ format :decode
169
175
  end
170
176
 
171
177
  def group
@@ -179,11 +185,11 @@ module Mail
179
185
  @data = nil
180
186
 
181
187
  case value
182
- when Mail::Parsers::AddressStruct
188
+ when Mail::Parsers::AddressListsParser::AddressStruct
183
189
  @data = value
184
190
  when String
185
191
  unless Utilities.blank?(value)
186
- address_list = Mail::Parsers::AddressListsParser.new.parse(value)
192
+ address_list = Mail::Parsers::AddressListsParser.parse(value)
187
193
  @data = address_list.addresses.first
188
194
  end
189
195
  end
@@ -192,7 +198,7 @@ module Mail
192
198
  def strip_all_comments(string)
193
199
  unless Utilities.blank?(comments)
194
200
  comments.each do |comment|
195
- string = string.gsub("(#{comment})", EMPTY)
201
+ string = string.gsub("(#{comment})", Constants::EMPTY)
196
202
  end
197
203
  end
198
204
  string.strip
@@ -202,7 +208,7 @@ module Mail
202
208
  unless Utilities.blank?(comments)
203
209
  comments.each do |comment|
204
210
  if @data.domain && @data.domain.include?("(#{comment})")
205
- value = value.gsub("(#{comment})", EMPTY)
211
+ value = value.gsub("(#{comment})", Constants::EMPTY)
206
212
  end
207
213
  end
208
214
  end
@@ -222,15 +228,15 @@ module Mail
222
228
  if display_name
223
229
  str = display_name
224
230
  elsif comments
225
- str = "(#{comments.join(SPACE).squeeze(SPACE)})"
231
+ str = "(#{comments.join(Constants::SPACE).squeeze(Constants::SPACE)})"
226
232
  end
227
233
 
228
- unparen(str) unless Utilities.blank?(str)
234
+ Utilities.unparen(str) unless Utilities.blank?(str)
229
235
  end
230
236
 
231
237
  def format_comments
232
238
  if comments
233
- 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)
234
240
  @format_comments ||= "(#{comment_text})"
235
241
  else
236
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 = Mail::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