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,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Resent-Cc Field
4
5
  #
@@ -38,8 +39,7 @@ module Mail
38
39
 
39
40
  def initialize(value = nil, charset = 'utf-8')
40
41
  self.charset = charset
41
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
- self.parse
42
+ super(CAPITALIZED_FIELD, value, charset)
43
43
  self
44
44
  end
45
45
 
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # resent-date = "Resent-Date:" date-time CRLF
4
5
  require 'mail/fields/common/common_date'
@@ -13,10 +14,9 @@ module Mail
13
14
 
14
15
  def initialize(value = nil, charset = 'utf-8')
15
16
  self.charset = charset
16
- if value.blank?
17
+ if Utilities.blank?(value)
17
18
  value = ::DateTime.now.strftime('%a, %d %b %Y %H:%M:%S %z')
18
19
  else
19
- value = strip_field(FIELD_NAME, value)
20
20
  value = ::DateTime.parse(value.to_s).strftime('%a, %d %b %Y %H:%M:%S %z')
21
21
  end
22
22
  super(CAPITALIZED_FIELD, value, charset)
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Resent-From Field
4
5
  #
@@ -38,8 +39,7 @@ module Mail
38
39
 
39
40
  def initialize(value = nil, charset = 'utf-8')
40
41
  self.charset = charset
41
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
- self.parse
42
+ super(CAPITALIZED_FIELD, value, charset)
43
43
  self
44
44
  end
45
45
 
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # resent-msg-id = "Resent-Message-ID:" msg-id CRLF
4
5
  require 'mail/fields/common/common_message_id'
@@ -13,7 +14,7 @@ module Mail
13
14
 
14
15
  def initialize(value = nil, charset = 'utf-8')
15
16
  self.charset = charset
16
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
17
+ super(CAPITALIZED_FIELD, value, charset)
17
18
  self.parse
18
19
  self
19
20
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Resent-Sender Field
4
5
  #
@@ -37,8 +38,7 @@ module Mail
37
38
 
38
39
  def initialize(value = nil, charset = 'utf-8')
39
40
  self.charset = charset
40
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
41
- self.parse
41
+ super(CAPITALIZED_FIELD, value, charset)
42
42
  self
43
43
  end
44
44
 
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Resent-To Field
4
5
  #
@@ -38,8 +39,7 @@ module Mail
38
39
 
39
40
  def initialize(value = nil, charset = 'utf-8')
40
41
  self.charset = charset
41
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
- self.parse
42
+ super(CAPITALIZED_FIELD, value, charset)
43
43
  self
44
44
  end
45
45
 
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # 4.4.3. REPLY-TO / RESENT-REPLY-TO
4
5
  #
@@ -40,8 +41,7 @@ module Mail
40
41
  def initialize(value = nil, charset = 'utf-8')
41
42
  value = nil if value == '<>'
42
43
  self.charset = charset
43
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
44
- self.parse
44
+ super(CAPITALIZED_FIELD, value, charset)
45
45
  self
46
46
  end
47
47
 
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Sender Field
4
5
  #
@@ -38,8 +39,7 @@ module Mail
38
39
 
39
40
  def initialize(value = nil, charset = 'utf-8')
40
41
  self.charset = charset
41
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
- self.parse
42
+ super(CAPITALIZED_FIELD, value, charset)
43
43
  self
44
44
  end
45
45
 
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'mail/fields/common/common_field'
3
4
 
4
5
  module Mail
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # subject = "Subject:" unstructured CRLF
4
5
  module Mail
@@ -9,7 +10,7 @@ module Mail
9
10
 
10
11
  def initialize(value = nil, charset = 'utf-8')
11
12
  self.charset = charset
12
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
13
+ super(CAPITALIZED_FIELD, value, charset)
13
14
  end
14
15
 
15
16
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = To Field
4
5
  #
@@ -38,8 +39,7 @@ module Mail
38
39
 
39
40
  def initialize(value = nil, charset = 'utf-8')
40
41
  self.charset = charset
41
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
- self.parse
42
+ super(CAPITALIZED_FIELD, value, charset)
43
43
  self
44
44
  end
45
45
 
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'mail/fields/common/common_field'
3
4
 
4
5
  module Mail
@@ -31,6 +32,12 @@ module Mail
31
32
  else
32
33
  # Ensure we are dealing with a string
33
34
  value = value.to_s
35
+
36
+ # Mark UTF-8 strings parsed from ASCII-8BIT
37
+ if value.respond_to?(:force_encoding) && value.encoding == Encoding::ASCII_8BIT
38
+ utf8 = value.dup.force_encoding(Encoding::UTF_8)
39
+ value = utf8 if utf8.valid_encoding?
40
+ end
34
41
  end
35
42
 
36
43
  if charset
@@ -66,11 +73,15 @@ module Mail
66
73
  private
67
74
 
68
75
  def do_encode
69
- value.nil? ? '' : "#{wrapped_value}\r\n"
76
+ if value && !value.empty?
77
+ "#{wrapped_value}\r\n"
78
+ else
79
+ ''
80
+ end
70
81
  end
71
82
 
72
83
  def do_decode
73
- value.blank? ? nil : Encodings.decode_encode(value, :decode)
84
+ Utilities.blank?(value) ? nil : Encodings.decode_encode(value, :decode)
74
85
  end
75
86
 
76
87
  # 2.2.3. Long Header Fields
@@ -120,16 +131,16 @@ module Mail
120
131
  def fold(prepend = 0) # :nodoc:
121
132
  encoding = normalized_encoding
122
133
  decoded_string = decoded.to_s
123
- should_encode = decoded_string.not_ascii_only?
134
+ should_encode = !decoded_string.ascii_only?
124
135
  if should_encode
125
136
  first = true
126
137
  words = decoded_string.split(/[ \t]/).map do |word|
127
138
  if first
128
139
  first = !first
129
140
  else
130
- word = " " << word
141
+ word = " #{word}"
131
142
  end
132
- if word.not_ascii_only?
143
+ if !word.ascii_only?
133
144
  word
134
145
  else
135
146
  word.scan(/.{7}|.+$/)
@@ -143,11 +154,18 @@ module Mail
143
154
  while !words.empty?
144
155
  limit = 78 - prepend
145
156
  limit = limit - 7 - encoding.length if should_encode
146
- line = ""
157
+ line = String.new
147
158
  first_word = true
148
159
  while !words.empty?
149
160
  break unless word = words.first.dup
150
- word.encode!(charset) if charset && word.respond_to?(:encode!)
161
+
162
+ # Convert on 1.9+ only since we aren't sure of the current
163
+ # charset encoding on 1.8. We'd need to track internal/external
164
+ # charset on each field.
165
+ if charset && word.respond_to?(:encoding)
166
+ word = Encodings.transcode_charset(word, word.encoding, charset)
167
+ end
168
+
151
169
  word = encode(word) if should_encode
152
170
  word = encode_crlf(word)
153
171
  # Skip to next line if we're going to go past the limit
@@ -178,7 +196,7 @@ module Mail
178
196
  end
179
197
 
180
198
  def encode(value)
181
- value = [value].pack("M").gsub("=\n", '')
199
+ value = [value].pack(CAPITAL_M).gsub(EQUAL_LF, EMPTY)
182
200
  value.gsub!(/"/, '=22')
183
201
  value.gsub!(/\(/, '=28')
184
202
  value.gsub!(/\)/, '=29')
@@ -189,8 +207,8 @@ module Mail
189
207
  end
190
208
 
191
209
  def encode_crlf(value)
192
- value.gsub!("\r", '=0D')
193
- value.gsub!("\n", '=0A')
210
+ value.gsub!(CR, CR_ENCODED)
211
+ value.gsub!(LF, LF_ENCODED)
194
212
  value
195
213
  end
196
214
 
data/lib/mail/fields.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Mail
2
3
  register_autoload :UnstructuredField, 'mail/fields/unstructured_field'
3
4
  register_autoload :StructuredField, 'mail/fields/structured_field'
data/lib/mail/header.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
 
4
5
  # Provides access to a header object.
@@ -17,7 +18,7 @@ module Mail
17
18
  # 2.2.3. All field bodies MUST conform to the syntax described in
18
19
  # sections 3 and 4 of this standard.
19
20
  class Header
20
- include Patterns
21
+ include Constants
21
22
  include Utilities
22
23
  include Enumerable
23
24
 
@@ -49,13 +50,14 @@ module Mail
49
50
  # me the example so we can fix it.
50
51
  def initialize(header_text = nil, charset = nil)
51
52
  @charset = charset
52
- self.raw_source = header_text.to_crlf.lstrip
53
+ self.raw_source = header_text
53
54
  split_header if header_text
54
55
  end
55
56
 
56
57
  def initialize_copy(original)
57
58
  super
58
59
  @fields = @fields.dup
60
+ @fields.map!(&:dup)
59
61
  end
60
62
 
61
63
  # The preserved raw source of the header as you passed it in, untouched
@@ -91,14 +93,15 @@ module Mail
91
93
  # h.fields = ['From: mikel@me.com', 'To: bob@you.com']
92
94
  def fields=(unfolded_fields)
93
95
  @fields = Mail::FieldList.new
94
- warn "Warning: more than #{self.class.maximum_amount} header fields only using the first #{self.class.maximum_amount}" if unfolded_fields.length > self.class.maximum_amount
96
+ Kernel.warn "WARNING: More than #{self.class.maximum_amount} header fields; only using the first #{self.class.maximum_amount} and ignoring the rest" if unfolded_fields.length > self.class.maximum_amount
95
97
  unfolded_fields[0..(self.class.maximum_amount-1)].each do |field|
96
98
 
97
- field = Field.new(field, nil, charset)
98
- if limited_field?(field.name) && (selected = select_field_for(field.name)) && selected.any?
99
- selected.first.update(field.name, field.value)
100
- else
101
- @fields << field
99
+ if field = Field.parse(field, charset)
100
+ if limited_field?(field.name) && (selected = select_field_for(field.name)) && selected.any?
101
+ selected.first.update(field.name, field.value)
102
+ else
103
+ @fields << field
104
+ end
102
105
  end
103
106
  end
104
107
 
@@ -130,12 +133,13 @@ module Mail
130
133
  # h['To'] #=> 'mikel@me.com'
131
134
  # h['X-Mail-SPAM'] #=> ['15', '20']
132
135
  def [](name)
133
- name = dasherize(name).downcase
136
+ name = dasherize(name)
137
+ name.downcase!
134
138
  selected = select_field_for(name)
135
139
  case
136
140
  when selected.length > 1
137
141
  selected.map { |f| f }
138
- when !selected.blank?
142
+ when !Utilities.blank?(selected)
139
143
  selected.first
140
144
  else
141
145
  nil
@@ -165,11 +169,11 @@ module Mail
165
169
 
166
170
  case
167
171
  # User wants to delete the field
168
- when !selected.blank? && value == nil
172
+ when !Utilities.blank?(selected) && value == nil
169
173
  fields.delete_if { |f| selected.include?(f) }
170
174
 
171
175
  # User wants to change the field
172
- when !selected.blank? && limited_field?(fn)
176
+ when !Utilities.blank?(selected) && limited_field?(fn)
173
177
  selected.first.update(fn, value)
174
178
 
175
179
  # User wants to create the field
@@ -203,7 +207,7 @@ module Mail
203
207
  content-id content-disposition content-location]
204
208
 
205
209
  def encoded
206
- buffer = ''
210
+ buffer = String.new
207
211
  buffer.force_encoding('us-ascii') if buffer.respond_to?(:force_encoding)
208
212
  fields.each do |field|
209
213
  buffer << field.encoded
@@ -246,7 +250,7 @@ module Mail
246
250
  private
247
251
 
248
252
  def raw_source=(val)
249
- @raw_source = val
253
+ @raw_source = ::Mail::Utilities.to_crlf(val).lstrip
250
254
  end
251
255
 
252
256
  # Splits an unfolded and line break cleaned header into individual field
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  # This is an almost cut and paste from ActiveSupport v3.0.6, copied in here so that Mail
4
5
  # itself does not depend on ActiveSupport to avoid versioning conflicts
data/lib/mail/mail.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
 
4
5
  # Allows you to create a new Mail::Message object.
@@ -89,19 +90,11 @@ module Mail
89
90
  # Each mail object inherits the default set in Mail.delivery_method, however, on
90
91
  # a per email basis, you can override the method:
91
92
  #
92
- # mail.delivery_method :sendmail
93
+ # mail.delivery_method :smtp
93
94
  #
94
95
  # Or you can override the method and pass in settings:
95
96
  #
96
- # mail.delivery_method :sendmail, { :address => 'some.host' }
97
- #
98
- # You can also just modify the settings:
99
- #
100
- # mail.delivery_settings = { :address => 'some.host' }
101
- #
102
- # The passed in hash is just merged against the defaults with +merge!+ and the result
103
- # assigned the mail object. So the above example will change only the :address value
104
- # of the global smtp_settings to be 'some.host', keeping all other values
97
+ # mail.delivery_method :smtp, :address => 'some.host'
105
98
  def self.defaults(&block)
106
99
  Configuration.instance.instance_eval(&block)
107
100
  end
@@ -245,9 +238,11 @@ module Mail
245
238
 
246
239
  protected
247
240
 
241
+ RANDOM_TAG='%x%x_%x%x%d%x'
242
+
248
243
  def self.random_tag
249
244
  t = Time.now
250
- sprintf('%x%x_%x%x%d%x',
245
+ sprintf(RANDOM_TAG,
251
246
  t.to_i, t.tv_usec,
252
247
  $$, Thread.current.object_id.abs, self.uniq, rand(255))
253
248
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module Mail
3
+ module Matchers
4
+ def any_attachment
5
+ AnyAttachmentMatcher.new
6
+ end
7
+
8
+ def an_attachment_with_filename(filename)
9
+ AttachmentFilenameMatcher.new(filename)
10
+ end
11
+
12
+ class AnyAttachmentMatcher
13
+ def ===(other)
14
+ other.attachment?
15
+ end
16
+ end
17
+
18
+ class AttachmentFilenameMatcher
19
+ attr_reader :filename
20
+ def initialize(filename)
21
+ @filename = filename
22
+ end
23
+
24
+ def ===(other)
25
+ other.attachment? && other.filename == filename
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Mail
2
3
  module Matchers
3
4
  def have_sent_email
@@ -42,15 +43,25 @@ module Mail
42
43
 
43
44
  def bcc(recipient_or_list)
44
45
  @blind_copy_recipients ||= []
46
+ @blind_copy_recipients.concat(Array(recipient_or_list))
47
+ self
48
+ end
45
49
 
46
- if recipient_or_list.kind_of?(Array)
47
- @blind_copy_recipients += recipient_or_list
48
- else
49
- @blind_copy_recipients << recipient_or_list
50
- end
50
+ def with_attachments(attachments)
51
+ @attachments ||= []
52
+ @attachments.concat(Array(attachments))
53
+ self
54
+ end
55
+
56
+ def with_no_attachments
57
+ @having_attachments = false
51
58
  self
52
59
  end
53
60
 
61
+ def with_any_attachments
62
+ @having_attachments = true
63
+ self
64
+ end
54
65
 
55
66
  def with_subject(subject)
56
67
  @subject = subject
@@ -72,6 +83,16 @@ module Mail
72
83
  self
73
84
  end
74
85
 
86
+ def with_html(body)
87
+ @html_part_body = body
88
+ self
89
+ end
90
+
91
+ def with_text(body)
92
+ @text_part_body = body
93
+ self
94
+ end
95
+
75
96
  def description
76
97
  result = "send a matching email"
77
98
  result
@@ -84,7 +105,7 @@ module Mail
84
105
  result
85
106
  end
86
107
 
87
- def negative_failure_message
108
+ def failure_message_when_negated
88
109
  result = "Expected no email to be sent "
89
110
  result += explain_expectations
90
111
  result += dump_deliveries
@@ -92,11 +113,13 @@ module Mail
92
113
  end
93
114
 
94
115
  protected
95
-
116
+
96
117
  def filter_matched_deliveries(deliveries)
97
118
  candidate_deliveries = deliveries
98
-
99
- %w(sender recipients copy_recipients blind_copy_recipients subject subject_matcher body body_matcher).each do |modifier_name|
119
+ modifiers =
120
+ %w(sender recipients copy_recipients blind_copy_recipients subject
121
+ subject_matcher body body_matcher html_part_body text_part_body having_attachments attachments)
122
+ modifiers.each do |modifier_name|
100
123
  next unless instance_variable_defined?("@#{modifier_name}")
101
124
  candidate_deliveries = candidate_deliveries.select{|matching_delivery| self.send("matches_on_#{modifier_name}?", matching_delivery)}
102
125
  end
@@ -128,6 +151,17 @@ module Mail
128
151
  @subject_matcher.match delivery.subject
129
152
  end
130
153
 
154
+ def matches_on_having_attachments?(delivery)
155
+ @having_attachments && delivery.attachments.any? ||
156
+ (!@having_attachments && delivery.attachments.none?)
157
+ end
158
+
159
+ def matches_on_attachments?(delivery)
160
+ @attachments.each_with_index.inject( true ) do |sent_attachments, (attachment, index)|
161
+ sent_attachments &&= (attachment === delivery.attachments[index])
162
+ end
163
+ end
164
+
131
165
  def matches_on_body?(delivery)
132
166
  delivery.body == @body
133
167
  end
@@ -136,6 +170,14 @@ module Mail
136
170
  @body_matcher.match delivery.body.raw_source
137
171
  end
138
172
 
173
+ def matches_on_html_part_body?(delivery)
174
+ delivery.html_part.body == @html_part_body
175
+ end
176
+
177
+ def matches_on_text_part_body?(delivery)
178
+ delivery.text_part.body == @text_part_body
179
+ end
180
+
139
181
  def explain_expectations
140
182
  result = ''
141
183
  result += "from #{@sender} " if instance_variable_defined?('@sender')
@@ -146,6 +188,8 @@ module Mail
146
188
  result += "with subject matching \"#{@subject_matcher}\" " if instance_variable_defined?('@subject_matcher')
147
189
  result += "with body \"#{@body}\" " if instance_variable_defined?('@body')
148
190
  result += "with body matching \"#{@body_matcher}\" " if instance_variable_defined?('@body_matcher')
191
+ result += "with a text part matching \"#{@text_part_body}\" " if instance_variable_defined?('@text_part_body')
192
+ result += "with an HTML part matching \"#{@html_part_body}\" " if instance_variable_defined?('@html_part_body')
149
193
  result
150
194
  end
151
195