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,7 +1,14 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/constants'
4
+
2
5
  module Mail
3
6
  module Utilities
4
- include Patterns
7
+
8
+ LF = "\n"
9
+ CRLF = "\r\n"
10
+
11
+ include Constants
5
12
 
6
13
  # Returns true if the string supplied is free from characters not allowed as an ATOM
7
14
  def atom_safe?( str )
@@ -17,15 +24,13 @@ module Mail
17
24
  # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
18
25
  # in double quotes, otherwise returns the string unmodified
19
26
  def quote_phrase( str )
20
- if RUBY_VERSION >= '1.9'
27
+ if str.respond_to?(:force_encoding)
21
28
  original_encoding = str.encoding
22
- str.force_encoding('ASCII-8BIT')
23
- if (PHRASE_UNSAFE === str)
24
- quoted_str = dquote(str).force_encoding(original_encoding)
25
- str.force_encoding(original_encoding)
26
- quoted_str
29
+ ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
30
+ if (PHRASE_UNSAFE === ascii_str)
31
+ dquote(ascii_str).force_encoding(original_encoding)
27
32
  else
28
- str.force_encoding(original_encoding)
33
+ str
29
34
  end
30
35
  else
31
36
  (PHRASE_UNSAFE === str) ? dquote(str) : str
@@ -40,7 +45,17 @@ module Mail
40
45
  # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
41
46
  # in double quotes, otherwise returns the string unmodified
42
47
  def quote_token( str )
43
- token_safe?( str ) ? str : dquote(str)
48
+ if str.respond_to?(:force_encoding)
49
+ original_encoding = str.encoding
50
+ ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
51
+ if token_safe?( ascii_str )
52
+ str
53
+ else
54
+ dquote(ascii_str).force_encoding(original_encoding)
55
+ end
56
+ else
57
+ token_safe?( str ) ? str : dquote(str)
58
+ end
44
59
  end
45
60
 
46
61
  # Wraps supplied string in double quotes and applies \-escaping as necessary,
@@ -69,11 +84,26 @@ module Mail
69
84
  # unqoute(string) #=> 'This is "a string"'
70
85
  def unquote( str )
71
86
  if str =~ /^"(.*?)"$/
72
- $1.gsub(/\\(.)/, '\1')
87
+ unescape($1)
73
88
  else
74
89
  str
75
90
  end
76
91
  end
92
+ module_function :unquote
93
+
94
+ # Removes any \-escaping.
95
+ #
96
+ # Example:
97
+ #
98
+ # string = 'This is \"a string\"'
99
+ # unescape(string) #=> 'This is "a string"'
100
+ #
101
+ # string = '"This is \"a string\""'
102
+ # unescape(string) #=> '"This is "a string""'
103
+ def unescape( str )
104
+ str.gsub(/\\(.)/, '\1')
105
+ end
106
+ module_function :unescape
77
107
 
78
108
  # Wraps a string in parenthesis and escapes any that are in the string itself.
79
109
  #
@@ -175,9 +205,9 @@ module Mail
175
205
  # Example:
176
206
  #
177
207
  # string = :resent_from_field
178
- # dasherize ( string ) #=> 'resent_from_field'
208
+ # dasherize( string ) #=> 'resent-from-field'
179
209
  def dasherize( str )
180
- str.to_s.gsub('_', '-')
210
+ str.to_s.tr(UNDERSCORE, HYPHEN)
181
211
  end
182
212
 
183
213
  # Swaps out all hyphens (-) for underscores (_) good for stringing to symbols
@@ -188,7 +218,7 @@ module Mail
188
218
  # string = :resent_from_field
189
219
  # underscoreize ( string ) #=> 'resent_from_field'
190
220
  def underscoreize( str )
191
- str.to_s.downcase.gsub('-', '_')
221
+ str.to_s.downcase.tr(HYPHEN, UNDERSCORE)
192
222
  end
193
223
 
194
224
  if RUBY_VERSION <= '1.8.6'
@@ -221,5 +251,74 @@ module Mail
221
251
 
222
252
  end
223
253
 
254
+ def self.binary_unsafe_to_lf(string) #:nodoc:
255
+ string.gsub(/\r\n|\r/, LF)
256
+ end
257
+
258
+ TO_CRLF_REGEX =
259
+ if RUBY_VERSION >= '1.9'
260
+ # This 1.9 only regex can save a reasonable amount of time (~20%)
261
+ # by not matching "\r\n" so the string is returned unchanged in
262
+ # the common case.
263
+ Regexp.new("(?<!\r)\n|\r(?!\n)")
264
+ else
265
+ /\n|\r\n|\r/
266
+ end
267
+
268
+ def self.binary_unsafe_to_crlf(string) #:nodoc:
269
+ string.gsub(TO_CRLF_REGEX, CRLF)
270
+ end
271
+
272
+ if RUBY_VERSION < '1.9'
273
+ def self.safe_for_line_ending_conversion?(string) #:nodoc:
274
+ string.ascii_only?
275
+ end
276
+ else
277
+ def self.safe_for_line_ending_conversion?(string) #:nodoc:
278
+ if string.encoding == Encoding::BINARY
279
+ string.ascii_only?
280
+ else
281
+ string.valid_encoding?
282
+ end
283
+ end
284
+ end
285
+
286
+ # Convert line endings to \n unless the string is binary. Used for
287
+ # sendmail delivery and for decoding 8bit Content-Transfer-Encoding.
288
+ def self.to_lf(string)
289
+ string = string.to_s
290
+ if safe_for_line_ending_conversion? string
291
+ binary_unsafe_to_lf string
292
+ else
293
+ string
294
+ end
295
+ end
296
+
297
+ # Convert line endings to \r\n unless the string is binary. Used for
298
+ # encoding 8bit and base64 Content-Transfer-Encoding and for convenience
299
+ # when parsing emails with \n line endings instead of the required \r\n.
300
+ def self.to_crlf(string)
301
+ string = string.to_s
302
+ if safe_for_line_ending_conversion? string
303
+ binary_unsafe_to_crlf string
304
+ else
305
+ string
306
+ end
307
+ end
308
+
309
+ # Returns true if the object is considered blank.
310
+ # A blank includes things like '', ' ', nil,
311
+ # and arrays and hashes that have nothing in them.
312
+ #
313
+ # This logic is mostly shared with ActiveSupport's blank?
314
+ def self.blank?(value)
315
+ if value.kind_of?(NilClass)
316
+ true
317
+ elsif value.kind_of?(String)
318
+ value !~ /\S/
319
+ else
320
+ value.respond_to?(:empty?) ? value.empty? : !value
321
+ end
322
+ end
224
323
  end
225
324
  end
Binary file
data/lib/mail/version.rb CHANGED
@@ -1,24 +1,17 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
  module Mail
3
3
  module VERSION
4
-
5
- version = {}
6
- File.read(File.join(File.dirname(__FILE__), '../', '../', 'VERSION')).each_line do |line|
7
- type, value = line.chomp.split(":")
8
- next if type =~ /^\s+$/ || value =~ /^\s+$/
9
- version[type] = value
10
- end
11
-
12
- MAJOR = version['major']
13
- MINOR = version['minor']
14
- PATCH = version['patch']
15
- BUILD = version['build']
4
+
5
+ MAJOR = 2
6
+ MINOR = 7
7
+ PATCH = 1
8
+ BUILD = nil
16
9
 
17
10
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
18
-
11
+
19
12
  def self.version
20
13
  STRING
21
14
  end
22
-
15
+
23
16
  end
24
17
  end
@@ -1,4 +1,6 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'net/imap' # for decode_utf7
2
4
 
3
5
  module Mail
4
6
  class Ruby18
@@ -54,19 +56,43 @@ module Mail
54
56
  klass.const_get( string )
55
57
  end
56
58
 
59
+ def Ruby18.transcode_charset(str, from_encoding, to_encoding = 'UTF-8')
60
+ case from_encoding
61
+ when /utf-?7/i
62
+ decode_utf7(str)
63
+ else
64
+ retried = false
65
+ begin
66
+ Iconv.conv("#{normalize_iconv_charset_encoding(to_encoding)}//IGNORE", normalize_iconv_charset_encoding(from_encoding), str)
67
+ rescue Iconv::InvalidEncoding
68
+ if retried
69
+ raise
70
+ else
71
+ from_encoding = 'ASCII'
72
+ retried = true
73
+ retry
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def Ruby18.decode_utf7(str)
80
+ Net::IMAP.decode_utf7(str)
81
+ end
82
+
57
83
  def Ruby18.b_value_encode(str, encoding)
58
84
  # Ruby 1.8 requires an encoding to work
59
85
  raise ArgumentError, "Must supply an encoding" if encoding.nil?
60
86
  encoding = encoding.to_s.upcase.gsub('_', '-')
61
- [Encodings::Base64.encode(str), fix_encoding(encoding)]
87
+ [Encodings::Base64.encode(str), normalize_iconv_charset_encoding(encoding)]
62
88
  end
63
89
 
64
90
  def Ruby18.b_value_decode(str)
65
- match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m)
91
+ match = str.match(/\=\?(.+)?\?[Bb]\?(.*)\?\=/m)
66
92
  if match
67
93
  encoding = match[1]
68
94
  str = Ruby18.decode_base64(match[2])
69
- str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str)
95
+ str = transcode_charset(str, encoding)
70
96
  end
71
97
  str
72
98
  end
@@ -79,20 +105,25 @@ module Mail
79
105
  end
80
106
 
81
107
  def Ruby18.q_value_decode(str)
82
- match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m)
108
+ match = str.match(/\=\?(.+)?\?[Qq]\?(.*)\?\=/m)
83
109
  if match
84
110
  encoding = match[1]
85
111
  string = match[2].gsub(/_/, '=20')
86
112
  # Remove trailing = if it exists in a Q encoding
87
113
  string = string.sub(/\=$/, '')
88
114
  str = Encodings::QuotedPrintable.decode(string)
89
- str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str)
115
+ str = transcode_charset(str, encoding)
90
116
  end
91
117
  str
92
118
  end
93
119
 
94
120
  def Ruby18.param_decode(str, encoding)
95
- URI.unescape(str)
121
+ str = URI.unescape(str)
122
+ if encoding
123
+ transcode_charset(str, encoding)
124
+ else
125
+ str
126
+ end
96
127
  end
97
128
 
98
129
  def Ruby18.param_encode(str)
@@ -101,9 +132,13 @@ module Mail
101
132
  "#{encoding}'#{language}'#{URI.escape(str)}"
102
133
  end
103
134
 
135
+ def Ruby18.string_byteslice(str, *args)
136
+ str.slice(*args)
137
+ end
138
+
104
139
  private
105
140
 
106
- def Ruby18.fix_encoding(encoding)
141
+ def Ruby18.normalize_iconv_charset_encoding(encoding)
107
142
  case encoding.upcase
108
143
  when 'UTF8', 'UTF_8'
109
144
  'UTF-8'
@@ -111,8 +146,17 @@ module Mail
111
146
  'UTF-16BE'
112
147
  when 'UTF32', 'UTF-32'
113
148
  'UTF-32BE'
149
+ when 'KS_C_5601-1987'
150
+ 'CP949'
114
151
  else
115
- encoding
152
+ # Fall back to ASCII for charsets that Iconv doesn't recognize
153
+ begin
154
+ Iconv.new('UTF-8', encoding)
155
+ rescue Iconv::InvalidEncoding => e
156
+ 'ASCII'
157
+ else
158
+ encoding
159
+ end
116
160
  end
117
161
  end
118
162
  end
@@ -1,7 +1,48 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Mail
4
5
  class Ruby19
6
+ class StrictCharsetEncoder
7
+ def encode(string, charset)
8
+ case charset
9
+ when /utf-?7/i
10
+ Mail::Ruby19.decode_utf7(string)
11
+ else
12
+ string.force_encoding(Mail::Ruby19.pick_encoding(charset))
13
+ end
14
+ end
15
+ end
16
+
17
+ class BestEffortCharsetEncoder
18
+ def encode(string, charset)
19
+ case charset
20
+ when /utf-?7/i
21
+ Mail::Ruby19.decode_utf7(string)
22
+ else
23
+ string.force_encoding(pick_encoding(charset))
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def pick_encoding(charset)
30
+ charset = case charset
31
+ when /ansi_x3.110-1983/
32
+ 'ISO-8859-1'
33
+ when /Windows-?1258/i # Windows-1258 is similar to 1252
34
+ "Windows-1252"
35
+ else
36
+ charset
37
+ end
38
+ Mail::Ruby19.pick_encoding(charset)
39
+ end
40
+ end
41
+
42
+ class << self
43
+ attr_accessor :charset_encoder
44
+ end
45
+ self.charset_encoder = BestEffortCharsetEncoder.new
5
46
 
6
47
  # Escapes any parenthesis in a string that are unescaped this uses
7
48
  # a Ruby 1.9.1 regexp feature of negative look behind
@@ -28,6 +69,9 @@ module Mail
28
69
  end
29
70
 
30
71
  def Ruby19.decode_base64(str)
72
+ if !str.end_with?("=") && str.length % 4 != 0
73
+ str = str.ljust((str.length + 3) & ~3, "=")
74
+ end
31
75
  str.unpack( 'm' ).first
32
76
  end
33
77
 
@@ -43,20 +87,51 @@ module Mail
43
87
  klass.const_get( string )
44
88
  end
45
89
 
90
+ def Ruby19.transcode_charset(str, from_encoding, to_encoding = Encoding::UTF_8)
91
+ to_encoding = to_encoding.to_s if RUBY_VERSION < '1.9.3'
92
+ to_encoding = Encoding.find(to_encoding)
93
+ replacement_char = to_encoding == Encoding::UTF_8 ? '�' : '?'
94
+ charset_encoder.encode(str.dup, from_encoding).encode(to_encoding, :undef => :replace, :invalid => :replace, :replace => replacement_char)
95
+ end
96
+
97
+ # From Ruby stdlib Net::IMAP
98
+ def Ruby19.encode_utf7(string)
99
+ string.gsub(/(&)|[^\x20-\x7e]+/) do
100
+ if $1
101
+ "&-"
102
+ else
103
+ base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
104
+ "&" + base64.delete("=").tr("/", ",") + "-"
105
+ end
106
+ end.force_encoding(Encoding::ASCII_8BIT)
107
+ end
108
+
109
+ def Ruby19.decode_utf7(utf7)
110
+ utf7.gsub(/&([^-]+)?-/n) do
111
+ if $1
112
+ ($1.tr(",", "/") + "===").unpack("m")[0].encode(Encoding::UTF_8, Encoding::UTF_16BE)
113
+ else
114
+ "&"
115
+ end
116
+ end
117
+ end
118
+
46
119
  def Ruby19.b_value_encode(str, encoding = nil)
47
120
  encoding = str.encoding.to_s
48
121
  [Ruby19.encode_base64(str), encoding]
49
122
  end
50
123
 
51
124
  def Ruby19.b_value_decode(str)
52
- match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m)
125
+ match = str.match(/\=\?(.+)?\?[Bb]\?(.*)\?\=/m)
53
126
  if match
54
127
  charset = match[1]
55
128
  str = Ruby19.decode_base64(match[2])
56
- str.force_encoding(pick_encoding(charset))
129
+ str = charset_encoder.encode(str, charset)
57
130
  end
58
- decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
59
- decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
131
+ transcode_to_scrubbed_utf8(str)
132
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
133
+ warn "Encoding conversion failed #{$!}"
134
+ str.dup.force_encoding(Encoding::UTF_8)
60
135
  end
61
136
 
62
137
  def Ruby19.q_value_encode(str, encoding = nil)
@@ -65,28 +140,31 @@ module Mail
65
140
  end
66
141
 
67
142
  def Ruby19.q_value_decode(str)
68
- match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m)
143
+ match = str.match(/\=\?(.+)?\?[Qq]\?(.*)\?\=/m)
69
144
  if match
70
145
  charset = match[1]
71
146
  string = match[2].gsub(/_/, '=20')
72
147
  # Remove trailing = if it exists in a Q encoding
73
148
  string = string.sub(/\=$/, '')
74
149
  str = Encodings::QuotedPrintable.decode(string)
75
- str.force_encoding(pick_encoding(charset))
150
+ str = charset_encoder.encode(str, charset)
76
151
  # We assume that binary strings hold utf-8 directly to work around
77
152
  # jruby/jruby#829 which subtly changes String#encode semantics.
78
- str.force_encoding('utf-8') if str.encoding == Encoding::ASCII_8BIT
153
+ str.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::ASCII_8BIT
79
154
  end
80
- decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
81
- decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
82
- rescue Encoding::UndefinedConversionError
83
- str.dup.force_encoding("utf-8")
155
+ transcode_to_scrubbed_utf8(str)
156
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
157
+ warn "Encoding conversion failed #{$!}"
158
+ str.dup.force_encoding(Encoding::UTF_8)
84
159
  end
85
160
 
86
161
  def Ruby19.param_decode(str, encoding)
87
- string = uri_parser.unescape(str)
88
- string.force_encoding(encoding) if encoding
89
- string
162
+ str = uri_parser.unescape(str)
163
+ str = charset_encoder.encode(str, encoding) if encoding
164
+ transcode_to_scrubbed_utf8(str)
165
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
166
+ warn "Encoding conversion failed #{$!}"
167
+ str.dup.force_encoding(Encoding::UTF_8)
90
168
  end
91
169
 
92
170
  def Ruby19.param_encode(str)
@@ -106,37 +184,38 @@ module Mail
106
184
  # Encoding.list.map { |e| [e.to_s.upcase == pick_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
107
185
  # Encoding.list.map { |e| [e.to_s == pick_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
108
186
  def Ruby19.pick_encoding(charset)
109
- case charset
187
+ charset = charset.to_s
188
+ encoding = case charset.downcase
110
189
 
111
190
  # ISO-8859-8-I etc. http://en.wikipedia.org/wiki/ISO-8859-8-I
112
- when /^iso-?8859-(\d+)(-i)?$/i
191
+ when /^iso[-_]?8859-(\d+)(-i)?$/
113
192
  "ISO-8859-#{$1}"
114
193
 
115
194
  # ISO-8859-15, ISO-2022-JP and alike
116
- when /iso-?(\d{4})-?(\w{1,2})/i
195
+ when /^iso[-_]?(\d{4})-?(\w{1,2})$/
117
196
  "ISO-#{$1}-#{$2}"
118
197
 
119
198
  # "ISO-2022-JP-KDDI" and alike
120
- when /iso-?(\d{4})-?(\w{1,2})-?(\w*)/i
199
+ when /^iso[-_]?(\d{4})-?(\w{1,2})-?(\w*)$/
121
200
  "ISO-#{$1}-#{$2}-#{$3}"
122
201
 
123
202
  # UTF-8, UTF-32BE and alike
124
- when /utf[\-_]?(\d{1,2})?(\w{1,2})/i
203
+ when /^utf[\-_]?(\d{1,2})?(\w{1,2})$/
125
204
  "UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
126
205
 
127
206
  # Windows-1252 and alike
128
- when /Windows-?(.*)/i
207
+ when /^windows-?(.*)$/
129
208
  "Windows-#{$1}"
130
209
 
131
- when /^8bit$/
210
+ when '8bit'
132
211
  Encoding::ASCII_8BIT
133
212
 
134
213
  # alternatives/misspellings of us-ascii seen in the wild
135
- when /^iso-?646(-us)?$/i, /us=ascii/i
214
+ when /^iso[-_]?646(-us)?$/, 'us=ascii'
136
215
  Encoding::ASCII
137
216
 
138
217
  # Microsoft-specific alias for MACROMAN
139
- when /^macintosh$/i
218
+ when 'macintosh'
140
219
  Encoding::MACROMAN
141
220
 
142
221
  # Microsoft-specific alias for CP949 (Korean)
@@ -148,12 +227,52 @@ module Mail
148
227
  Encoding::Shift_JIS
149
228
 
150
229
  # GB2312 (Chinese charset) is a subset of GB18030 (its replacement)
151
- when /gb2312/i
230
+ when 'gb2312'
152
231
  Encoding::GB18030
153
232
 
233
+ when 'cp-850'
234
+ Encoding::CP850
235
+
236
+ when 'latin2'
237
+ Encoding::ISO_8859_2
238
+
154
239
  else
155
240
  charset
156
241
  end
242
+
243
+ convert_to_encoding(encoding)
244
+ end
245
+
246
+ if "string".respond_to?(:byteslice)
247
+ def Ruby19.string_byteslice(str, *args)
248
+ str.byteslice(*args)
249
+ end
250
+ else
251
+ def Ruby19.string_byteslice(str, *args)
252
+ str.unpack('C*').slice(*args).pack('C*').force_encoding(str.encoding)
253
+ end
254
+ end
255
+
256
+ class << self
257
+ private
258
+
259
+ def convert_to_encoding(encoding)
260
+ if encoding.is_a?(Encoding)
261
+ encoding
262
+ else
263
+ # Fall back to ASCII for charsets that Ruby doesn't recognize
264
+ begin
265
+ Encoding.find(encoding)
266
+ rescue ArgumentError
267
+ Encoding::BINARY
268
+ end
269
+ end
270
+ end
271
+
272
+ def transcode_to_scrubbed_utf8(str)
273
+ decoded = str.encode(Encoding::UTF_8, :undef => :replace, :invalid => :replace, :replace => "�")
274
+ decoded.valid_encoding? ? decoded : decoded.encode(Encoding::UTF_16LE, :invalid => :replace, :replace => "�").encode(Encoding::UTF_8)
275
+ end
157
276
  end
158
277
  end
159
278
  end
data/lib/mail.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail # :doc:
3
4
 
4
5
  require 'date'
@@ -6,7 +7,7 @@ module Mail # :doc:
6
7
 
7
8
  require 'uri'
8
9
  require 'net/smtp'
9
- require 'mime/types'
10
+ require 'mini_mime'
10
11
 
11
12
  if RUBY_VERSION <= '1.8.6'
12
13
  begin
@@ -26,22 +27,13 @@ module Mail # :doc:
26
27
 
27
28
  require 'mail/version'
28
29
 
29
- require 'mail/core_extensions/nil'
30
- require 'mail/core_extensions/object'
31
30
  require 'mail/core_extensions/string'
32
- require 'mail/core_extensions/smtp' if RUBY_VERSION < '1.9.3'
31
+ require 'mail/core_extensions/smtp'
33
32
  require 'mail/indifferent_hash'
34
33
 
35
- # Only load our multibyte extensions if AS is not already loaded
36
- if defined?(ActiveSupport)
37
- require 'active_support/inflector'
38
- else
39
- require 'mail/core_extensions/string/access'
40
- require 'mail/core_extensions/string/multibyte'
41
- require 'mail/multibyte'
42
- end
34
+ require 'mail/multibyte'
43
35
 
44
- require 'mail/patterns'
36
+ require 'mail/constants'
45
37
  require 'mail/utilities'
46
38
  require 'mail/configuration'
47
39
 
@@ -76,15 +68,17 @@ module Mail # :doc:
76
68
 
77
69
  require 'mail/envelope'
78
70
 
79
- require 'mail/parsers'
71
+ register_autoload :Parsers, "mail/parsers"
80
72
 
81
73
  # Autoload header field elements and transfer encodings.
82
74
  require 'mail/elements'
83
75
  require 'mail/encodings'
84
76
  require 'mail/encodings/base64'
85
77
  require 'mail/encodings/quoted_printable'
78
+ require 'mail/encodings/unix_to_unix'
86
79
 
87
80
  require 'mail/matchers/has_sent_mail'
81
+ require 'mail/matchers/attachment_matchers.rb'
88
82
 
89
83
  # Finally... require all the Mail.methods
90
84
  require 'mail/mail'