mail 2.6.1 → 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
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'