mail 2.6.3 → 2.7.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 (178) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +91 -79
  4. data/lib/mail/attachments_list.rb +11 -5
  5. data/lib/mail/body.rb +46 -39
  6. data/lib/mail/check_delivery_params.rb +50 -10
  7. data/lib/mail/configuration.rb +3 -0
  8. data/lib/mail/constants.rb +4 -2
  9. data/lib/mail/core_extensions/smtp.rb +20 -16
  10. data/lib/mail/core_extensions/string.rb +1 -30
  11. data/lib/mail/elements/address.rb +43 -32
  12. data/lib/mail/elements/address_list.rb +11 -18
  13. data/lib/mail/elements/content_disposition_element.rb +9 -15
  14. data/lib/mail/elements/content_location_element.rb +8 -12
  15. data/lib/mail/elements/content_transfer_encoding_element.rb +6 -10
  16. data/lib/mail/elements/content_type_element.rb +9 -19
  17. data/lib/mail/elements/date_time_element.rb +7 -14
  18. data/lib/mail/elements/envelope_from_element.rb +15 -21
  19. data/lib/mail/elements/message_ids_element.rb +12 -14
  20. data/lib/mail/elements/mime_version_element.rb +7 -14
  21. data/lib/mail/elements/phrase_list.rb +7 -9
  22. data/lib/mail/elements/received_element.rb +10 -15
  23. data/lib/mail/elements.rb +1 -0
  24. data/lib/mail/encodings/7bit.rb +6 -15
  25. data/lib/mail/encodings/8bit.rb +5 -18
  26. data/lib/mail/encodings/base64.rb +15 -10
  27. data/lib/mail/encodings/binary.rb +4 -22
  28. data/lib/mail/encodings/identity.rb +24 -0
  29. data/lib/mail/encodings/quoted_printable.rb +13 -7
  30. data/lib/mail/encodings/transfer_encoding.rb +47 -28
  31. data/lib/mail/encodings/unix_to_unix.rb +3 -1
  32. data/lib/mail/encodings.rb +114 -60
  33. data/lib/mail/envelope.rb +2 -1
  34. data/lib/mail/field.rb +112 -62
  35. data/lib/mail/field_list.rb +1 -0
  36. data/lib/mail/fields/bcc_field.rb +17 -5
  37. data/lib/mail/fields/cc_field.rb +2 -2
  38. data/lib/mail/fields/comments_field.rb +2 -1
  39. data/lib/mail/fields/common/address_container.rb +3 -2
  40. data/lib/mail/fields/common/common_address.rb +40 -14
  41. data/lib/mail/fields/common/common_date.rb +2 -1
  42. data/lib/mail/fields/common/common_field.rb +5 -11
  43. data/lib/mail/fields/common/common_message_id.rb +3 -2
  44. data/lib/mail/fields/common/parameter_hash.rb +2 -1
  45. data/lib/mail/fields/content_description_field.rb +2 -1
  46. data/lib/mail/fields/content_disposition_field.rb +14 -13
  47. data/lib/mail/fields/content_id_field.rb +5 -4
  48. data/lib/mail/fields/content_location_field.rb +3 -2
  49. data/lib/mail/fields/content_transfer_encoding_field.rb +3 -2
  50. data/lib/mail/fields/content_type_field.rb +6 -10
  51. data/lib/mail/fields/date_field.rb +4 -4
  52. data/lib/mail/fields/from_field.rb +2 -2
  53. data/lib/mail/fields/in_reply_to_field.rb +2 -1
  54. data/lib/mail/fields/keywords_field.rb +3 -3
  55. data/lib/mail/fields/message_id_field.rb +3 -2
  56. data/lib/mail/fields/mime_version_field.rb +4 -3
  57. data/lib/mail/fields/optional_field.rb +5 -1
  58. data/lib/mail/fields/received_field.rb +5 -4
  59. data/lib/mail/fields/references_field.rb +2 -1
  60. data/lib/mail/fields/reply_to_field.rb +2 -2
  61. data/lib/mail/fields/resent_bcc_field.rb +2 -2
  62. data/lib/mail/fields/resent_cc_field.rb +2 -2
  63. data/lib/mail/fields/resent_date_field.rb +2 -2
  64. data/lib/mail/fields/resent_from_field.rb +2 -2
  65. data/lib/mail/fields/resent_message_id_field.rb +2 -1
  66. data/lib/mail/fields/resent_sender_field.rb +2 -2
  67. data/lib/mail/fields/resent_to_field.rb +2 -2
  68. data/lib/mail/fields/return_path_field.rb +2 -2
  69. data/lib/mail/fields/sender_field.rb +2 -2
  70. data/lib/mail/fields/structured_field.rb +1 -0
  71. data/lib/mail/fields/subject_field.rb +2 -1
  72. data/lib/mail/fields/to_field.rb +2 -2
  73. data/lib/mail/fields/unstructured_field.rb +25 -7
  74. data/lib/mail/fields.rb +1 -0
  75. data/lib/mail/header.rb +15 -12
  76. data/lib/mail/indifferent_hash.rb +1 -0
  77. data/lib/mail/mail.rb +3 -10
  78. data/lib/mail/matchers/attachment_matchers.rb +29 -0
  79. data/lib/mail/matchers/has_sent_mail.rb +51 -7
  80. data/lib/mail/message.rb +91 -85
  81. data/lib/mail/multibyte/chars.rb +32 -30
  82. data/lib/mail/multibyte/unicode.rb +21 -16
  83. data/lib/mail/multibyte/utils.rb +1 -0
  84. data/lib/mail/multibyte.rb +65 -15
  85. data/lib/mail/network/delivery_methods/exim.rb +7 -10
  86. data/lib/mail/network/delivery_methods/file_delivery.rb +5 -8
  87. data/lib/mail/network/delivery_methods/logger_delivery.rb +37 -0
  88. data/lib/mail/network/delivery_methods/sendmail.rb +17 -11
  89. data/lib/mail/network/delivery_methods/smtp.rb +60 -53
  90. data/lib/mail/network/delivery_methods/smtp_connection.rb +11 -6
  91. data/lib/mail/network/delivery_methods/test_mailer.rb +6 -8
  92. data/lib/mail/network/retriever_methods/base.rb +1 -0
  93. data/lib/mail/network/retriever_methods/imap.rb +19 -5
  94. data/lib/mail/network/retriever_methods/pop3.rb +4 -1
  95. data/lib/mail/network/retriever_methods/test_retriever.rb +2 -1
  96. data/lib/mail/network.rb +2 -0
  97. data/lib/mail/parser_tools.rb +15 -0
  98. data/lib/mail/parsers/address_lists_parser.rb +33187 -112
  99. data/lib/mail/parsers/address_lists_parser.rl +172 -0
  100. data/lib/mail/parsers/content_disposition_parser.rb +889 -55
  101. data/lib/mail/parsers/content_disposition_parser.rl +82 -0
  102. data/lib/mail/parsers/content_location_parser.rb +801 -23
  103. data/lib/mail/parsers/content_location_parser.rl +71 -0
  104. data/lib/mail/parsers/content_transfer_encoding_parser.rb +511 -22
  105. data/lib/mail/parsers/content_transfer_encoding_parser.rl +64 -0
  106. data/lib/mail/parsers/content_type_parser.rb +1033 -54
  107. data/lib/mail/parsers/content_type_parser.rl +83 -0
  108. data/lib/mail/parsers/date_time_parser.rb +875 -25
  109. data/lib/mail/parsers/date_time_parser.rl +62 -0
  110. data/lib/mail/parsers/envelope_from_parser.rb +3565 -36
  111. data/lib/mail/parsers/envelope_from_parser.rl +82 -0
  112. data/lib/mail/parsers/message_ids_parser.rb +2844 -25
  113. data/lib/mail/parsers/message_ids_parser.rl +82 -0
  114. data/lib/mail/parsers/mime_version_parser.rb +494 -26
  115. data/lib/mail/parsers/mime_version_parser.rl +61 -0
  116. data/lib/mail/parsers/phrase_lists_parser.rb +876 -18
  117. data/lib/mail/parsers/phrase_lists_parser.rl +83 -0
  118. data/lib/mail/parsers/received_parser.rb +8744 -37
  119. data/lib/mail/parsers/received_parser.rl +84 -0
  120. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  121. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  122. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  123. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  124. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  125. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  126. data/lib/mail/parsers/rfc5322.rl +59 -0
  127. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  128. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  129. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  130. data/lib/mail/parsers.rb +17 -24
  131. data/lib/mail/part.rb +6 -3
  132. data/lib/mail/parts_list.rb +31 -14
  133. data/lib/mail/utilities.rb +107 -8
  134. data/lib/mail/values/unicode_tables.dat +0 -0
  135. data/lib/mail/version.rb +3 -2
  136. data/lib/mail/version_specific/ruby_1_8.rb +50 -6
  137. data/lib/mail/version_specific/ruby_1_9.rb +103 -18
  138. data/lib/mail.rb +5 -12
  139. metadata +33 -57
  140. data/CHANGELOG.rdoc +0 -759
  141. data/CONTRIBUTING.md +0 -60
  142. data/Dependencies.txt +0 -2
  143. data/Gemfile +0 -15
  144. data/Rakefile +0 -29
  145. data/TODO.rdoc +0 -9
  146. data/lib/mail/core_extensions/nil.rb +0 -19
  147. data/lib/mail/core_extensions/object.rb +0 -13
  148. data/lib/mail/core_extensions/string/access.rb +0 -145
  149. data/lib/mail/core_extensions/string/multibyte.rb +0 -78
  150. data/lib/mail/multibyte/exceptions.rb +0 -8
  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 -17
@@ -1,4 +1,6 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/multibyte/unicode'
2
4
 
3
5
  module Mail #:nodoc:
4
6
  module Multibyte #:nodoc:
@@ -39,7 +41,7 @@ module Mail #:nodoc:
39
41
  if RUBY_VERSION >= "1.9"
40
42
  # Creates a new Chars instance by wrapping _string_.
41
43
  def initialize(string)
42
- @wrapped_string = string
44
+ @wrapped_string = string.dup
43
45
  @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
44
46
  end
45
47
  else
@@ -102,7 +104,7 @@ module Mail #:nodoc:
102
104
  # Returns a new Chars object containing the _other_ object concatenated to the string.
103
105
  #
104
106
  # Example:
105
- # ('Café'.mb_chars + ' périferôl').to_s # => "Café périferôl"
107
+ # (Mail::Multibyte.mb_chars('Café') + ' périferôl').to_s # => "Café périferôl"
106
108
  def +(other)
107
109
  chars(@wrapped_string + other)
108
110
  end
@@ -110,7 +112,7 @@ module Mail #:nodoc:
110
112
  # Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
111
113
  #
112
114
  # Example:
113
- # 'Café périferôl'.mb_chars =~ /ô/ # => 12
115
+ # Mail::Multibyte.mb_chars('Café périferôl') =~ /ô/ # => 12
114
116
  def =~(other)
115
117
  translate_offset(@wrapped_string =~ other)
116
118
  end
@@ -118,7 +120,7 @@ module Mail #:nodoc:
118
120
  # Inserts the passed string at specified codepoint offsets.
119
121
  #
120
122
  # Example:
121
- # 'Café'.mb_chars.insert(4, ' périferôl').to_s # => "Café périferôl"
123
+ # Mail::Multibyte.mb_chars('Café').insert(4, ' périferôl').to_s # => "Café périferôl"
122
124
  def insert(offset, fragment)
123
125
  unpacked = Unicode.u_unpack(@wrapped_string)
124
126
  unless offset > unpacked.length
@@ -134,7 +136,7 @@ module Mail #:nodoc:
134
136
  # Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
135
137
  #
136
138
  # Example:
137
- # 'Café'.mb_chars.include?('é') # => true
139
+ # Mail::Multibyte.mb_chars('Café').include?('é') # => true
138
140
  def include?(other)
139
141
  # We have to redefine this method because Enumerable defines it.
140
142
  @wrapped_string.include?(other)
@@ -143,8 +145,8 @@ module Mail #:nodoc:
143
145
  # Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
144
146
  #
145
147
  # Example:
146
- # 'Café périferôl'.mb_chars.index('ô') # => 12
147
- # 'Café périferôl'.mb_chars.index(/\w/u) # => 0
148
+ # Mail::Multibyte.mb_chars('Café périferôl').index('ô') # => 12
149
+ # Mail::Multibyte.mb_chars('Café périferôl').index(/\w/u) # => 0
148
150
  def index(needle, offset=0)
149
151
  wrapped_offset = first(offset).wrapped_string.length
150
152
  index = @wrapped_string.index(needle, wrapped_offset)
@@ -156,8 +158,8 @@ module Mail #:nodoc:
156
158
  # string. Returns +nil+ if _needle_ isn't found.
157
159
  #
158
160
  # Example:
159
- # 'Café périferôl'.mb_chars.rindex('é') # => 6
160
- # 'Café périferôl'.mb_chars.rindex(/\w/u) # => 13
161
+ # Mail::Multibyte.mb_chars('Café périferôl').rindex('é') # => 6
162
+ # Mail::Multibyte.mb_chars('Café périferôl').rindex(/\w/u) # => 13
161
163
  def rindex(needle, offset=nil)
162
164
  offset ||= length
163
165
  wrapped_offset = first(offset).wrapped_string.length
@@ -189,7 +191,7 @@ module Mail #:nodoc:
189
191
  # Returns the codepoint of the first character in the string.
190
192
  #
191
193
  # Example:
192
- # 'こんにちは'.mb_chars.ord # => 12371
194
+ # Mail::Multibyte.mb_chars('こんにちは').ord # => 12371
193
195
  def ord
194
196
  Unicode.u_unpack(@wrapped_string)[0]
195
197
  end
@@ -198,10 +200,10 @@ module Mail #:nodoc:
198
200
  #
199
201
  # Example:
200
202
  #
201
- # "¾ cup".mb_chars.rjust(8).to_s
203
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8).to_s
202
204
  # # => " ¾ cup"
203
205
  #
204
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
206
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8, " ").to_s # Use non-breaking whitespace
205
207
  # # => "   ¾ cup"
206
208
  def rjust(integer, padstr=' ')
207
209
  justify(integer, :right, padstr)
@@ -211,10 +213,10 @@ module Mail #:nodoc:
211
213
  #
212
214
  # Example:
213
215
  #
214
- # "¾ cup".mb_chars.rjust(8).to_s
216
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8).to_s
215
217
  # # => "¾ cup "
216
218
  #
217
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
219
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8, " ").to_s # Use non-breaking whitespace
218
220
  # # => "¾ cup   "
219
221
  def ljust(integer, padstr=' ')
220
222
  justify(integer, :left, padstr)
@@ -224,10 +226,10 @@ module Mail #:nodoc:
224
226
  #
225
227
  # Example:
226
228
  #
227
- # "¾ cup".mb_chars.center(8).to_s
229
+ # Mail::Multibyte.mb_chars("¾ cup").center(8).to_s
228
230
  # # => " ¾ cup "
229
231
  #
230
- # "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace
232
+ # Mail::Multibyte.mb_chars("¾ cup").center(8, " ").to_s # Use non-breaking whitespace
231
233
  # # => " ¾ cup  "
232
234
  def center(integer, padstr=' ')
233
235
  justify(integer, :center, padstr)
@@ -243,7 +245,7 @@ module Mail #:nodoc:
243
245
  # instances instead of String. This makes chaining methods easier.
244
246
  #
245
247
  # Example:
246
- # 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
248
+ # Mail::Multibyte.mb_chars('Café périferôl').split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
247
249
  def split(*args)
248
250
  @wrapped_string.split(*args).map { |i| i.mb_chars }
249
251
  end
@@ -268,12 +270,12 @@ module Mail #:nodoc:
268
270
  @wrapped_string[*args] = replace_by
269
271
  else
270
272
  result = Unicode.u_unpack(@wrapped_string)
271
- if args[0].is_a?(Fixnum)
273
+ if args[0].is_a?(Integer)
272
274
  raise IndexError, "index #{args[0]} out of string" if args[0] >= result.length
273
275
  min = args[0]
274
276
  max = args[1].nil? ? min : (min + args[1] - 1)
275
277
  range = Range.new(min, max)
276
- replace_by = [replace_by].pack('U') if replace_by.is_a?(Fixnum)
278
+ replace_by = [replace_by].pack('U') if replace_by.is_a?(Integer)
277
279
  elsif args.first.is_a?(Range)
278
280
  raise RangeError, "#{args[0]} out of range" if args[0].min >= result.length
279
281
  range = args[0]
@@ -291,7 +293,7 @@ module Mail #:nodoc:
291
293
  # Reverses all characters in the string.
292
294
  #
293
295
  # Example:
294
- # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
296
+ # Mail::Multibyte.mb_chars('Café').reverse.to_s # => 'éfaC'
295
297
  def reverse
296
298
  chars(Unicode.g_unpack(@wrapped_string).reverse.flatten.pack('U*'))
297
299
  end
@@ -300,7 +302,7 @@ module Mail #:nodoc:
300
302
  # character.
301
303
  #
302
304
  # Example:
303
- # 'こんにちは'.mb_chars.slice(2..3).to_s # => "にち"
305
+ # Mail::Multibyte.mb_chars('こんにちは').slice(2..3).to_s # => "にち"
304
306
  def slice(*args)
305
307
  if args.size > 2
306
308
  raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
@@ -337,7 +339,7 @@ module Mail #:nodoc:
337
339
  # Convert characters in the string to uppercase.
338
340
  #
339
341
  # Example:
340
- # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
342
+ # Mail::Multibyte.mb_chars('Laurent, où sont les tests ?').upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
341
343
  def upcase
342
344
  chars(Unicode.apply_mapping(@wrapped_string, :uppercase_mapping))
343
345
  end
@@ -345,7 +347,7 @@ module Mail #:nodoc:
345
347
  # Convert characters in the string to lowercase.
346
348
  #
347
349
  # Example:
348
- # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
350
+ # Mail::Multibyte.mb_chars('VĚDA A VÝZKUM').downcase.to_s # => "věda a výzkum"
349
351
  def downcase
350
352
  chars(Unicode.apply_mapping(@wrapped_string, :lowercase_mapping))
351
353
  end
@@ -353,7 +355,7 @@ module Mail #:nodoc:
353
355
  # Converts the first character to uppercase and the remainder to lowercase.
354
356
  #
355
357
  # Example:
356
- # 'über'.mb_chars.capitalize.to_s # => "Über"
358
+ # Mail::Multibyte.mb_chars('über').capitalize.to_s # => "Über"
357
359
  def capitalize
358
360
  (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
359
361
  end
@@ -361,8 +363,8 @@ module Mail #:nodoc:
361
363
  # Capitalizes the first letter of every word, when possible.
362
364
  #
363
365
  # Example:
364
- # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
365
- # "日本語".mb_chars.titleize # => "日本語"
366
+ # Mail::Multibyte.mb_chars("ÉL QUE SE ENTERÓ").titleize # => "Él Que Se Enteró"
367
+ # Mail::Multibyte.mb_chars("日本語").titleize # => "日本語"
366
368
  def titleize
367
369
  chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.apply_mapping $1, :uppercase_mapping })
368
370
  end
@@ -382,7 +384,7 @@ module Mail #:nodoc:
382
384
  #
383
385
  # Example:
384
386
  # 'é'.length # => 2
385
- # 'é'.mb_chars.decompose.to_s.length # => 3
387
+ # Mail::Multibyte.mb_chars('é').decompose.to_s.length # => 3
386
388
  def decompose
387
389
  chars(Unicode.decompose_codepoints(:canonical, Unicode.u_unpack(@wrapped_string)).pack('U*'))
388
390
  end
@@ -391,7 +393,7 @@ module Mail #:nodoc:
391
393
  #
392
394
  # Example:
393
395
  # 'é'.length # => 3
394
- # 'é'.mb_chars.compose.to_s.length # => 2
396
+ # Mail::Multibyte.mb_chars('é').compose.to_s.length # => 2
395
397
  def compose
396
398
  chars(Unicode.compose_codepoints(Unicode.u_unpack(@wrapped_string)).pack('U*'))
397
399
  end
@@ -399,8 +401,8 @@ module Mail #:nodoc:
399
401
  # Returns the number of grapheme clusters in the string.
400
402
  #
401
403
  # Example:
402
- # 'क्षि'.mb_chars.length # => 4
403
- # 'क्षि'.mb_chars.g_length # => 3
404
+ # Mail::Multibyte.mb_chars('क्षि').length # => 4
405
+ # Mail::Multibyte.mb_chars('क्षि').g_length # => 3
404
406
  def g_length
405
407
  Unicode.g_unpack(@wrapped_string).length
406
408
  end
@@ -1,6 +1,27 @@
1
+ # frozen_string_literal: true
1
2
  module Mail
2
3
  module Multibyte
3
4
  module Unicode
5
+ # Adapted from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/multibyte/unicode.rb
6
+ # under the MIT license
7
+ # The Unicode version that is supported by the implementation
8
+ UNICODE_VERSION = '7.0.0'
9
+
10
+ # Holds data about a codepoint in the Unicode database.
11
+ class Codepoint
12
+ attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping
13
+
14
+ # Initializing Codepoint object with default values
15
+ def initialize
16
+ @combining_class = 0
17
+ @uppercase_mapping = 0
18
+ @lowercase_mapping = 0
19
+ end
20
+
21
+ def swapcase_mapping
22
+ uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping
23
+ end
24
+ end
4
25
 
5
26
  extend self
6
27
 
@@ -8,9 +29,6 @@ module Mail
8
29
  # information about normalization.
9
30
  NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
10
31
 
11
- # The Unicode version that is supported by the implementation
12
- UNICODE_VERSION = '5.2.0'
13
-
14
32
  # The default normalization used for operations that require normalization. It can be set to any of the
15
33
  # normalizations in NORMALIZATION_FORMS.
16
34
  #
@@ -308,11 +326,6 @@ module Mail
308
326
  end.pack('U*')
309
327
  end
310
328
 
311
- # Holds data about a codepoint in the Unicode database
312
- class Codepoint
313
- attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping
314
- end
315
-
316
329
  # Holds static data from the Unicode database
317
330
  class UnicodeDatabase
318
331
  ATTRIBUTES = :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252
@@ -390,11 +403,3 @@ module Mail
390
403
  end
391
404
  end
392
405
  end
393
-
394
- unless defined?(ActiveSupport)
395
- module ActiveSupport
396
- unless const_defined?(:Multibyte)
397
- Multibyte = Mail::Multibyte
398
- end
399
- end
400
- end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Mail #:nodoc:
4
5
  module Multibyte #:nodoc:
@@ -1,23 +1,73 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/multibyte/chars'
4
+
2
5
  module Mail #:nodoc:
3
6
  module Multibyte
4
- require 'mail/multibyte/exceptions'
5
- require 'mail/multibyte/chars'
6
- require 'mail/multibyte/unicode'
7
+ # Raised when a problem with the encoding was found.
8
+ class EncodingError < StandardError; end
7
9
 
8
- # The proxy class returned when calling mb_chars. You can use this accessor to configure your own proxy
9
- # class so you can support other encodings. See the Mail::Multibyte::Chars implementation for
10
- # an example how to do this.
11
- #
12
- # Example:
13
- # Mail::Multibyte.proxy_class = CharsForUTF32
14
- def self.proxy_class=(klass)
15
- @proxy_class = klass
10
+ class << self
11
+ # The proxy class returned when calling mb_chars. You can use this accessor to configure your own proxy
12
+ # class so you can support other encodings. See the Mail::Multibyte::Chars implementation for
13
+ # an example how to do this.
14
+ #
15
+ # Example:
16
+ # Mail::Multibyte.proxy_class = CharsForUTF32
17
+ attr_accessor :proxy_class
16
18
  end
17
19
 
18
- # Returns the current proxy class
19
- def self.proxy_class
20
- @proxy_class ||= Mail::Multibyte::Chars
20
+ self.proxy_class = Mail::Multibyte::Chars
21
+
22
+ if RUBY_VERSION >= "1.9"
23
+ # == Multibyte proxy
24
+ #
25
+ # +mb_chars+ is a multibyte safe proxy for string methods.
26
+ #
27
+ # In Ruby 1.8 and older it creates and returns an instance of the Mail::Multibyte::Chars class which
28
+ # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
29
+ # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsuled string.
30
+ #
31
+ # name = 'Claus Müller'
32
+ # name.reverse # => "rell??M sualC"
33
+ # name.length # => 13
34
+ #
35
+ # name.mb_chars.reverse.to_s # => "rellüM sualC"
36
+ # name.mb_chars.length # => 12
37
+ #
38
+ # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
39
+ # it becomes easy to run one version of your code on multiple Ruby versions.
40
+ #
41
+ # == Method chaining
42
+ #
43
+ # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
44
+ # method chaining on the result of any of these methods.
45
+ #
46
+ # name.mb_chars.reverse.length # => 12
47
+ #
48
+ # == Interoperability and configuration
49
+ #
50
+ # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
51
+ # String and Char work like expected. The bang! methods change the internal string representation in the Chars
52
+ # object. Interoperability problems can be resolved easily with a +to_s+ call.
53
+ #
54
+ # For more information about the methods defined on the Chars proxy see Mail::Multibyte::Chars. For
55
+ # information about how to change the default Multibyte behaviour see Mail::Multibyte.
56
+ def self.mb_chars(str)
57
+ if proxy_class.consumes?(str)
58
+ proxy_class.new(str)
59
+ else
60
+ str
61
+ end
62
+ end
63
+ else
64
+ def self.mb_chars(str)
65
+ if proxy_class.wants?(str)
66
+ proxy_class.new(str)
67
+ else
68
+ str
69
+ end
70
+ end
21
71
  end
22
72
 
23
73
  # Regular expressions that describe valid byte sequences for a character
@@ -39,4 +89,4 @@ module Mail #:nodoc:
39
89
  end
40
90
  end
41
91
 
42
- require 'mail/multibyte/utils'
92
+ require 'mail/multibyte/utils'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Mail
2
3
 
3
4
  # A delivery method implementation which sends via exim.
@@ -36,17 +37,13 @@ module Mail
36
37
  #
37
38
  # mail.deliver!
38
39
  class Exim < Sendmail
39
- def initialize(values)
40
- self.settings = { :location => '/usr/sbin/exim',
41
- :arguments => '-i -t' }.merge(values)
42
- end
40
+ DEFAULTS = {
41
+ :location => '/usr/sbin/exim',
42
+ :arguments => '-i -t'
43
+ }
43
44
 
44
- def self.call(path, arguments, destinations, mail)
45
- popen "#{path} #{arguments}" do |io|
46
- io.puts mail.encoded.to_lf
47
- io.flush
48
- end
45
+ def self.call(path, arguments, destinations, encoded_message)
46
+ super path, arguments, nil, encoded_message
49
47
  end
50
-
51
48
  end
52
49
  end
@@ -1,7 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  require 'mail/check_delivery_params'
2
3
 
3
4
  module Mail
4
-
5
5
  # FileDelivery class delivers emails into multiple files based on the destination
6
6
  # address. Each file is appended to if it already exists.
7
7
  #
@@ -13,22 +13,20 @@ module Mail
13
13
  # Make sure the path you specify with :location is writable by the Ruby process
14
14
  # running Mail.
15
15
  class FileDelivery
16
- include Mail::CheckDeliveryParams
17
-
18
16
  if RUBY_VERSION >= '1.9.1'
19
17
  require 'fileutils'
20
18
  else
21
19
  require 'ftools'
22
20
  end
23
21
 
22
+ attr_accessor :settings
23
+
24
24
  def initialize(values)
25
25
  self.settings = { :location => './mails' }.merge!(values)
26
26
  end
27
-
28
- attr_accessor :settings
29
-
27
+
30
28
  def deliver!(mail)
31
- check_delivery_params(mail)
29
+ Mail::CheckDeliveryParams.check(mail)
32
30
 
33
31
  if ::File.respond_to?(:makedirs)
34
32
  ::File.makedirs settings[:location]
@@ -40,6 +38,5 @@ module Mail
40
38
  ::File.open(::File.join(settings[:location], File.basename(to.to_s)), 'a') { |f| "#{f.write(mail.encoded)}\r\n\r\n" }
41
39
  end
42
40
  end
43
-
44
41
  end
45
42
  end
@@ -0,0 +1,37 @@
1
+ require 'mail/check_delivery_params'
2
+
3
+ module Mail
4
+ class LoggerDelivery
5
+ include Mail::CheckDeliveryParams
6
+
7
+ attr_reader :logger, :severity, :settings
8
+
9
+ def initialize(settings)
10
+ @settings = settings
11
+ @logger = settings.fetch(:logger) { default_logger }
12
+ @severity = derive_severity(settings[:severity])
13
+ end
14
+
15
+ def deliver!(mail)
16
+ Mail::CheckDeliveryParams.check(mail)
17
+ logger.log(severity) { mail.encoded }
18
+ end
19
+
20
+ private
21
+ def default_logger
22
+ require 'logger'
23
+ ::Logger.new($stdout)
24
+ end
25
+
26
+ def derive_severity(severity)
27
+ case severity
28
+ when nil
29
+ Logger::INFO
30
+ when Integer
31
+ severity
32
+ else
33
+ Logger.const_get(severity.to_s.upcase)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'mail/check_delivery_params'
2
3
 
3
4
  module Mail
@@ -37,19 +38,21 @@ module Mail
37
38
  #
38
39
  # mail.deliver!
39
40
  class Sendmail
40
- include Mail::CheckDeliveryParams
41
+ DEFAULTS = {
42
+ :location => '/usr/sbin/sendmail',
43
+ :arguments => '-i'
44
+ }
45
+
46
+ attr_accessor :settings
41
47
 
42
48
  def initialize(values)
43
- self.settings = { :location => '/usr/sbin/sendmail',
44
- :arguments => '-i' }.merge(values)
49
+ self.settings = self.class::DEFAULTS.merge(values)
45
50
  end
46
51
 
47
- attr_accessor :settings
48
-
49
52
  def deliver!(mail)
50
- smtp_from, smtp_to, message = check_delivery_params(mail)
53
+ smtp_from, smtp_to, message = Mail::CheckDeliveryParams.check(mail)
51
54
 
52
- from = "-f #{self.class.shellquote(smtp_from)}"
55
+ from = "-f #{self.class.shellquote(smtp_from)}" if smtp_from
53
56
  to = smtp_to.map { |_to| self.class.shellquote(_to) }.join(' ')
54
57
 
55
58
  arguments = "#{settings[:arguments]} #{from} --"
@@ -58,7 +61,7 @@ module Mail
58
61
 
59
62
  def self.call(path, arguments, destinations, encoded_message)
60
63
  popen "#{path} #{arguments} #{destinations}" do |io|
61
- io.puts encoded_message.to_lf
64
+ io.puts ::Mail::Utilities.binary_unsafe_to_lf(encoded_message)
62
65
  io.flush
63
66
  end
64
67
  end
@@ -74,15 +77,18 @@ module Mail
74
77
  end
75
78
 
76
79
  # The following is an adaptation of ruby 1.9.2's shellwords.rb file,
77
- # it is modified to include '+' in the allowed list to allow for
78
- # sendmail to accept email addresses as the sender with a + in them.
80
+ # with the following modifications:
81
+ #
82
+ # - Wraps in double quotes
83
+ # - Allows '+' to accept email addresses with them
84
+ # - Allows '~' as it is not unescaped in double quotes
79
85
  def self.shellquote(address)
80
86
  # Process as a single byte sequence because not all shell
81
87
  # implementations are multibyte aware.
82
88
  #
83
89
  # A LF cannot be escaped with a backslash because a backslash + LF
84
90
  # combo is regarded as line continuation and simply ignored. Strip it.
85
- escaped = address.gsub(/([^A-Za-z0-9_\s\+\-.,:\/@])/n, "\\\\\\1").gsub("\n", '')
91
+ escaped = address.gsub(/([^A-Za-z0-9_\s\+\-.,:\/@~])/n, "\\\\\\1").gsub("\n", '')
86
92
  %("#{escaped}")
87
93
  end
88
94
  end