mail 2.7.0 → 2.8.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 (125) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +59 -28
  3. data/lib/mail/attachments_list.rb +2 -5
  4. data/lib/mail/body.rb +33 -50
  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/transfer_encoding.rb +1 -1
  23. data/lib/mail/encodings/unix_to_unix.rb +1 -0
  24. data/lib/mail/encodings.rb +30 -59
  25. data/lib/mail/envelope.rb +11 -14
  26. data/lib/mail/field.rb +39 -53
  27. data/lib/mail/field_list.rb +60 -7
  28. data/lib/mail/fields/bcc_field.rb +34 -52
  29. data/lib/mail/fields/cc_field.rb +28 -49
  30. data/lib/mail/fields/comments_field.rb +27 -37
  31. data/lib/mail/fields/common_address_field.rb +170 -0
  32. data/lib/mail/fields/common_date_field.rb +58 -0
  33. data/lib/mail/fields/common_field.rb +77 -0
  34. data/lib/mail/fields/common_message_id_field.rb +42 -0
  35. data/lib/mail/fields/content_description_field.rb +7 -14
  36. data/lib/mail/fields/content_disposition_field.rb +13 -38
  37. data/lib/mail/fields/content_id_field.rb +24 -51
  38. data/lib/mail/fields/content_location_field.rb +11 -25
  39. data/lib/mail/fields/content_transfer_encoding_field.rb +31 -31
  40. data/lib/mail/fields/content_type_field.rb +47 -72
  41. data/lib/mail/fields/date_field.rb +23 -51
  42. data/lib/mail/fields/from_field.rb +28 -49
  43. data/lib/mail/fields/in_reply_to_field.rb +38 -49
  44. data/lib/mail/fields/keywords_field.rb +18 -31
  45. data/lib/mail/fields/message_id_field.rb +25 -71
  46. data/lib/mail/fields/mime_version_field.rb +19 -30
  47. data/lib/mail/fields/named_structured_field.rb +11 -0
  48. data/lib/mail/fields/named_unstructured_field.rb +11 -0
  49. data/lib/mail/fields/optional_field.rb +5 -6
  50. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +12 -10
  51. data/lib/mail/fields/received_field.rb +43 -57
  52. data/lib/mail/fields/references_field.rb +35 -49
  53. data/lib/mail/fields/reply_to_field.rb +28 -49
  54. data/lib/mail/fields/resent_bcc_field.rb +28 -49
  55. data/lib/mail/fields/resent_cc_field.rb +28 -49
  56. data/lib/mail/fields/resent_date_field.rb +5 -29
  57. data/lib/mail/fields/resent_from_field.rb +28 -49
  58. data/lib/mail/fields/resent_message_id_field.rb +5 -29
  59. data/lib/mail/fields/resent_sender_field.rb +27 -56
  60. data/lib/mail/fields/resent_to_field.rb +28 -49
  61. data/lib/mail/fields/return_path_field.rb +50 -54
  62. data/lib/mail/fields/sender_field.rb +34 -55
  63. data/lib/mail/fields/structured_field.rb +3 -30
  64. data/lib/mail/fields/subject_field.rb +9 -11
  65. data/lib/mail/fields/to_field.rb +28 -49
  66. data/lib/mail/fields/unstructured_field.rb +16 -48
  67. data/lib/mail/header.rb +69 -110
  68. data/lib/mail/matchers/attachment_matchers.rb +15 -0
  69. data/lib/mail/message.rb +53 -68
  70. data/lib/mail/multibyte/chars.rb +8 -166
  71. data/lib/mail/multibyte/unicode.rb +10 -10
  72. data/lib/mail/multibyte/utils.rb +26 -43
  73. data/lib/mail/multibyte.rb +1 -11
  74. data/lib/mail/network/delivery_methods/exim.rb +5 -4
  75. data/lib/mail/network/delivery_methods/file_delivery.rb +11 -10
  76. data/lib/mail/network/delivery_methods/logger_delivery.rb +2 -5
  77. data/lib/mail/network/delivery_methods/sendmail.rb +56 -18
  78. data/lib/mail/network/delivery_methods/smtp.rb +25 -9
  79. data/lib/mail/network/delivery_methods/smtp_connection.rb +3 -12
  80. data/lib/mail/network/delivery_methods/test_mailer.rb +4 -2
  81. data/lib/mail/network/retriever_methods/base.rb +8 -8
  82. data/lib/mail/network/retriever_methods/imap.rb +3 -3
  83. data/lib/mail/network/retriever_methods/pop3.rb +2 -2
  84. data/lib/mail/network/retriever_methods/test_retriever.rb +2 -1
  85. data/lib/mail/parsers/address_lists_parser.rb +33175 -33140
  86. data/lib/mail/parsers/address_lists_parser.rl +7 -0
  87. data/lib/mail/parsers/content_disposition_parser.rb +889 -889
  88. data/lib/mail/parsers/content_disposition_parser.rl +7 -0
  89. data/lib/mail/parsers/content_location_parser.rb +796 -787
  90. data/lib/mail/parsers/content_location_parser.rl +7 -0
  91. data/lib/mail/parsers/content_transfer_encoding_parser.rb +496 -496
  92. data/lib/mail/parsers/content_transfer_encoding_parser.rl +7 -0
  93. data/lib/mail/parsers/content_type_parser.rb +1008 -1005
  94. data/lib/mail/parsers/content_type_parser.rl +7 -0
  95. data/lib/mail/parsers/date_time_parser.rb +864 -859
  96. data/lib/mail/parsers/date_time_parser.rl +7 -0
  97. data/lib/mail/parsers/envelope_from_parser.rb +3649 -3548
  98. data/lib/mail/parsers/envelope_from_parser.rl +7 -0
  99. data/lib/mail/parsers/message_ids_parser.rb +5135 -2832
  100. data/lib/mail/parsers/message_ids_parser.rl +12 -1
  101. data/lib/mail/parsers/mime_version_parser.rb +487 -483
  102. data/lib/mail/parsers/mime_version_parser.rl +7 -0
  103. data/lib/mail/parsers/phrase_lists_parser.rb +858 -865
  104. data/lib/mail/parsers/phrase_lists_parser.rl +8 -1
  105. data/lib/mail/parsers/received_parser.rb +8756 -8728
  106. data/lib/mail/parsers/received_parser.rl +7 -0
  107. data/lib/mail/parsers/rfc5322.rl +28 -13
  108. data/lib/mail/parsers.rb +11 -17
  109. data/lib/mail/part.rb +6 -10
  110. data/lib/mail/parts_list.rb +57 -0
  111. data/lib/mail/smtp_envelope.rb +57 -0
  112. data/lib/mail/utilities.rb +325 -87
  113. data/lib/mail/version.rb +2 -2
  114. data/lib/mail/yaml.rb +30 -0
  115. data/lib/mail.rb +3 -20
  116. metadata +86 -18
  117. data/lib/mail/core_extensions/smtp.rb +0 -28
  118. data/lib/mail/core_extensions/string.rb +0 -17
  119. data/lib/mail/fields/common/address_container.rb +0 -17
  120. data/lib/mail/fields/common/common_address.rb +0 -161
  121. data/lib/mail/fields/common/common_date.rb +0 -36
  122. data/lib/mail/fields/common/common_field.rb +0 -52
  123. data/lib/mail/fields/common/common_message_id.rb +0 -49
  124. data/lib/mail/version_specific/ruby_1_8.rb +0 -163
  125. data/lib/mail/version_specific/ruby_1_9.rb +0 -278
@@ -1,18 +1,15 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
3
  require 'mail/constants'
4
+ require 'socket'
4
5
 
5
6
  module Mail
6
7
  module Utilities
7
-
8
- LF = "\n"
9
- CRLF = "\r\n"
10
-
11
- include Constants
8
+ extend self
12
9
 
13
10
  # Returns true if the string supplied is free from characters not allowed as an ATOM
14
11
  def atom_safe?( str )
15
- not ATOM_UNSAFE === str
12
+ not Constants::ATOM_UNSAFE === str
16
13
  end
17
14
 
18
15
  # If the string supplied has ATOM unsafe characters in it, will return the string quoted
@@ -24,28 +21,38 @@ module Mail
24
21
  # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
25
22
  # in double quotes, otherwise returns the string unmodified
26
23
  def quote_phrase( str )
27
- if RUBY_VERSION >= '1.9'
24
+ if str.respond_to?(:force_encoding)
28
25
  original_encoding = str.encoding
29
- ascii_str = str.dup.force_encoding('ASCII-8BIT')
30
- if (PHRASE_UNSAFE === ascii_str)
26
+ ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
27
+ if Constants::PHRASE_UNSAFE === ascii_str
31
28
  dquote(ascii_str).force_encoding(original_encoding)
32
29
  else
33
30
  str
34
31
  end
35
32
  else
36
- (PHRASE_UNSAFE === str) ? dquote(str) : str
33
+ Constants::PHRASE_UNSAFE === str ? dquote(str) : str
37
34
  end
38
35
  end
39
36
 
40
37
  # Returns true if the string supplied is free from characters not allowed as a TOKEN
41
38
  def token_safe?( str )
42
- not TOKEN_UNSAFE === str
39
+ not Constants::TOKEN_UNSAFE === str
43
40
  end
44
41
 
45
42
  # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
46
43
  # in double quotes, otherwise returns the string unmodified
47
44
  def quote_token( str )
48
- token_safe?( str ) ? str : dquote(str)
45
+ if str.respond_to?(:force_encoding)
46
+ original_encoding = str.encoding
47
+ ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
48
+ if token_safe?( ascii_str )
49
+ str
50
+ else
51
+ dquote(ascii_str).force_encoding(original_encoding)
52
+ end
53
+ else
54
+ token_safe?( str ) ? str : dquote(str)
55
+ end
49
56
  end
50
57
 
51
58
  # Wraps supplied string in double quotes and applies \-escaping as necessary,
@@ -79,7 +86,6 @@ module Mail
79
86
  str
80
87
  end
81
88
  end
82
- module_function :unquote
83
89
 
84
90
  # Removes any \-escaping.
85
91
  #
@@ -93,7 +99,6 @@ module Mail
93
99
  def unescape( str )
94
100
  str.gsub(/\\(.)/, '\1')
95
101
  end
96
- module_function :unescape
97
102
 
98
103
  # Wraps a string in parenthesis and escapes any that are in the string itself.
99
104
  #
@@ -101,7 +106,7 @@ module Mail
101
106
  #
102
107
  # paren( 'This is a string' ) #=> '(This is a string)'
103
108
  def paren( str )
104
- RubyVer.paren( str )
109
+ Utilities.paren( str )
105
110
  end
106
111
 
107
112
  # Unwraps a string from being wrapped in parenthesis
@@ -111,8 +116,11 @@ module Mail
111
116
  # str = '(This is a string)'
112
117
  # unparen( str ) #=> 'This is a string'
113
118
  def unparen( str )
114
- match = str.match(/^\((.*?)\)$/)
115
- match ? match[1] : str
119
+ if str.start_with?('(') && str.end_with?(')')
120
+ str.slice(1..-2)
121
+ else
122
+ str
123
+ end
116
124
  end
117
125
 
118
126
  # Wraps a string in angle brackets and escapes any that are in the string itself
@@ -121,7 +129,7 @@ module Mail
121
129
  #
122
130
  # bracket( 'This is a string' ) #=> '<This is a string>'
123
131
  def bracket( str )
124
- RubyVer.bracket( str )
132
+ Utilities.bracket( str )
125
133
  end
126
134
 
127
135
  # Unwraps a string from being wrapped in parenthesis
@@ -131,8 +139,11 @@ module Mail
131
139
  # str = '<This is a string>'
132
140
  # unbracket( str ) #=> 'This is a string'
133
141
  def unbracket( str )
134
- match = str.match(/^\<(.*?)\>$/)
135
- match ? match[1] : str
142
+ if str.start_with?('<') && str.end_with?('>')
143
+ str.slice(1..-2)
144
+ else
145
+ str
146
+ end
136
147
  end
137
148
 
138
149
  # Escape parenthesies in a string
@@ -142,7 +153,7 @@ module Mail
142
153
  # str = 'This is (a) string'
143
154
  # escape_paren( str ) #=> 'This is \(a\) string'
144
155
  def escape_paren( str )
145
- RubyVer.escape_paren( str )
156
+ Utilities.escape_paren( str )
146
157
  end
147
158
 
148
159
  def uri_escape( str )
@@ -154,7 +165,7 @@ module Mail
154
165
  end
155
166
 
156
167
  def uri_parser
157
- @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
168
+ @uri_parser ||= URI.const_defined?(:DEFAULT_PARSER) ? URI::DEFAULT_PARSER : URI
158
169
  end
159
170
 
160
171
  # Matches two objects with their to_s values case insensitively
@@ -197,7 +208,7 @@ module Mail
197
208
  # string = :resent_from_field
198
209
  # dasherize( string ) #=> 'resent-from-field'
199
210
  def dasherize( str )
200
- str.to_s.tr(UNDERSCORE, HYPHEN)
211
+ str.to_s.tr(Constants::UNDERSCORE, Constants::HYPHEN)
201
212
  end
202
213
 
203
214
  # Swaps out all hyphens (-) for underscores (_) good for stringing to symbols
@@ -208,78 +219,36 @@ module Mail
208
219
  # string = :resent_from_field
209
220
  # underscoreize ( string ) #=> 'resent_from_field'
210
221
  def underscoreize( str )
211
- str.to_s.downcase.tr(HYPHEN, UNDERSCORE)
222
+ str.to_s.downcase.tr(Constants::HYPHEN, Constants::UNDERSCORE)
212
223
  end
213
224
 
214
- if RUBY_VERSION <= '1.8.6'
215
-
216
- def map_lines( str, &block )
217
- results = []
218
- str.each_line do |line|
219
- results << yield(line)
220
- end
221
- results
222
- end
223
-
224
- def map_with_index( enum, &block )
225
- results = []
226
- enum.each_with_index do |token, i|
227
- results[i] = yield(token, i)
228
- end
229
- results
230
- end
231
-
232
- else
233
-
234
- def map_lines( str, &block )
235
- str.each_line.map(&block)
236
- end
237
-
238
- def map_with_index( enum, &block )
239
- enum.each_with_index.map(&block)
240
- end
241
-
225
+ def map_lines( str, &block )
226
+ str.each_line.map(&block)
242
227
  end
243
228
 
244
- # Test String#encode works correctly with line endings.
245
- # Some versions of Ruby (e.g. MRI <1.9, JRuby, Rubinius) line ending
246
- # normalization does not work correctly or did not have #encode.
247
- if ("\r".encode(:universal_newline => true) rescue nil) == LF &&
248
- (LF.encode(:crlf_newline => true) rescue nil) == CRLF
249
- # Using String#encode is better performing than Regexp
250
-
251
- def self.binary_unsafe_to_lf(string) #:nodoc:
252
- string.encode(string.encoding, :universal_newline => true)
253
- end
254
-
255
- def self.binary_unsafe_to_crlf(string) #:nodoc:
256
- string.encode(string.encoding, :universal_newline => true).encode!(string.encoding, :crlf_newline => true)
257
- end
229
+ def map_with_index( enum, &block )
230
+ enum.each_with_index.map(&block)
231
+ end
258
232
 
259
- def self.safe_for_line_ending_conversion?(string) #:nodoc:
260
- string.ascii_only?
261
- end
262
- else
263
- def self.binary_unsafe_to_lf(string) #:nodoc:
264
- string.gsub(/\r\n|\r/, LF)
265
- end
233
+ def self.binary_unsafe_to_lf(string) #:nodoc:
234
+ string.gsub(/\r\n|\r/, Constants::LF)
235
+ end
266
236
 
267
- TO_CRLF_REGEX =
268
- if RUBY_VERSION >= '1.9'
269
- # This 1.9 only regex can save a reasonable amount of time (~20%)
270
- # by not matching "\r\n" so the string is returned unchanged in
271
- # the common case.
272
- Regexp.new("(?<!\r)\n|\r(?!\n)")
273
- else
274
- /\n|\r\n|\r/
275
- end
237
+ TO_CRLF_REGEX =
238
+ # This 1.9 only regex can save a reasonable amount of time (~20%)
239
+ # by not matching "\r\n" so the string is returned unchanged in
240
+ # the common case.
241
+ Regexp.new("(?<!\r)\n|\r(?!\n)")
276
242
 
277
- def self.binary_unsafe_to_crlf(string) #:nodoc:
278
- string.gsub(TO_CRLF_REGEX, CRLF)
279
- end
243
+ def self.binary_unsafe_to_crlf(string) #:nodoc:
244
+ string.gsub(TO_CRLF_REGEX, Constants::CRLF)
245
+ end
280
246
 
281
- def self.safe_for_line_ending_conversion?(string) #:nodoc:
247
+ def self.safe_for_line_ending_conversion?(string) #:nodoc:
248
+ if string.encoding == Encoding::BINARY
282
249
  string.ascii_only?
250
+ else
251
+ string.valid_encoding?
283
252
  end
284
253
  end
285
254
 
@@ -311,7 +280,7 @@ module Mail
311
280
  # and arrays and hashes that have nothing in them.
312
281
  #
313
282
  # This logic is mostly shared with ActiveSupport's blank?
314
- def self.blank?(value)
283
+ def blank?(value)
315
284
  if value.kind_of?(NilClass)
316
285
  true
317
286
  elsif value.kind_of?(String)
@@ -320,5 +289,274 @@ module Mail
320
289
  value.respond_to?(:empty?) ? value.empty? : !value
321
290
  end
322
291
  end
292
+
293
+ def generate_message_id
294
+ "<#{Mail.random_tag}@#{::Socket.gethostname}.mail>"
295
+ end
296
+
297
+ class StrictCharsetEncoder
298
+ def encode(string, charset)
299
+ case charset
300
+ when /utf-?7/i
301
+ Mail::Utilities.decode_utf7(string)
302
+ else
303
+ string.force_encoding(Mail::Utilities.pick_encoding(charset))
304
+ end
305
+ end
306
+ end
307
+
308
+ class BestEffortCharsetEncoder
309
+ def encode(string, charset)
310
+ case charset
311
+ when /utf-?7/i
312
+ Mail::Utilities.decode_utf7(string)
313
+ else
314
+ string.force_encoding(pick_encoding(charset))
315
+ end
316
+ end
317
+
318
+ private
319
+
320
+ def pick_encoding(charset)
321
+ charset = case charset
322
+ when /ansi_x3.110-1983/
323
+ 'ISO-8859-1'
324
+ when /Windows-?1258/i # Windows-1258 is similar to 1252
325
+ "Windows-1252"
326
+ else
327
+ charset
328
+ end
329
+ Mail::Utilities.pick_encoding(charset)
330
+ end
331
+ end
332
+
333
+ class << self
334
+ attr_accessor :charset_encoder
335
+ end
336
+ self.charset_encoder = BestEffortCharsetEncoder.new
337
+
338
+ # Escapes any parenthesis in a string that are unescaped this uses
339
+ # a Ruby 1.9.1 regexp feature of negative look behind
340
+ def Utilities.escape_paren( str )
341
+ re = /(?<!\\)([\(\)])/ # Only match unescaped parens
342
+ str.gsub(re) { |s| '\\' + s }
343
+ end
344
+
345
+ def Utilities.paren( str )
346
+ str = ::Mail::Utilities.unparen( str )
347
+ str = escape_paren( str )
348
+ '(' + str + ')'
349
+ end
350
+
351
+ def Utilities.escape_bracket( str )
352
+ re = /(?<!\\)([\<\>])/ # Only match unescaped brackets
353
+ str.gsub(re) { |s| '\\' + s }
354
+ end
355
+
356
+ def Utilities.bracket( str )
357
+ str = ::Mail::Utilities.unbracket( str )
358
+ str = escape_bracket( str )
359
+ '<' + str + '>'
360
+ end
361
+
362
+ def Utilities.decode_base64(str)
363
+ if !str.end_with?("=") && str.length % 4 != 0
364
+ str = str.ljust((str.length + 3) & ~3, "=")
365
+ end
366
+ str.unpack( 'm' ).first
367
+ end
368
+
369
+ def Utilities.encode_base64(str)
370
+ [str].pack( 'm' )
371
+ end
372
+
373
+ def Utilities.has_constant?(klass, string)
374
+ klass.const_defined?( string, false )
375
+ end
376
+
377
+ def Utilities.get_constant(klass, string)
378
+ klass.const_get( string )
379
+ end
380
+
381
+ def Utilities.transcode_charset(str, from_encoding, to_encoding = Encoding::UTF_8)
382
+ to_encoding = Encoding.find(to_encoding)
383
+ replacement_char = to_encoding == Encoding::UTF_8 ? '�' : '?'
384
+ charset_encoder.encode(str.dup, from_encoding).encode(to_encoding, :undef => :replace, :invalid => :replace, :replace => replacement_char)
385
+ end
386
+
387
+ # From Ruby stdlib Net::IMAP
388
+ def Utilities.encode_utf7(string)
389
+ string.gsub(/(&)|[^\x20-\x7e]+/) do
390
+ if $1
391
+ "&-"
392
+ else
393
+ base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
394
+ "&" + base64.delete("=").tr("/", ",") + "-"
395
+ end
396
+ end.force_encoding(Encoding::ASCII_8BIT)
397
+ end
398
+
399
+ def Utilities.decode_utf7(utf7)
400
+ utf7.gsub(/&([^-]+)?-/n) do
401
+ if $1
402
+ ($1.tr(",", "/") + "===").unpack("m")[0].encode(Encoding::UTF_8, Encoding::UTF_16BE)
403
+ else
404
+ "&"
405
+ end
406
+ end
407
+ end
408
+
409
+ def Utilities.b_value_encode(str, encoding = nil)
410
+ encoding = str.encoding.to_s
411
+ [Utilities.encode_base64(str), encoding]
412
+ end
413
+
414
+ def Utilities.b_value_decode(str)
415
+ match = str.match(/\=\?(.+)?\?[Bb]\?(.*)\?\=/m)
416
+ if match
417
+ charset = match[1]
418
+ str = Utilities.decode_base64(match[2])
419
+ str = charset_encoder.encode(str, charset)
420
+ end
421
+ transcode_to_scrubbed_utf8(str)
422
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError, Encoding::InvalidByteSequenceError
423
+ warn "Encoding conversion failed #{$!}"
424
+ str.dup.force_encoding(Encoding::UTF_8)
425
+ end
426
+
427
+ def Utilities.q_value_encode(str, encoding = nil)
428
+ encoding = str.encoding.to_s
429
+ [Encodings::QuotedPrintable.encode(str), encoding]
430
+ end
431
+
432
+ def Utilities.q_value_decode(str)
433
+ match = str.match(/\=\?(.+)?\?[Qq]\?(.*)\?\=/m)
434
+ if match
435
+ charset = match[1]
436
+ string = match[2].gsub(/_/, '=20')
437
+ # Remove trailing = if it exists in a Q encoding
438
+ string = string.sub(/\=$/, '')
439
+ str = Encodings::QuotedPrintable.decode(string)
440
+ str = charset_encoder.encode(str, charset)
441
+ # We assume that binary strings hold utf-8 directly to work around
442
+ # jruby/jruby#829 which subtly changes String#encode semantics.
443
+ str.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::ASCII_8BIT
444
+ end
445
+ transcode_to_scrubbed_utf8(str)
446
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
447
+ warn "Encoding conversion failed #{$!}"
448
+ str.dup.force_encoding(Encoding::UTF_8)
449
+ end
450
+
451
+ def Utilities.param_decode(str, encoding)
452
+ str = uri_parser.unescape(str)
453
+ str = charset_encoder.encode(str, encoding) if encoding
454
+ transcode_to_scrubbed_utf8(str)
455
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
456
+ warn "Encoding conversion failed #{$!}"
457
+ str.dup.force_encoding(Encoding::UTF_8)
458
+ end
459
+
460
+ def Utilities.param_encode(str)
461
+ encoding = str.encoding.to_s.downcase
462
+ language = Configuration.instance.param_encode_language
463
+ "#{encoding}'#{language}'#{uri_parser.escape(str)}"
464
+ end
465
+
466
+ def Utilities.uri_parser
467
+ URI::DEFAULT_PARSER
468
+ end
469
+
470
+ # Pick a Ruby encoding corresponding to the message charset. Most
471
+ # charsets have a Ruby encoding, but some need manual aliasing here.
472
+ #
473
+ # TODO: add this as a test somewhere:
474
+ # Encoding.list.map { |e| [e.to_s.upcase == pick_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
475
+ # Encoding.list.map { |e| [e.to_s == pick_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
476
+ def Utilities.pick_encoding(charset)
477
+ charset = charset.to_s
478
+ encoding = case charset.downcase
479
+
480
+ # ISO-8859-8-I etc. http://en.wikipedia.org/wiki/ISO-8859-8-I
481
+ when /^iso[-_]?8859-(\d+)(-i)?$/
482
+ "ISO-8859-#{$1}"
483
+
484
+ # ISO-8859-15, ISO-2022-JP and alike
485
+ when /^iso[-_]?(\d{4})-?(\w{1,2})$/
486
+ "ISO-#{$1}-#{$2}"
487
+
488
+ # "ISO-2022-JP-KDDI" and alike
489
+ when /^iso[-_]?(\d{4})-?(\w{1,2})-?(\w*)$/
490
+ "ISO-#{$1}-#{$2}-#{$3}"
491
+
492
+ # UTF-8, UTF-32BE and alike
493
+ when /^utf[\-_]?(\d{1,2})?(\w{1,2})$/
494
+ "UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
495
+
496
+ # Windows-1252 and alike
497
+ when /^windows-?(.*)$/
498
+ "Windows-#{$1}"
499
+
500
+ when '8bit'
501
+ Encoding::ASCII_8BIT
502
+
503
+ # alternatives/misspellings of us-ascii seen in the wild
504
+ when /^iso[-_]?646(-us)?$/, 'us=ascii'
505
+ Encoding::ASCII
506
+
507
+ # Microsoft-specific alias for MACROMAN
508
+ when 'macintosh'
509
+ Encoding::MACROMAN
510
+
511
+ # Microsoft-specific alias for CP949 (Korean)
512
+ when 'ks_c_5601-1987'
513
+ Encoding::CP949
514
+
515
+ # Wrongly written Shift_JIS (Japanese)
516
+ when 'shift-jis'
517
+ Encoding::Shift_JIS
518
+
519
+ # GB2312 (Chinese charset) is a subset of GB18030 (its replacement)
520
+ when 'gb2312'
521
+ Encoding::GB18030
522
+
523
+ when 'cp-850'
524
+ Encoding::CP850
525
+
526
+ when 'latin2'
527
+ Encoding::ISO_8859_2
528
+
529
+ else
530
+ charset
531
+ end
532
+
533
+ convert_to_encoding(encoding)
534
+ end
535
+
536
+ def Utilities.string_byteslice(str, *args)
537
+ str.byteslice(*args)
538
+ end
539
+
540
+ class << self
541
+ private
542
+
543
+ def convert_to_encoding(encoding)
544
+ if encoding.is_a?(Encoding)
545
+ encoding
546
+ else
547
+ # Fall back to ASCII for charsets that Ruby doesn't recognize
548
+ begin
549
+ Encoding.find(encoding)
550
+ rescue ArgumentError
551
+ Encoding::BINARY
552
+ end
553
+ end
554
+ end
555
+
556
+ def transcode_to_scrubbed_utf8(str)
557
+ decoded = str.encode(Encoding::UTF_8, :undef => :replace, :invalid => :replace, :replace => "�")
558
+ decoded.valid_encoding? ? decoded : decoded.encode(Encoding::UTF_16LE, :invalid => :replace, :replace => "�").encode(Encoding::UTF_8)
559
+ end
560
+ end
323
561
  end
324
562
  end
data/lib/mail/version.rb CHANGED
@@ -3,8 +3,8 @@ module Mail
3
3
  module VERSION
4
4
 
5
5
  MAJOR = 2
6
- MINOR = 7
7
- PATCH = 0
6
+ MINOR = 8
7
+ PATCH = 1
8
8
  BUILD = nil
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
data/lib/mail/yaml.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'yaml'
2
+
3
+ module Mail
4
+ module YAML
5
+ def self.load(yaml)
6
+ permitted_classes = [
7
+ Symbol,
8
+
9
+ Mail::Body,
10
+
11
+ # Delivery methods as listed in mail/configuration.rb
12
+ Mail::SMTP,
13
+ Mail::Sendmail,
14
+ Mail::Exim,
15
+ Mail::FileDelivery,
16
+ Mail::SMTPConnection,
17
+ Mail::TestMailer,
18
+ Mail::LoggerDelivery,
19
+
20
+ Mail.delivery_method.class,
21
+ ]
22
+
23
+ if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1')
24
+ ::YAML.safe_load(yaml, :permitted_classes => permitted_classes)
25
+ else
26
+ ::YAML.safe_load(yaml, permitted_classes)
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/mail.rb CHANGED
@@ -9,26 +9,8 @@ module Mail # :doc:
9
9
  require 'net/smtp'
10
10
  require 'mini_mime'
11
11
 
12
- if RUBY_VERSION <= '1.8.6'
13
- begin
14
- require 'tlsmail'
15
- rescue LoadError
16
- raise "You need to install tlsmail if you are using ruby <= 1.8.6"
17
- end
18
- end
19
-
20
- if RUBY_VERSION >= "1.9.0"
21
- require 'mail/version_specific/ruby_1_9'
22
- RubyVer = Ruby19
23
- else
24
- require 'mail/version_specific/ruby_1_8'
25
- RubyVer = Ruby18
26
- end
27
-
28
12
  require 'mail/version'
29
13
 
30
- require 'mail/core_extensions/string'
31
- require 'mail/core_extensions/smtp'
32
14
  require 'mail/indifferent_hash'
33
15
 
34
16
  require 'mail/multibyte'
@@ -68,8 +50,6 @@ module Mail # :doc:
68
50
 
69
51
  require 'mail/envelope'
70
52
 
71
- register_autoload :Parsers, "mail/parsers"
72
-
73
53
  # Autoload header field elements and transfer encodings.
74
54
  require 'mail/elements'
75
55
  require 'mail/encodings'
@@ -80,6 +60,9 @@ module Mail # :doc:
80
60
  require 'mail/matchers/has_sent_mail'
81
61
  require 'mail/matchers/attachment_matchers.rb'
82
62
 
63
+ # Deprecated will be removed in 3.0 release
64
+ require 'mail/check_delivery_params'
65
+
83
66
  # Finally... require all the Mail.methods
84
67
  require 'mail/mail'
85
68
  end