mail 2.6.1 → 2.7.1

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/MIT-LICENSE +1 -1
  3. data/README.md +92 -80
  4. data/lib/mail/attachments_list.rb +11 -5
  5. data/lib/mail/body.rb +81 -44
  6. data/lib/mail/check_delivery_params.rb +50 -10
  7. data/lib/mail/configuration.rb +3 -0
  8. data/lib/mail/{patterns.rb → constants.rb} +26 -6
  9. data/lib/mail/core_extensions/smtp.rb +20 -16
  10. data/lib/mail/core_extensions/string.rb +1 -27
  11. data/lib/mail/elements/address.rb +81 -93
  12. data/lib/mail/elements/address_list.rb +12 -29
  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 +20 -0
  32. data/lib/mail/encodings.rb +121 -82
  33. data/lib/mail/envelope.rb +2 -1
  34. data/lib/mail/field.rb +114 -62
  35. data/lib/mail/field_list.rb +2 -1
  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 +6 -11
  43. data/lib/mail/fields/common/common_message_id.rb +3 -2
  44. data/lib/mail/fields/common/parameter_hash.rb +5 -4
  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 +7 -11
  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 +28 -10
  74. data/lib/mail/fields.rb +1 -0
  75. data/lib/mail/header.rb +18 -14
  76. data/lib/mail/indifferent_hash.rb +1 -0
  77. data/lib/mail/mail.rb +6 -11
  78. data/lib/mail/matchers/attachment_matchers.rb +29 -0
  79. data/lib/mail/matchers/has_sent_mail.rb +53 -9
  80. data/lib/mail/message.rb +99 -89
  81. data/lib/mail/multibyte/chars.rb +32 -30
  82. data/lib/mail/multibyte/unicode.rb +31 -26
  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 +33208 -104
  99. data/lib/mail/parsers/address_lists_parser.rl +172 -0
  100. data/lib/mail/parsers/content_disposition_parser.rb +877 -49
  101. data/lib/mail/parsers/content_disposition_parser.rl +82 -0
  102. data/lib/mail/parsers/content_location_parser.rb +804 -23
  103. data/lib/mail/parsers/content_location_parser.rl +71 -0
  104. data/lib/mail/parsers/content_transfer_encoding_parser.rb +502 -19
  105. data/lib/mail/parsers/content_transfer_encoding_parser.rl +64 -0
  106. data/lib/mail/parsers/content_type_parser.rb +1024 -46
  107. data/lib/mail/parsers/content_type_parser.rl +83 -0
  108. data/lib/mail/parsers/date_time_parser.rb +872 -23
  109. data/lib/mail/parsers/date_time_parser.rl +62 -0
  110. data/lib/mail/parsers/envelope_from_parser.rb +3570 -34
  111. data/lib/mail/parsers/envelope_from_parser.rl +82 -0
  112. data/lib/mail/parsers/message_ids_parser.rb +2840 -25
  113. data/lib/mail/parsers/message_ids_parser.rl +82 -0
  114. data/lib/mail/parsers/mime_version_parser.rb +492 -26
  115. data/lib/mail/parsers/mime_version_parser.rl +61 -0
  116. data/lib/mail/parsers/phrase_lists_parser.rb +862 -17
  117. data/lib/mail/parsers/phrase_lists_parser.rl +83 -0
  118. data/lib/mail/parsers/received_parser.rb +8765 -36
  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 +8 -5
  132. data/lib/mail/parts_list.rb +31 -14
  133. data/lib/mail/utilities.rb +112 -13
  134. data/lib/mail/values/unicode_tables.dat +0 -0
  135. data/lib/mail/version.rb +8 -15
  136. data/lib/mail/version_specific/ruby_1_8.rb +52 -8
  137. data/lib/mail/version_specific/ruby_1_9.rb +143 -24
  138. data/lib/mail.rb +8 -14
  139. metadata +71 -81
  140. data/CHANGELOG.rdoc +0 -752
  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/VERSION +0 -4
  147. data/lib/mail/core_extensions/nil.rb +0 -19
  148. data/lib/mail/core_extensions/object.rb +0 -13
  149. data/lib/mail/core_extensions/string/access.rb +0 -145
  150. data/lib/mail/core_extensions/string/multibyte.rb +0 -78
  151. data/lib/mail/multibyte/exceptions.rb +0 -8
  152. data/lib/mail/parsers/ragel/common.rl +0 -184
  153. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  154. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  155. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  156. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  157. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  158. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  159. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  160. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  161. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  162. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  163. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  164. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  165. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  166. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2129
  167. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  168. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  169. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  170. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  171. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  172. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  173. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  174. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  175. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  176. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  177. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  178. data/lib/mail/parsers/ragel/ruby.rb +0 -39
  179. data/lib/mail/parsers/ragel.rb +0 -17
@@ -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
  #
@@ -284,16 +302,16 @@ module Mail
284
302
  # See http://www.unicode.org/reports/tr15, Table 1
285
303
  codepoints = u_unpack(string)
286
304
  case form
287
- when :d
288
- reorder_characters(decompose_codepoints(:canonical, codepoints))
289
- when :c
290
- compose_codepoints(reorder_characters(decompose_codepoints(:canonical, codepoints)))
291
- when :kd
292
- reorder_characters(decompose_codepoints(:compatability, codepoints))
293
- when :kc
294
- compose_codepoints(reorder_characters(decompose_codepoints(:compatability, codepoints)))
295
- else
296
- raise ArgumentError, "#{form} is not a valid normalization variant", caller
305
+ when :d
306
+ reorder_characters(decompose_codepoints(:canonical, codepoints))
307
+ when :c
308
+ compose_codepoints(reorder_characters(decompose_codepoints(:canonical, codepoints)))
309
+ when :kd
310
+ reorder_characters(decompose_codepoints(:compatability, codepoints))
311
+ when :kc
312
+ compose_codepoints(reorder_characters(decompose_codepoints(:compatability, codepoints)))
313
+ else
314
+ raise ArgumentError, "#{form} is not a valid normalization variant", caller
297
315
  end.pack('U*')
298
316
  end
299
317
 
@@ -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
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'mail/check_delivery_params'
2
3
 
3
4
  module Mail
@@ -44,9 +45,8 @@ module Mail
44
45
  # hostname or update the certificate authorities trusted by your ruby. If
45
46
  # that isn't possible, you can control this behavior with
46
47
  # an :openssl_verify_mode setting. Its value may be either an OpenSSL
47
- # verify mode constant (OpenSSL::SSL::VERIFY_NONE), or a string containing
48
- # the name of an OpenSSL verify mode (none, peer, client_once,
49
- # fail_if_no_peer_cert).
48
+ # verify mode constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER),
49
+ # or a string containing the name of an OpenSSL verify mode (none, peer).
50
50
  #
51
51
  # === Others
52
52
  #
@@ -74,69 +74,76 @@ module Mail
74
74
  #
75
75
  # mail.deliver!
76
76
  class SMTP
77
- include Mail::CheckDeliveryParams
77
+ attr_accessor :settings
78
+
79
+ DEFAULTS = {
80
+ :address => 'localhost',
81
+ :port => 25,
82
+ :domain => 'localhost.localdomain',
83
+ :user_name => nil,
84
+ :password => nil,
85
+ :authentication => nil,
86
+ :enable_starttls => nil,
87
+ :enable_starttls_auto => true,
88
+ :openssl_verify_mode => nil,
89
+ :ssl => nil,
90
+ :tls => nil,
91
+ :open_timeout => nil,
92
+ :read_timeout => nil
93
+ }
78
94
 
79
95
  def initialize(values)
80
- self.settings = { :address => "localhost",
81
- :port => 25,
82
- :domain => 'localhost.localdomain',
83
- :user_name => nil,
84
- :password => nil,
85
- :authentication => nil,
86
- :enable_starttls_auto => true,
87
- :openssl_verify_mode => nil,
88
- :ssl => nil,
89
- :tls => nil
90
- }.merge!(values)
96
+ self.settings = DEFAULTS.merge(values)
91
97
  end
92
98
 
93
- attr_accessor :settings
94
-
95
- # Send the message via SMTP.
96
- # The from and to attributes are optional. If not set, they are retrieve from the Message.
97
99
  def deliver!(mail)
98
- smtp_from, smtp_to, message = check_delivery_params(mail)
99
-
100
- smtp = Net::SMTP.new(settings[:address], settings[:port])
101
- if settings[:tls] || settings[:ssl]
102
- if smtp.respond_to?(:enable_tls)
103
- smtp.enable_tls(ssl_context)
104
- end
105
- elsif settings[:enable_starttls_auto]
106
- if smtp.respond_to?(:enable_starttls_auto)
107
- smtp.enable_starttls_auto(ssl_context)
108
- end
109
- end
110
-
111
- response = nil
112
- smtp.start(settings[:domain], settings[:user_name], settings[:password], settings[:authentication]) do |smtp_obj|
113
- response = smtp_obj.sendmail(message, smtp_from, smtp_to)
100
+ response = start_smtp_session do |smtp|
101
+ Mail::SMTPConnection.new(:connection => smtp, :return_response => true).deliver!(mail)
114
102
  end
115
103
 
116
- if settings[:return_response]
117
- response
118
- else
119
- self
120
- end
104
+ settings[:return_response] ? response : self
121
105
  end
122
-
123
106
 
124
107
  private
108
+ def start_smtp_session(&block)
109
+ build_smtp_session.start(settings[:domain], settings[:user_name], settings[:password], settings[:authentication], &block)
110
+ end
125
111
 
126
- # Allow SSL context to be configured via settings, for Ruby >= 1.9
127
- # Just returns openssl verify mode for Ruby 1.8.x
128
- def ssl_context
129
- openssl_verify_mode = settings[:openssl_verify_mode]
112
+ def build_smtp_session
113
+ Net::SMTP.new(settings[:address], settings[:port]).tap do |smtp|
114
+ if settings[:tls] || settings[:ssl]
115
+ if smtp.respond_to?(:enable_tls)
116
+ smtp.enable_tls(ssl_context)
117
+ end
118
+ elsif settings[:enable_starttls]
119
+ if smtp.respond_to?(:enable_starttls)
120
+ smtp.enable_starttls(ssl_context)
121
+ end
122
+ elsif settings[:enable_starttls_auto]
123
+ if smtp.respond_to?(:enable_starttls_auto)
124
+ smtp.enable_starttls_auto(ssl_context)
125
+ end
126
+ end
130
127
 
131
- if openssl_verify_mode.kind_of?(String)
132
- openssl_verify_mode = "OpenSSL::SSL::VERIFY_#{openssl_verify_mode.upcase}".constantize
128
+ smtp.open_timeout = settings[:open_timeout] if settings[:open_timeout]
129
+ smtp.read_timeout = settings[:read_timeout] if settings[:read_timeout]
130
+ end
133
131
  end
134
132
 
135
- context = Net::SMTP.default_ssl_context
136
- context.verify_mode = openssl_verify_mode
137
- context.ca_path = settings[:ca_path] if settings[:ca_path]
138
- context.ca_file = settings[:ca_file] if settings[:ca_file]
139
- context
140
- end
133
+ # Allow SSL context to be configured via settings, for Ruby >= 1.9
134
+ # Just returns openssl verify mode for Ruby 1.8.x
135
+ def ssl_context
136
+ openssl_verify_mode = settings[:openssl_verify_mode]
137
+
138
+ if openssl_verify_mode.kind_of?(String)
139
+ openssl_verify_mode = OpenSSL::SSL.const_get("VERIFY_#{openssl_verify_mode.upcase}")
140
+ end
141
+
142
+ context = Net::SMTP.default_ssl_context
143
+ context.verify_mode = openssl_verify_mode if openssl_verify_mode
144
+ context.ca_path = settings[:ca_path] if settings[:ca_path]
145
+ context.ca_file = settings[:ca_file] if settings[:ca_file]
146
+ context
147
+ end
141
148
  end
142
149
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'mail/check_delivery_params'
2
3
 
3
4
  module Mail
@@ -37,7 +38,7 @@ module Mail
37
38
  #
38
39
  # mail.deliver!
39
40
  class SMTPConnection
40
- include Mail::CheckDeliveryParams
41
+ attr_accessor :smtp, :settings
41
42
 
42
43
  def initialize(values)
43
44
  raise ArgumentError.new('A Net::SMTP object is required for this delivery method') if values[:connection].nil?
@@ -45,17 +46,21 @@ module Mail
45
46
  self.settings = values
46
47
  end
47
48
 
48
- attr_accessor :smtp
49
- attr_accessor :settings
50
-
51
49
  # Send the message via SMTP.
52
50
  # The from and to attributes are optional. If not set, they are retrieve from the Message.
53
51
  def deliver!(mail)
54
- smtp_from, smtp_to, message = check_delivery_params(mail)
55
- response = smtp.sendmail(message, smtp_from, smtp_to)
52
+ smtp_from, smtp_to, message = Mail::CheckDeliveryParams.check(mail)
53
+
54
+ response = smtp.sendmail(dot_stuff(message), smtp_from, smtp_to)
56
55
 
57
56
  settings[:return_response] ? response : self
58
57
  end
59
58
 
59
+ private
60
+ # This is Net::SMTP's job, but before Ruby 2.x it does not dot-stuff
61
+ # an unterminated last line: https://bugs.ruby-lang.org/issues/9627
62
+ def dot_stuff(message)
63
+ message.gsub(/(\r\n\.)(\r\n|$)/, '\1.\2')
64
+ end
60
65
  end
61
66
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'mail/check_delivery_params'
2
3
 
3
4
  module Mail
@@ -7,10 +8,8 @@ module Mail
7
8
  # It also provides a template of the minimum methods you require to implement
8
9
  # if you want to make a custom mailer for Mail
9
10
  class TestMailer
10
- include Mail::CheckDeliveryParams
11
-
12
11
  # Provides a store of all the emails sent with the TestMailer so you can check them.
13
- def TestMailer.deliveries
12
+ def self.deliveries
14
13
  @@deliveries ||= []
15
14
  end
16
15
 
@@ -25,20 +24,19 @@ module Mail
25
24
  # * length
26
25
  # * size
27
26
  # * and other common Array methods
28
- def TestMailer.deliveries=(val)
27
+ def self.deliveries=(val)
29
28
  @@deliveries = val
30
29
  end
31
30
 
31
+ attr_accessor :settings
32
+
32
33
  def initialize(values)
33
34
  @settings = values.dup
34
35
  end
35
-
36
- attr_accessor :settings
37
36
 
38
37
  def deliver!(mail)
39
- check_delivery_params(mail)
38
+ Mail::CheckDeliveryParams.check(mail)
40
39
  Mail::TestMailer.deliveries << mail
41
40
  end
42
-
43
41
  end
44
42
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Mail
4
5
 
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Mail
4
5
  # The IMAP retriever allows to get the last, first or all emails from a IMAP server.
@@ -44,7 +45,8 @@ module Mail
44
45
  :user_name => nil,
45
46
  :password => nil,
46
47
  :authentication => nil,
47
- :enable_ssl => false }.merge!(values)
48
+ :enable_ssl => false,
49
+ :enable_starttls => false }.merge!(values)
48
50
  end
49
51
 
50
52
  attr_accessor :settings
@@ -66,14 +68,14 @@ module Mail
66
68
  # keys: are passed as criteria to the SEARCH command. They can either be a string holding the entire search string,
67
69
  # or a single-dimension array of search keywords and arguments. Refer to [IMAP] section 6.4.4 for a full list
68
70
  # The default is 'ALL'
71
+ # search_charset: charset to pass to IMAP server search. Omitted by default. Example: 'UTF-8' or 'ASCII'.
69
72
  #
70
73
  def find(options={}, &block)
71
74
  options = validate_options(options)
72
75
 
73
76
  start do |imap|
74
77
  options[:read_only] ? imap.examine(options[:mailbox]) : imap.select(options[:mailbox])
75
-
76
- uids = imap.uid_search(options[:keys])
78
+ uids = imap.uid_search(options[:keys], options[:search_charset])
77
79
  uids.reverse! if options[:what].to_sym == :last
78
80
  uids = uids.first(options[:count]) if options[:count].is_a?(Integer)
79
81
  uids.reverse! if (options[:what].to_sym == :last && options[:order].to_sym == :asc) ||
@@ -82,14 +84,18 @@ module Mail
82
84
  if block_given?
83
85
  uids.each do |uid|
84
86
  uid = options[:uid].to_i unless options[:uid].nil?
85
- fetchdata = imap.uid_fetch(uid, ['RFC822'])[0]
87
+ fetchdata = imap.uid_fetch(uid, ['RFC822', 'FLAGS'])[0]
86
88
  new_message = Mail.new(fetchdata.attr['RFC822'])
87
89
  new_message.mark_for_delete = true if options[:delete_after_find]
88
- if block.arity == 3
90
+
91
+ if block.arity == 4
92
+ yield new_message, imap, uid, fetchdata.attr['FLAGS']
93
+ elsif block.arity == 3
89
94
  yield new_message, imap, uid
90
95
  else
91
96
  yield new_message
92
97
  end
98
+
93
99
  imap.uid_store(uid, "+FLAGS", [Net::IMAP::DELETED]) if options[:delete_after_find] && new_message.is_marked_for_delete?
94
100
  break unless options[:uid].nil?
95
101
  end
@@ -115,6 +121,7 @@ module Mail
115
121
  mailbox = Net::IMAP.encode_utf7(mailbox)
116
122
 
117
123
  start do |imap|
124
+ imap.select(mailbox)
118
125
  imap.uid_search(['ALL']).each do |uid|
119
126
  imap.uid_store(uid, "+FLAGS", [Net::IMAP::DELETED])
120
127
  end
@@ -153,7 +160,14 @@ module Mail
153
160
  def start(config=Mail::Configuration.instance, &block)
154
161
  raise ArgumentError.new("Mail::Retrievable#imap_start takes a block") unless block_given?
155
162
 
163
+ if settings[:enable_starttls] && settings[:enable_ssl]
164
+ raise ArgumentError, ":enable_starttls and :enable_ssl are mutually exclusive. Set :enable_ssl if you're on an IMAPS connection. Set :enable_starttls if you're on an IMAP connection and using STARTTLS for secure TLS upgrade."
165
+ end
166
+
156
167
  imap = Net::IMAP.new(settings[:address], settings[:port], settings[:enable_ssl], nil, false)
168
+
169
+ imap.starttls if settings[:enable_starttls]
170
+
157
171
  if settings[:authentication].nil?
158
172
  imap.login(settings[:user_name], settings[:password])
159
173
  else