mail 2.6.3 → 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 (178) hide show
  1. checksums.yaml +5 -5
  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 +54 -41
  6. data/lib/mail/check_delivery_params.rb +50 -10
  7. data/lib/mail/configuration.rb +3 -0
  8. data/lib/mail/constants.rb +5 -3
  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 +4 -1
  32. data/lib/mail/encodings.rb +114 -60
  33. data/lib/mail/envelope.rb +2 -1
  34. data/lib/mail/field.rb +114 -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 +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 +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 -86
  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 -48
  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 +109 -10
  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 +47 -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,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Mail
4
5
  # Raised when attempting to decode an unknown encoding type
@@ -6,7 +7,6 @@ module Mail
6
7
  end
7
8
 
8
9
  module Encodings
9
-
10
10
  include Mail::Constants
11
11
  extend Mail::Utilities
12
12
 
@@ -18,7 +18,7 @@ module Mail
18
18
  #
19
19
  # Encodings.register "base64", Mail::Encodings::Base64
20
20
  def Encodings.register(name, cls)
21
- @transfer_encodings[get_name(name)] = cls
21
+ @transfer_encodings[get_name(name)] = cls
22
22
  end
23
23
 
24
24
  # Is the encoding we want defined?
@@ -26,8 +26,8 @@ module Mail
26
26
  # Example:
27
27
  #
28
28
  # Encodings.defined?(:base64) #=> true
29
- def Encodings.defined?( str )
30
- @transfer_encodings.include? get_name(str)
29
+ def Encodings.defined?(name)
30
+ @transfer_encodings.include? get_name(name)
31
31
  end
32
32
 
33
33
  # Gets a defined encoding type, QuotedPrintable or Base64 for now.
@@ -38,16 +38,24 @@ module Mail
38
38
  # Example:
39
39
  #
40
40
  # Encodings.get_encoding(:base64) #=> Mail::Encodings::Base64
41
- def Encodings.get_encoding( str )
42
- @transfer_encodings[get_name(str)]
41
+ def Encodings.get_encoding(name)
42
+ @transfer_encodings[get_name(name)]
43
43
  end
44
44
 
45
45
  def Encodings.get_all
46
46
  @transfer_encodings.values
47
47
  end
48
48
 
49
- def Encodings.get_name(enc)
50
- enc = underscoreize(enc).downcase
49
+ def Encodings.get_name(name)
50
+ underscoreize(name).downcase
51
+ end
52
+
53
+ def Encodings.transcode_charset(str, from_charset, to_charset = 'UTF-8')
54
+ if from_charset
55
+ RubyVer.transcode_charset str, from_charset, to_charset
56
+ else
57
+ str
58
+ end
51
59
  end
52
60
 
53
61
  # Encodes a parameter value using URI Escaping, note the language field 'en' can
@@ -121,7 +129,7 @@ module Mail
121
129
  # Split on white-space boundaries with capture, so we capture the white-space as well
122
130
  lines.each do |line|
123
131
  line.gsub!(ENCODED_VALUE) do |string|
124
- case $1
132
+ case $2
125
133
  when *B_VALUES then b_value_decode(string)
126
134
  when *Q_VALUES then q_value_decode(string)
127
135
  end
@@ -159,32 +167,51 @@ module Mail
159
167
 
160
168
  def Encodings.address_encode(address, charset = 'utf-8')
161
169
  if address.is_a?(Array)
162
- # loop back through for each element
163
170
  address.compact.map { |a| Encodings.address_encode(a, charset) }.join(", ")
164
- else
165
- # find any word boundary that is not ascii and encode it
166
- encode_non_usascii(address, charset) if address
171
+ elsif address
172
+ encode_non_usascii(address, charset)
167
173
  end
168
174
  end
169
175
 
170
176
  def Encodings.encode_non_usascii(address, charset)
171
177
  return address if address.ascii_only? or charset.nil?
172
- us_ascii = %Q{\x00-\x7f}
173
- # Encode any non usascii strings embedded inside of quotes
174
- address = address.gsub(/(".*?[^#{us_ascii}].*?")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
175
- # Then loop through all remaining items and encode as needed
176
- tokens = address.split(/\s/)
177
- map_with_index(tokens) do |word, i|
178
- if word.ascii_only?
179
- word
180
- else
181
- previous_non_ascii = i>0 && tokens[i-1] && !tokens[i-1].ascii_only?
182
- if previous_non_ascii #why are we adding an extra space here?
183
- word = " #{word}"
178
+
179
+ # With KCODE=u we can't use regexps on other encodings. Go ASCII.
180
+ with_ascii_kcode do
181
+ # Encode all strings embedded inside of quotes
182
+ address = address.gsub(/("[^"]*[^\/]")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
183
+
184
+ # Then loop through all remaining items and encode as needed
185
+ tokens = address.split(/\s/)
186
+
187
+ map_with_index(tokens) do |word, i|
188
+ if word.ascii_only?
189
+ word
190
+ else
191
+ previous_non_ascii = i>0 && tokens[i-1] && !tokens[i-1].ascii_only?
192
+ if previous_non_ascii #why are we adding an extra space here?
193
+ word = " #{word}"
194
+ end
195
+ Encodings.b_value_encode(word, charset)
184
196
  end
185
- Encodings.b_value_encode(word, charset)
197
+ end.join(' ')
198
+ end
199
+ end
200
+
201
+ if RUBY_VERSION < '1.9'
202
+ # With KCODE=u we can't use regexps on other encodings. Go ASCII.
203
+ def Encodings.with_ascii_kcode #:nodoc:
204
+ if $KCODE
205
+ $KCODE, original_kcode = '', $KCODE
186
206
  end
187
- end.join(' ')
207
+ yield
208
+ ensure
209
+ $KCODE = original_kcode if original_kcode
210
+ end
211
+ else
212
+ def Encodings.with_ascii_kcode #:nodoc:
213
+ yield
214
+ end
188
215
  end
189
216
 
190
217
  # Encode a string with Base64 Encoding and returns it ready to be inserted
@@ -194,12 +221,15 @@ module Mail
194
221
  #
195
222
  # Encodings.b_value_encode('This is あ string', 'UTF-8')
196
223
  # #=> "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?="
197
- def Encodings.b_value_encode(encoded_str, encoding = nil)
198
- return encoded_str if encoded_str.to_s.ascii_only?
199
- string, encoding = RubyVer.b_value_encode(encoded_str, encoding)
200
- map_lines(string) do |str|
201
- "=?#{encoding}?B?#{str.chomp}?="
202
- end.join(" ")
224
+ def Encodings.b_value_encode(string, encoding = nil)
225
+ if string.to_s.ascii_only?
226
+ string
227
+ else
228
+ Encodings.each_base64_chunk_byterange(string, 60).map do |chunk|
229
+ str, encoding = RubyVer.b_value_encode(chunk, encoding)
230
+ "=?#{encoding}?B?#{str.chomp}?="
231
+ end.join(" ")
232
+ end
203
233
  end
204
234
 
205
235
  # Encode a string with Quoted-Printable Encoding and returns it ready to be inserted
@@ -240,50 +270,74 @@ module Mail
240
270
  RubyVer.q_value_decode(str)
241
271
  end
242
272
 
243
- def Encodings.split_encoding_from_string( str )
244
- match = str.match(/\=\?([^?]+)?\?[QB]\?(.*)\?\=/mi)
245
- if match
246
- match[1]
247
- else
248
- nil
249
- end
250
- end
251
-
252
273
  def Encodings.find_encoding(str)
253
274
  RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
254
275
  end
255
276
 
256
277
  # Gets the encoding type (Q or B) from the string.
257
- def Encodings.split_value_encoding_from_string(str)
258
- match = str.match(/\=\?[^?]+?\?([QB])\?(.*)\?\=/mi)
259
- if match
260
- match[1]
261
- else
262
- nil
263
- end
278
+ def Encodings.value_encoding_from_string(str)
279
+ str[ENCODED_VALUE, 1]
264
280
  end
265
281
 
266
- # When the encoded string consists of multiple lines, lines with the same
267
- # encoding (Q or B) can be joined together.
282
+ # Split header line into proper encoded and unencoded parts.
268
283
  #
269
284
  # String has to be of the format =?<encoding>?[QB]?<string>?=
285
+ #
286
+ # Omit unencoded space after an encoded-word.
270
287
  def Encodings.collapse_adjacent_encodings(str)
271
- lines = str.split(/(\?=)\s*(=\?)/).each_slice(2).map(&:join)
272
288
  results = []
273
- previous_encoding = nil
289
+ last_encoded = nil # Track whether to preserve or drop whitespace
274
290
 
275
- lines.each do |line|
276
- encoding = split_value_encoding_from_string(line)
291
+ lines = str.split(FULL_ENCODED_VALUE)
292
+ lines.each_slice(2) do |unencoded, encoded|
293
+ if last_encoded = encoded
294
+ if !Utilities.blank?(unencoded) || (!last_encoded && unencoded != EMPTY)
295
+ results << unencoded
296
+ end
277
297
 
278
- if encoding == previous_encoding
279
- line = results.pop + line
298
+ results << encoded
299
+ else
300
+ results << unencoded
280
301
  end
281
-
282
- previous_encoding = encoding
283
- results << line
284
302
  end
285
303
 
286
304
  results
287
305
  end
306
+
307
+ # Partition the string into bounded-size chunks without splitting
308
+ # multibyte characters.
309
+ def Encodings.each_base64_chunk_byterange(str, max_bytesize_per_base64_chunk, &block)
310
+ raise "size per chunk must be multiple of 4" if (max_bytesize_per_base64_chunk % 4).nonzero?
311
+
312
+ if block_given?
313
+ max_bytesize = ((3 * max_bytesize_per_base64_chunk) / 4.0).floor
314
+ each_chunk_byterange(str, max_bytesize, &block)
315
+ else
316
+ enum_for :each_base64_chunk_byterange, str, max_bytesize_per_base64_chunk
317
+ end
318
+ end
319
+
320
+ # Partition the string into bounded-size chunks without splitting
321
+ # multibyte characters.
322
+ def Encodings.each_chunk_byterange(str, max_bytesize_per_chunk)
323
+ return enum_for(:each_chunk_byterange, str, max_bytesize_per_chunk) unless block_given?
324
+
325
+ offset = 0
326
+ chunksize = 0
327
+
328
+ str.each_char do |chr|
329
+ charsize = chr.bytesize
330
+
331
+ if chunksize + charsize > max_bytesize_per_chunk
332
+ yield RubyVer.string_byteslice(str, offset, chunksize)
333
+ offset += chunksize
334
+ chunksize = charsize
335
+ else
336
+ chunksize += charsize
337
+ end
338
+ end
339
+
340
+ yield RubyVer.string_byteslice(str, offset, chunksize)
341
+ end
288
342
  end
289
343
  end
data/lib/mail/envelope.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Mail Envelope
4
5
  #
@@ -11,7 +12,7 @@ module Mail
11
12
  class Envelope < StructuredField
12
13
 
13
14
  def initialize(*args)
14
- super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
15
+ super(FIELD_NAME, args.last.to_s)
15
16
  end
16
17
 
17
18
  def element
data/lib/mail/field.rb CHANGED
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  require 'mail/fields'
3
+ require 'mail/constants'
2
4
 
3
5
  # encoding: utf-8
4
6
  module Mail
@@ -82,9 +84,31 @@ module Mail
82
84
 
83
85
  def initialize(element, value, reason)
84
86
  @element = element
85
- @value = value
86
- @reason = reason
87
- super("#{element} can not parse |#{value}|\nReason was: #{reason}")
87
+ @value = to_utf8(value)
88
+ @reason = to_utf8(reason)
89
+ super("#{@element} can not parse |#{@value}|: #{@reason}")
90
+ end
91
+
92
+ private
93
+ def to_utf8(text)
94
+ if text.respond_to?(:force_encoding)
95
+ text.dup.force_encoding(Encoding::UTF_8)
96
+ else
97
+ text
98
+ end
99
+ end
100
+ end
101
+
102
+ class NilParseError < ParseError #:nodoc:
103
+ def initialize(element)
104
+ super element, nil, 'nil is invalid'
105
+ end
106
+ end
107
+
108
+ class IncompleteParseError < ParseError #:nodoc:
109
+ def initialize(element, original_text, unparsed_index)
110
+ parsed_text = to_utf8(original_text[0...unparsed_index])
111
+ super element, original_text, "Only able to parse up to #{parsed_text.inspect}"
88
112
  end
89
113
  end
90
114
 
@@ -92,41 +116,64 @@ module Mail
92
116
  class SyntaxError < FieldError #:nodoc:
93
117
  end
94
118
 
95
- # Accepts a string:
96
- #
97
- # Field.new("field-name: field data")
98
- #
99
- # Or name, value pair:
100
- #
101
- # Field.new("field-name", "value")
119
+ class << self
120
+ # Parse a field from a raw header line:
121
+ #
122
+ # Mail::Field.parse("field-name: field data")
123
+ # # => #<Mail::Field …>
124
+ def parse(field, charset = nil)
125
+ name, value = split(field)
126
+ if name && value
127
+ new name, value, charset
128
+ end
129
+ end
130
+
131
+ def split(raw_field) #:nodoc:
132
+ if raw_field.index(Constants::COLON)
133
+ name, value = raw_field.split(Constants::COLON, 2)
134
+ name.rstrip!
135
+ if name =~ /\A#{Constants::FIELD_NAME}\z/
136
+ [ name.rstrip, value.strip ]
137
+ else
138
+ Kernel.warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: invalid header name syntax: #{name.inspect}"
139
+ nil
140
+ end
141
+ else
142
+ raw_field.strip
143
+ end
144
+ rescue => error
145
+ warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: #{error.class}: #{error.message}"
146
+ nil
147
+ end
148
+ end
149
+
150
+ attr_reader :unparsed_value
151
+
152
+ # Create a field by name and optional value:
102
153
  #
103
- # Or a name by itself:
154
+ # Mail::Field.new("field-name", "value")
155
+ # # => #<Mail::Field …>
104
156
  #
105
- # Field.new("field-name")
157
+ # Values that aren't strings or arrays are coerced to Strings with `#to_s`.
106
158
  #
107
- # Note, does not want a terminating carriage return. Returns
108
- # self appropriately parsed. If value is not a string, then
109
- # it will be passed through as is, for example, content-type
110
- # field can accept an array with the type and a hash of
111
- # parameters:
159
+ # Mail::Field.new("field-name", 1234)
160
+ # # => #<Mail::Field …>
112
161
  #
113
- # Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
162
+ # Mail::Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
163
+ # # => #<Mail::Field …>
114
164
  def initialize(name, value = nil, charset = 'utf-8')
115
165
  case
116
- when name.index(COLON) # Field.new("field-name: field data")
117
- @charset = value.blank? ? charset : value
118
- @name = name[FIELD_PREFIX]
119
- @raw_value = name
120
- @value = nil
121
- when value.blank? # Field.new("field-name")
166
+ when name.index(COLON)
167
+ Kernel.warn 'Passing an unparsed header field to Mail::Field.new is deprecated and will be removed in Mail 2.8.0. Use Mail::Field.parse instead.'
168
+ @name, @unparsed_value = self.class.split(name)
169
+ @charset = Utilities.blank?(value) ? charset : value
170
+ when Utilities.blank?(value)
122
171
  @name = name
123
- @value = nil
124
- @raw_value = nil
172
+ @unparsed_value = nil
125
173
  @charset = charset
126
- else # Field.new("field-name", "value")
174
+ else
127
175
  @name = name
128
- @value = value
129
- @raw_value = nil
176
+ @unparsed_value = value
130
177
  @charset = charset
131
178
  end
132
179
  @name = FIELD_NAME_MAP[@name.to_s.downcase] || @name
@@ -137,8 +184,7 @@ module Mail
137
184
  end
138
185
 
139
186
  def field
140
- _, @value = split(@raw_value) if @raw_value && !@value
141
- @field ||= create_field(@name, @value, @charset)
187
+ @field ||= create_field(@name, @unparsed_value, @charset)
142
188
  end
143
189
 
144
190
  def name
@@ -168,15 +214,19 @@ module Mail
168
214
  end
169
215
 
170
216
  def same( other )
217
+ return false unless other.kind_of?(self.class)
171
218
  match_to_s(other.name, self.name)
172
219
  end
173
220
 
221
+ def ==( other )
222
+ return false unless other.kind_of?(self.class)
223
+ match_to_s(other.name, self.name) && match_to_s(other.value, self.value)
224
+ end
225
+
174
226
  def responsible_for?( val )
175
227
  name.to_s.casecmp(val.to_s) == 0
176
228
  end
177
229
 
178
- alias_method :==, :same
179
-
180
230
  def <=>( other )
181
231
  self.field_order_id <=> other.field_order_id
182
232
  end
@@ -189,6 +239,16 @@ module Mail
189
239
  field.send(name, *args, &block)
190
240
  end
191
241
 
242
+ if RUBY_VERSION >= '1.9.2'
243
+ def respond_to_missing?(method_name, include_private)
244
+ field.respond_to?(method_name, include_private) || super
245
+ end
246
+ else
247
+ def respond_to?(method_name, include_private = false)
248
+ field.respond_to?(method_name, include_private) || super
249
+ end
250
+ end
251
+
192
252
  FIELD_ORDER = %w[ return-path received
193
253
  resent-date resent-from resent-sender resent-to
194
254
  resent-cc resent-bcc resent-message-id
@@ -202,11 +262,26 @@ module Mail
202
262
 
203
263
  private
204
264
 
205
- def split(raw_field)
206
- match_data = raw_field.mb_chars.match(FIELD_SPLIT)
207
- [match_data[1].to_s.mb_chars.strip, match_data[2].to_s.mb_chars.strip.to_s]
208
- rescue
209
- STDERR.puts "WARNING: Could not parse (and so ignoring) '#{raw_field}'"
265
+ def create_field(name, value, charset)
266
+ new_field(name, value, charset)
267
+ rescue Mail::Field::ParseError => e
268
+ field = Mail::UnstructuredField.new(name, value)
269
+ field.errors << [name, value, e]
270
+ field
271
+ end
272
+
273
+ def new_field(name, value, charset)
274
+ value = unfold(value) if value.is_a?(String)
275
+
276
+ if klass = field_class_for(name)
277
+ klass.new(value, charset)
278
+ else
279
+ OptionalField.new(name, value, charset)
280
+ end
281
+ end
282
+
283
+ def field_class_for(name)
284
+ FIELDS_MAP[name.to_s.downcase]
210
285
  end
211
286
 
212
287
  # 2.2.3. Long Header Fields
@@ -218,30 +293,7 @@ module Mail
218
293
  # treated in its unfolded form for further syntactic and semantic
219
294
  # evaluation.
220
295
  def unfold(string)
221
- string.gsub(/[\r\n \t]+/m, ' ')
222
- end
223
-
224
- def create_field(name, value, charset)
225
- value = unfold(value) if value.is_a?(String)
226
-
227
- begin
228
- new_field(name, value, charset)
229
- rescue Mail::Field::ParseError => e
230
- field = Mail::UnstructuredField.new(name, value)
231
- field.errors << [name, value, e]
232
- field
233
- end
234
- end
235
-
236
- def new_field(name, value, charset)
237
- lower_case_name = name.to_s.downcase
238
- if field_klass = FIELDS_MAP[lower_case_name]
239
- field_klass.new(value, charset)
240
- else
241
- OptionalField.new(name, value, charset)
242
- end
296
+ string.gsub(/#{Constants::CRLF}(#{Constants::WSP})/m, '\1')
243
297
  end
244
-
245
298
  end
246
-
247
299
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
 
4
5
  # Field List class provides an enhanced array that keeps a list of
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Blind Carbon Copy Field
4
5
  #
@@ -36,16 +37,27 @@ module Mail
36
37
  FIELD_NAME = 'bcc'
37
38
  CAPITALIZED_FIELD = 'Bcc'
38
39
 
39
- def initialize(value = '', charset = 'utf-8')
40
+ def initialize(value = nil, charset = 'utf-8')
40
41
  @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
 
46
- # Bcc field should never be :encoded
46
+ def include_in_headers=(include_in_headers)
47
+ @include_in_headers = include_in_headers
48
+ end
49
+
50
+ def include_in_headers
51
+ defined?(@include_in_headers) ? @include_in_headers : self.include_in_headers = false
52
+ end
53
+
54
+ # Bcc field should not be :encoded by default
47
55
  def encoded
48
- ''
56
+ if include_in_headers
57
+ do_encode(CAPITALIZED_FIELD)
58
+ else
59
+ ''
60
+ end
49
61
  end
50
62
 
51
63
  def decoded
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Carbon Copy 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
  # = Comments Field
4
5
  #
@@ -32,7 +33,7 @@ module Mail
32
33
 
33
34
  def initialize(value = nil, charset = 'utf-8')
34
35
  @charset = charset
35
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value))
36
+ super(CAPITALIZED_FIELD, value)
36
37
  self.parse
37
38
  self
38
39
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Mail
2
3
 
3
4
  class AddressContainer < Array
@@ -7,10 +8,10 @@ module Mail
7
8
  super(list)
8
9
  end
9
10
 
10
- def << (address)
11
+ def <<(address)
11
12
  @field << address
12
13
  end
13
14
 
14
15
  end
15
16
 
16
- end
17
+ end