mail 2.7.1 → 2.8.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -28
  3. data/lib/mail/attachments_list.rb +2 -5
  4. data/lib/mail/body.rb +24 -47
  5. data/lib/mail/check_delivery_params.rb +21 -16
  6. data/lib/mail/constants.rb +27 -5
  7. data/lib/mail/elements/address.rb +27 -27
  8. data/lib/mail/elements/address_list.rb +1 -1
  9. data/lib/mail/elements/content_disposition_element.rb +1 -1
  10. data/lib/mail/elements/content_location_element.rb +1 -1
  11. data/lib/mail/elements/content_transfer_encoding_element.rb +1 -1
  12. data/lib/mail/elements/content_type_element.rb +8 -4
  13. data/lib/mail/elements/date_time_element.rb +1 -1
  14. data/lib/mail/elements/envelope_from_element.rb +13 -7
  15. data/lib/mail/elements/message_ids_element.rb +14 -5
  16. data/lib/mail/elements/mime_version_element.rb +1 -1
  17. data/lib/mail/elements/phrase_list.rb +7 -2
  18. data/lib/mail/elements/received_element.rb +20 -6
  19. data/lib/mail/encodings/7bit.rb +5 -0
  20. data/lib/mail/encodings/base64.rb +2 -2
  21. data/lib/mail/encodings/quoted_printable.rb +2 -2
  22. data/lib/mail/encodings.rb +30 -59
  23. data/lib/mail/envelope.rb +11 -14
  24. data/lib/mail/field.rb +37 -53
  25. data/lib/mail/field_list.rb +60 -7
  26. data/lib/mail/fields/bcc_field.rb +34 -52
  27. data/lib/mail/fields/cc_field.rb +28 -49
  28. data/lib/mail/fields/comments_field.rb +27 -37
  29. data/lib/mail/fields/common_address_field.rb +170 -0
  30. data/lib/mail/fields/common_date_field.rb +58 -0
  31. data/lib/mail/fields/common_field.rb +77 -0
  32. data/lib/mail/fields/common_message_id_field.rb +42 -0
  33. data/lib/mail/fields/content_description_field.rb +7 -14
  34. data/lib/mail/fields/content_disposition_field.rb +13 -38
  35. data/lib/mail/fields/content_id_field.rb +24 -51
  36. data/lib/mail/fields/content_location_field.rb +11 -25
  37. data/lib/mail/fields/content_transfer_encoding_field.rb +31 -31
  38. data/lib/mail/fields/content_type_field.rb +46 -71
  39. data/lib/mail/fields/date_field.rb +23 -51
  40. data/lib/mail/fields/from_field.rb +28 -49
  41. data/lib/mail/fields/in_reply_to_field.rb +38 -49
  42. data/lib/mail/fields/keywords_field.rb +18 -31
  43. data/lib/mail/fields/message_id_field.rb +25 -71
  44. data/lib/mail/fields/mime_version_field.rb +19 -30
  45. data/lib/mail/fields/named_structured_field.rb +11 -0
  46. data/lib/mail/fields/named_unstructured_field.rb +11 -0
  47. data/lib/mail/fields/optional_field.rb +5 -6
  48. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +12 -10
  49. data/lib/mail/fields/received_field.rb +43 -57
  50. data/lib/mail/fields/references_field.rb +35 -49
  51. data/lib/mail/fields/reply_to_field.rb +28 -49
  52. data/lib/mail/fields/resent_bcc_field.rb +28 -49
  53. data/lib/mail/fields/resent_cc_field.rb +28 -49
  54. data/lib/mail/fields/resent_date_field.rb +5 -29
  55. data/lib/mail/fields/resent_from_field.rb +28 -49
  56. data/lib/mail/fields/resent_message_id_field.rb +5 -29
  57. data/lib/mail/fields/resent_sender_field.rb +27 -56
  58. data/lib/mail/fields/resent_to_field.rb +28 -49
  59. data/lib/mail/fields/return_path_field.rb +50 -54
  60. data/lib/mail/fields/sender_field.rb +34 -55
  61. data/lib/mail/fields/structured_field.rb +3 -30
  62. data/lib/mail/fields/subject_field.rb +9 -11
  63. data/lib/mail/fields/to_field.rb +28 -49
  64. data/lib/mail/fields/unstructured_field.rb +16 -48
  65. data/lib/mail/header.rb +69 -110
  66. data/lib/mail/matchers/attachment_matchers.rb +15 -0
  67. data/lib/mail/message.rb +52 -66
  68. data/lib/mail/multibyte/chars.rb +8 -166
  69. data/lib/mail/multibyte/utils.rb +26 -43
  70. data/lib/mail/multibyte.rb +1 -11
  71. data/lib/mail/network/delivery_methods/exim.rb +5 -4
  72. data/lib/mail/network/delivery_methods/file_delivery.rb +11 -10
  73. data/lib/mail/network/delivery_methods/logger_delivery.rb +2 -5
  74. data/lib/mail/network/delivery_methods/sendmail.rb +27 -35
  75. data/lib/mail/network/delivery_methods/smtp.rb +25 -9
  76. data/lib/mail/network/delivery_methods/smtp_connection.rb +3 -12
  77. data/lib/mail/network/delivery_methods/test_mailer.rb +4 -2
  78. data/lib/mail/network/retriever_methods/base.rb +8 -8
  79. data/lib/mail/network/retriever_methods/imap.rb +2 -2
  80. data/lib/mail/network/retriever_methods/pop3.rb +2 -2
  81. data/lib/mail/network/retriever_methods/test_retriever.rb +2 -1
  82. data/lib/mail/parsers/address_lists_parser.rb +33070 -33064
  83. data/lib/mail/parsers/address_lists_parser.rl +7 -0
  84. data/lib/mail/parsers/content_disposition_parser.rb +833 -827
  85. data/lib/mail/parsers/content_disposition_parser.rl +7 -0
  86. data/lib/mail/parsers/content_location_parser.rb +770 -764
  87. data/lib/mail/parsers/content_location_parser.rl +7 -0
  88. data/lib/mail/parsers/content_transfer_encoding_parser.rb +474 -468
  89. data/lib/mail/parsers/content_transfer_encoding_parser.rl +7 -0
  90. data/lib/mail/parsers/content_type_parser.rb +971 -965
  91. data/lib/mail/parsers/content_type_parser.rl +7 -0
  92. data/lib/mail/parsers/date_time_parser.rb +838 -832
  93. data/lib/mail/parsers/date_time_parser.rl +7 -0
  94. data/lib/mail/parsers/envelope_from_parser.rb +3623 -3529
  95. data/lib/mail/parsers/envelope_from_parser.rl +7 -0
  96. data/lib/mail/parsers/message_ids_parser.rb +5107 -2800
  97. data/lib/mail/parsers/message_ids_parser.rl +12 -1
  98. data/lib/mail/parsers/mime_version_parser.rb +463 -457
  99. data/lib/mail/parsers/mime_version_parser.rl +7 -0
  100. data/lib/mail/parsers/phrase_lists_parser.rb +836 -830
  101. data/lib/mail/parsers/phrase_lists_parser.rl +8 -1
  102. data/lib/mail/parsers/received_parser.rb +8688 -8682
  103. data/lib/mail/parsers/received_parser.rl +7 -0
  104. data/lib/mail/parsers/rfc5322.rl +28 -13
  105. data/lib/mail/parsers.rb +11 -17
  106. data/lib/mail/part.rb +5 -9
  107. data/lib/mail/parts_list.rb +57 -0
  108. data/lib/mail/smtp_envelope.rb +57 -0
  109. data/lib/mail/utilities.rb +307 -69
  110. data/lib/mail/version.rb +3 -3
  111. data/lib/mail/yaml.rb +30 -0
  112. data/lib/mail.rb +3 -20
  113. metadata +72 -18
  114. data/lib/mail/core_extensions/smtp.rb +0 -28
  115. data/lib/mail/core_extensions/string.rb +0 -17
  116. data/lib/mail/fields/common/address_container.rb +0 -17
  117. data/lib/mail/fields/common/common_address.rb +0 -161
  118. data/lib/mail/fields/common/common_date.rb +0 -36
  119. data/lib/mail/fields/common/common_field.rb +0 -52
  120. data/lib/mail/fields/common/common_message_id.rb +0 -49
  121. data/lib/mail/version_specific/ruby_1_8.rb +0 -163
  122. data/lib/mail/version_specific/ruby_1_9.rb +0 -278
@@ -38,16 +38,10 @@ module Mail #:nodoc:
38
38
  alias to_s wrapped_string
39
39
  alias to_str wrapped_string
40
40
 
41
- if RUBY_VERSION >= "1.9"
42
- # Creates a new Chars instance by wrapping _string_.
43
- def initialize(string)
44
- @wrapped_string = string.dup
45
- @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
46
- end
47
- else
48
- def initialize(string) #:nodoc:
49
- @wrapped_string = string
50
- end
41
+ # Creates a new Chars instance by wrapping _string_.
42
+ def initialize(string)
43
+ @wrapped_string = string.dup
44
+ @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
51
45
  end
52
46
 
53
47
  # Forward all undefined methods to the wrapped string.
@@ -72,15 +66,6 @@ module Mail #:nodoc:
72
66
  true
73
67
  end
74
68
 
75
- # Returns +true+ when the proxy class can handle the string. Returns +false+ otherwise.
76
- def self.consumes?(string)
77
- # Unpack is a little bit faster than regular expressions.
78
- string.unpack('U*')
79
- true
80
- rescue ArgumentError
81
- false
82
- end
83
-
84
69
  include Comparable
85
70
 
86
71
  # Returns -1, 0, or 1, depending on whether the Chars object is to be sorted before,
@@ -94,151 +79,8 @@ module Mail #:nodoc:
94
79
  @wrapped_string <=> other.to_s
95
80
  end
96
81
 
97
- if RUBY_VERSION < "1.9"
98
- # Returns +true+ if the Chars class can and should act as a proxy for the string _string_. Returns
99
- # +false+ otherwise.
100
- def self.wants?(string)
101
- $KCODE == 'UTF8' && consumes?(string)
102
- end
103
-
104
- # Returns a new Chars object containing the _other_ object concatenated to the string.
105
- #
106
- # Example:
107
- # (Mail::Multibyte.mb_chars('Café') + ' périferôl').to_s # => "Café périferôl"
108
- def +(other)
109
- chars(@wrapped_string + other)
110
- end
111
-
112
- # Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
113
- #
114
- # Example:
115
- # Mail::Multibyte.mb_chars('Café périferôl') =~ /ô/ # => 12
116
- def =~(other)
117
- translate_offset(@wrapped_string =~ other)
118
- end
119
-
120
- # Inserts the passed string at specified codepoint offsets.
121
- #
122
- # Example:
123
- # Mail::Multibyte.mb_chars('Café').insert(4, ' périferôl').to_s # => "Café périferôl"
124
- def insert(offset, fragment)
125
- unpacked = Unicode.u_unpack(@wrapped_string)
126
- unless offset > unpacked.length
127
- @wrapped_string.replace(
128
- Unicode.u_unpack(@wrapped_string).insert(offset, *Unicode.u_unpack(fragment)).pack('U*')
129
- )
130
- else
131
- raise IndexError, "index #{offset} out of string"
132
- end
133
- self
134
- end
135
-
136
- # Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
137
- #
138
- # Example:
139
- # Mail::Multibyte.mb_chars('Café').include?('é') # => true
140
- def include?(other)
141
- # We have to redefine this method because Enumerable defines it.
142
- @wrapped_string.include?(other)
143
- end
144
-
145
- # Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
146
- #
147
- # Example:
148
- # Mail::Multibyte.mb_chars('Café périferôl').index('ô') # => 12
149
- # Mail::Multibyte.mb_chars('Café périferôl').index(/\w/u) # => 0
150
- def index(needle, offset=0)
151
- wrapped_offset = first(offset).wrapped_string.length
152
- index = @wrapped_string.index(needle, wrapped_offset)
153
- index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
154
- end
155
-
156
- # Returns the position _needle_ in the string, counting in
157
- # codepoints, searching backward from _offset_ or the end of the
158
- # string. Returns +nil+ if _needle_ isn't found.
159
- #
160
- # Example:
161
- # Mail::Multibyte.mb_chars('Café périferôl').rindex('é') # => 6
162
- # Mail::Multibyte.mb_chars('Café périferôl').rindex(/\w/u) # => 13
163
- def rindex(needle, offset=nil)
164
- offset ||= length
165
- wrapped_offset = first(offset).wrapped_string.length
166
- index = @wrapped_string.rindex(needle, wrapped_offset)
167
- index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
168
- end
169
-
170
- # Returns the number of codepoints in the string
171
- def size
172
- Unicode.u_unpack(@wrapped_string).size
173
- end
174
- alias_method :length, :size
175
-
176
- # Strips entire range of Unicode whitespace from the right of the string.
177
- def rstrip
178
- chars(@wrapped_string.gsub(Unicode::TRAILERS_PAT, ''))
179
- end
180
-
181
- # Strips entire range of Unicode whitespace from the left of the string.
182
- def lstrip
183
- chars(@wrapped_string.gsub(Unicode::LEADERS_PAT, ''))
184
- end
185
-
186
- # Strips entire range of Unicode whitespace from the right and left of the string.
187
- def strip
188
- rstrip.lstrip
189
- end
190
-
191
- # Returns the codepoint of the first character in the string.
192
- #
193
- # Example:
194
- # Mail::Multibyte.mb_chars('こんにちは').ord # => 12371
195
- def ord
196
- Unicode.u_unpack(@wrapped_string)[0]
197
- end
198
-
199
- # Works just like <tt>String#rjust</tt>, only integer specifies characters instead of bytes.
200
- #
201
- # Example:
202
- #
203
- # Mail::Multibyte.mb_chars("¾ cup").rjust(8).to_s
204
- # # => " ¾ cup"
205
- #
206
- # Mail::Multibyte.mb_chars("¾ cup").rjust(8, " ").to_s # Use non-breaking whitespace
207
- # # => "   ¾ cup"
208
- def rjust(integer, padstr=' ')
209
- justify(integer, :right, padstr)
210
- end
211
-
212
- # Works just like <tt>String#ljust</tt>, only integer specifies characters instead of bytes.
213
- #
214
- # Example:
215
- #
216
- # Mail::Multibyte.mb_chars("¾ cup").rjust(8).to_s
217
- # # => "¾ cup "
218
- #
219
- # Mail::Multibyte.mb_chars("¾ cup").rjust(8, " ").to_s # Use non-breaking whitespace
220
- # # => "¾ cup   "
221
- def ljust(integer, padstr=' ')
222
- justify(integer, :left, padstr)
223
- end
224
-
225
- # Works just like <tt>String#center</tt>, only integer specifies characters instead of bytes.
226
- #
227
- # Example:
228
- #
229
- # Mail::Multibyte.mb_chars("¾ cup").center(8).to_s
230
- # # => " ¾ cup "
231
- #
232
- # Mail::Multibyte.mb_chars("¾ cup").center(8, " ").to_s # Use non-breaking whitespace
233
- # # => " ¾ cup  "
234
- def center(integer, padstr=' ')
235
- justify(integer, :center, padstr)
236
- end
237
-
238
- else
239
- def =~(other)
240
- @wrapped_string =~ other
241
- end
82
+ def =~(other)
83
+ @wrapped_string =~ other
242
84
  end
243
85
 
244
86
  # Works just like <tt>String#split</tt>, with the exception that the items in the resulting list are Chars
@@ -331,7 +173,7 @@ module Mail #:nodoc:
331
173
  #
332
174
  # Example:
333
175
  # s = 'こんにちは'
334
- # s.mb_chars.limit(7) # => "こに"
176
+ # s.mb_chars.limit(7) # => "こん"
335
177
  def limit(limit)
336
178
  slice(0...translate_offset(limit))
337
179
  end
@@ -419,7 +261,7 @@ module Mail #:nodoc:
419
261
  # exclude lstrip!, rstrip! and strip! because they are already work as expected on multibyte strings.
420
262
  if public_method_defined?(method)
421
263
  define_method("#{method}!") do |*args|
422
- @wrapped_string = send(args.nil? ? method : method, *args).to_s
264
+ @wrapped_string = send(method, *args).to_s
423
265
  self
424
266
  end
425
267
  end
@@ -3,59 +3,42 @@
3
3
 
4
4
  module Mail #:nodoc:
5
5
  module Multibyte #:nodoc:
6
- if RUBY_VERSION >= "1.9"
7
- # Returns a regular expression that matches valid characters in the current encoding
8
- def self.valid_character
9
- VALID_CHARACTER[Encoding.default_external.to_s]
10
- end
11
- else
12
- def self.valid_character
13
- case $KCODE
14
- when 'UTF8'
15
- VALID_CHARACTER['UTF-8']
16
- when 'SJIS'
17
- VALID_CHARACTER['Shift_JIS']
18
- end
19
- end
6
+ # Returns a regular expression that matches valid characters in the current encoding
7
+ def self.valid_character
8
+ VALID_CHARACTER[Encoding.default_external.to_s]
20
9
  end
21
10
 
22
- if 'string'.respond_to?(:valid_encoding?)
23
- # Verifies the encoding of a string
24
- def self.verify(string)
25
- string.valid_encoding?
26
- end
27
- else
28
- def self.verify(string)
29
- if expression = valid_character
30
- # Splits the string on character boundaries, which are determined based on $KCODE.
31
- string.split(//).all? { |c| expression =~ c }
32
- else
33
- true
34
- end
11
+ # Returns true if string has valid utf-8 encoding
12
+ def self.is_utf8?(string)
13
+ case string.encoding
14
+ when Encoding::UTF_8
15
+ verify(string)
16
+ when Encoding::ASCII_8BIT, Encoding::US_ASCII
17
+ verify(to_utf8(string))
18
+ else
19
+ false
35
20
  end
36
21
  end
37
22
 
23
+ # Verifies the encoding of a string
24
+ def self.verify(string)
25
+ string.valid_encoding?
26
+ end
27
+
38
28
  # Verifies the encoding of the string and raises an exception when it's not valid
39
29
  def self.verify!(string)
40
30
  raise EncodingError.new("Found characters with invalid encoding") unless verify(string)
41
31
  end
42
32
 
43
- if 'string'.respond_to?(:force_encoding)
44
- # Removes all invalid characters from the string.
45
- #
46
- # Note: this method is a no-op in Ruby 1.9
47
- def self.clean(string)
48
- string
49
- end
50
- else
51
- def self.clean(string)
52
- if expression = valid_character
53
- # Splits the string on character boundaries, which are determined based on $KCODE.
54
- string.split(//).grep(expression).join
55
- else
56
- string
57
- end
58
- end
33
+ # Removes all invalid characters from the string.
34
+ #
35
+ # Note: this method is a no-op in Ruby 1.9
36
+ def self.clean(string)
37
+ string
38
+ end
39
+
40
+ def self.to_utf8(string)
41
+ string.dup.force_encoding(Encoding::UTF_8)
59
42
  end
60
43
  end
61
44
  end
@@ -19,7 +19,6 @@ module Mail #:nodoc:
19
19
 
20
20
  self.proxy_class = Mail::Multibyte::Chars
21
21
 
22
- if RUBY_VERSION >= "1.9"
23
22
  # == Multibyte proxy
24
23
  #
25
24
  # +mb_chars+ is a multibyte safe proxy for string methods.
@@ -54,21 +53,12 @@ module Mail #:nodoc:
54
53
  # For more information about the methods defined on the Chars proxy see Mail::Multibyte::Chars. For
55
54
  # information about how to change the default Multibyte behaviour see Mail::Multibyte.
56
55
  def self.mb_chars(str)
57
- if proxy_class.consumes?(str)
56
+ if is_utf8?(str)
58
57
  proxy_class.new(str)
59
58
  else
60
59
  str
61
60
  end
62
61
  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
71
- end
72
62
 
73
63
  # Regular expressions that describe valid byte sequences for a character
74
64
  VALID_CHARACTER = {
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
- module Mail
3
2
 
3
+ module Mail
4
4
  # A delivery method implementation which sends via exim.
5
5
  #
6
6
  # To use this, first find out where the exim binary is on your computer,
@@ -39,11 +39,12 @@ module Mail
39
39
  class Exim < Sendmail
40
40
  DEFAULTS = {
41
41
  :location => '/usr/sbin/exim',
42
- :arguments => '-i -t'
42
+ :arguments => %w[ -i -t ]
43
43
  }
44
44
 
45
- def self.call(path, arguments, destinations, encoded_message)
46
- super path, arguments, nil, encoded_message
45
+ # Uses -t option to extract recipients from the message.
46
+ def destinations_for(envelope)
47
+ nil
47
48
  end
48
49
  end
49
50
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require 'mail/check_delivery_params'
2
+ require 'mail/smtp_envelope'
3
3
 
4
4
  module Mail
5
5
  # FileDelivery class delivers emails into multiple files based on the destination
@@ -13,20 +13,16 @@ 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
- if RUBY_VERSION >= '1.9.1'
17
- require 'fileutils'
18
- else
19
- require 'ftools'
20
- end
16
+ require 'fileutils'
21
17
 
22
18
  attr_accessor :settings
23
19
 
24
20
  def initialize(values)
25
- self.settings = { :location => './mails' }.merge!(values)
21
+ self.settings = { :location => './mails', :extension => '' }.merge!(values)
26
22
  end
27
23
 
28
24
  def deliver!(mail)
29
- Mail::CheckDeliveryParams.check(mail)
25
+ envelope = Mail::SmtpEnvelope.new(mail)
30
26
 
31
27
  if ::File.respond_to?(:makedirs)
32
28
  ::File.makedirs settings[:location]
@@ -34,8 +30,13 @@ module Mail
34
30
  ::FileUtils.mkdir_p settings[:location]
35
31
  end
36
32
 
37
- mail.destinations.uniq.each do |to|
38
- ::File.open(::File.join(settings[:location], File.basename(to.to_s)), 'a') { |f| "#{f.write(mail.encoded)}\r\n\r\n" }
33
+ envelope.to.uniq.each do |to|
34
+ path = ::File.join(settings[:location], File.basename(to.to_s+settings[:extension]))
35
+
36
+ ::File.open(path, 'a') do |f|
37
+ f.write envelope.message
38
+ f.write "\r\n\r\n"
39
+ end
39
40
  end
40
41
  end
41
42
  end
@@ -1,9 +1,7 @@
1
- require 'mail/check_delivery_params'
1
+ require 'mail/smtp_envelope'
2
2
 
3
3
  module Mail
4
4
  class LoggerDelivery
5
- include Mail::CheckDeliveryParams
6
-
7
5
  attr_reader :logger, :severity, :settings
8
6
 
9
7
  def initialize(settings)
@@ -13,8 +11,7 @@ module Mail
13
11
  end
14
12
 
15
13
  def deliver!(mail)
16
- Mail::CheckDeliveryParams.check(mail)
17
- logger.log(severity) { mail.encoded }
14
+ logger.log(severity) { Mail::SmtpEnvelope.new(mail).message }
18
15
  end
19
16
 
20
17
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require 'mail/check_delivery_params'
2
+ require 'mail/smtp_envelope'
3
3
 
4
4
  module Mail
5
5
  # A delivery method implementation which sends via sendmail.
@@ -40,56 +40,48 @@ module Mail
40
40
  class Sendmail
41
41
  DEFAULTS = {
42
42
  :location => '/usr/sbin/sendmail',
43
- :arguments => '-i'
43
+ :arguments => %w[ -i ]
44
44
  }
45
45
 
46
46
  attr_accessor :settings
47
47
 
48
+ class DeliveryError < StandardError
49
+ end
50
+
48
51
  def initialize(values)
49
52
  self.settings = self.class::DEFAULTS.merge(values)
53
+ raise ArgumentError, ":arguments expected to be an Array of individual string args" if settings[:arguments].is_a?(String)
54
+ end
55
+
56
+ def destinations_for(envelope)
57
+ envelope.to
50
58
  end
51
59
 
52
60
  def deliver!(mail)
53
- smtp_from, smtp_to, message = Mail::CheckDeliveryParams.check(mail)
61
+ envelope = Mail::SmtpEnvelope.new(mail)
54
62
 
55
- from = "-f #{self.class.shellquote(smtp_from)}" if smtp_from
56
- to = smtp_to.map { |_to| self.class.shellquote(_to) }.join(' ')
63
+ command = [settings[:location]]
64
+ command.concat Array(settings[:arguments])
65
+ command.concat [ '-f', envelope.from ] if envelope.from
57
66
 
58
- arguments = "#{settings[:arguments]} #{from} --"
59
- self.class.call(settings[:location], arguments, to, message)
60
- end
67
+ if destinations = destinations_for(envelope)
68
+ command.push '--'
69
+ command.concat destinations
70
+ end
61
71
 
62
- def self.call(path, arguments, destinations, encoded_message)
63
- popen "#{path} #{arguments} #{destinations}" do |io|
64
- io.puts ::Mail::Utilities.binary_unsafe_to_lf(encoded_message)
72
+ popen(command) do |io|
73
+ io.puts ::Mail::Utilities.binary_unsafe_to_lf(envelope.message)
65
74
  io.flush
66
75
  end
67
76
  end
68
77
 
69
- if RUBY_VERSION < '1.9.0'
70
- def self.popen(command, &block)
71
- IO.popen "#{command} 2>&1", 'w+', &block
72
- end
73
- else
74
- def self.popen(command, &block)
75
- IO.popen command, 'w+', :err => :out, &block
78
+ private
79
+ def popen(command, &block)
80
+ IO.popen(command, 'w+', :err => :out, &block).tap do
81
+ if $?.exitstatus != 0
82
+ raise DeliveryError, "Delivery failed with exitstatus #{$?.exitstatus}: #{command.inspect}"
83
+ end
84
+ end
76
85
  end
77
- end
78
-
79
- # The following is an adaptation of ruby 1.9.2's shellwords.rb file,
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
85
- def self.shellquote(address)
86
- # Process as a single byte sequence because not all shell
87
- # implementations are multibyte aware.
88
- #
89
- # A LF cannot be escaped with a backslash because a backslash + LF
90
- # combo is regarded as line continuation and simply ignored. Strip it.
91
- escaped = address.gsub(/([^A-Za-z0-9_\s\+\-.,:\/@~])/n, "\\\\\\1").gsub("\n", '')
92
- %("#{escaped}")
93
- end
94
86
  end
95
87
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require 'mail/check_delivery_params'
2
+ require 'mail/smtp_envelope'
3
3
 
4
4
  module Mail
5
5
  # == Sending Email with SMTP
@@ -88,8 +88,8 @@ module Mail
88
88
  :openssl_verify_mode => nil,
89
89
  :ssl => nil,
90
90
  :tls => nil,
91
- :open_timeout => nil,
92
- :read_timeout => nil
91
+ :open_timeout => 5,
92
+ :read_timeout => 5
93
93
  }
94
94
 
95
95
  def initialize(values)
@@ -111,17 +111,33 @@ module Mail
111
111
 
112
112
  def build_smtp_session
113
113
  Net::SMTP.new(settings[:address], settings[:port]).tap do |smtp|
114
- if settings[:tls] || settings[:ssl]
115
- if smtp.respond_to?(:enable_tls)
114
+ tls = settings[:tls] || settings[:ssl]
115
+ if !tls.nil?
116
+ case tls
117
+ when true
116
118
  smtp.enable_tls(ssl_context)
119
+ when false
120
+ smtp.disable_tls
121
+ else
122
+ raise ArgumentError, "Unrecognized :tls value #{settings[:tls].inspect}; expected true, false, or nil"
117
123
  end
118
- elsif settings[:enable_starttls]
119
- if smtp.respond_to?(:enable_starttls)
124
+ elsif settings.include?(:enable_starttls) && !settings[:enable_starttls].nil?
125
+ case settings[:enable_starttls]
126
+ when true
120
127
  smtp.enable_starttls(ssl_context)
128
+ when false
129
+ smtp.disable_starttls
130
+ else
131
+ raise ArgumentError, "Unrecognized :enable_starttls value #{settings[:enable_starttls].inspect}; expected true, false, or nil"
121
132
  end
122
- elsif settings[:enable_starttls_auto]
123
- if smtp.respond_to?(:enable_starttls_auto)
133
+ elsif settings.include?(:enable_starttls_auto) && !settings[:enable_starttls_auto].nil?
134
+ case settings[:enable_starttls_auto]
135
+ when true
124
136
  smtp.enable_starttls_auto(ssl_context)
137
+ when false
138
+ smtp.disable_starttls_auto
139
+ else
140
+ raise ArgumentError, "Unrecognized :enable_starttls_auto value #{settings[:enable_starttls_auto].inspect}; expected true, false, or nil"
125
141
  end
126
142
  end
127
143
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require 'mail/check_delivery_params'
2
+ require 'mail/smtp_envelope'
3
3
 
4
4
  module Mail
5
5
  # == Sending Email with SMTP
@@ -49,18 +49,9 @@ module Mail
49
49
  # Send the message via SMTP.
50
50
  # The from and to attributes are optional. If not set, they are retrieve from the Message.
51
51
  def deliver!(mail)
52
- smtp_from, smtp_to, message = Mail::CheckDeliveryParams.check(mail)
53
-
54
- response = smtp.sendmail(dot_stuff(message), smtp_from, smtp_to)
55
-
52
+ envelope = Mail::SmtpEnvelope.new(mail)
53
+ response = smtp.sendmail(envelope.message, envelope.from, envelope.to)
56
54
  settings[:return_response] ? response : self
57
55
  end
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
65
56
  end
66
57
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require 'mail/check_delivery_params'
2
+ require 'mail/smtp_envelope'
3
3
 
4
4
  module Mail
5
5
  # The TestMailer is a bare bones mailer that does nothing. It is useful
@@ -35,7 +35,9 @@ module Mail
35
35
  end
36
36
 
37
37
  def deliver!(mail)
38
- Mail::CheckDeliveryParams.check(mail)
38
+ # Create the envelope to validate it
39
+ Mail::SmtpEnvelope.new(mail)
40
+
39
41
  Mail::TestMailer.deliveries << mail
40
42
  end
41
43
  end
@@ -11,8 +11,8 @@ module Mail
11
11
  # count: number of emails to retrieve. The default value is 1.
12
12
  # order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
13
13
  #
14
- def first(options = {}, &block)
15
- options ||= {}
14
+ def first(options = nil, &block)
15
+ options = options ? Hash[options] : {}
16
16
  options[:what] = :first
17
17
  options[:count] ||= 1
18
18
  find(options, &block)
@@ -24,8 +24,8 @@ module Mail
24
24
  # count: number of emails to retrieve. The default value is 1.
25
25
  # order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
26
26
  #
27
- def last(options = {}, &block)
28
- options ||= {}
27
+ def last(options = nil, &block)
28
+ options = options ? Hash[options] : {}
29
29
  options[:what] = :last
30
30
  options[:count] ||= 1
31
31
  find(options, &block)
@@ -36,8 +36,8 @@ module Mail
36
36
  # Possible options:
37
37
  # order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
38
38
  #
39
- def all(options = {}, &block)
40
- options ||= {}
39
+ def all(options = nil, &block)
40
+ options = options ? Hash[options] : {}
41
41
  options[:count] = :all
42
42
  find(options, &block)
43
43
  end
@@ -53,8 +53,8 @@ module Mail
53
53
  # delete_after_find: flag for whether to delete each retreived email after find. Default
54
54
  # is true. Call #find if you would like this to default to false.
55
55
  #
56
- def find_and_delete(options = {}, &block)
57
- options ||= {}
56
+ def find_and_delete(options = nil, &block)
57
+ options = options ? Hash[options] : {}
58
58
  options[:delete_after_find] ||= true
59
59
  find(options, &block)
60
60
  end
@@ -70,7 +70,7 @@ module Mail
70
70
  # The default is 'ALL'
71
71
  # search_charset: charset to pass to IMAP server search. Omitted by default. Example: 'UTF-8' or 'ASCII'.
72
72
  #
73
- def find(options={}, &block)
73
+ def find(options=nil, &block)
74
74
  options = validate_options(options)
75
75
 
76
76
  start do |imap|
@@ -142,7 +142,7 @@ module Mail
142
142
 
143
143
  # Set default options
144
144
  def validate_options(options)
145
- options ||= {}
145
+ options = options ? Hash[options] : {}
146
146
  options[:mailbox] ||= 'INBOX'
147
147
  options[:count] ||= 10
148
148
  options[:order] ||= :asc