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,6 +1,13 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/constants'
4
+
2
5
  module Mail
3
6
  module Utilities
7
+
8
+ LF = "\n"
9
+ CRLF = "\r\n"
10
+
4
11
  include Constants
5
12
 
6
13
  # Returns true if the string supplied is free from characters not allowed as an ATOM
@@ -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,7 +205,7 @@ 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
210
  str.to_s.tr(UNDERSCORE, HYPHEN)
181
211
  end
@@ -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,9 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  module Mail
2
3
  module VERSION
3
4
 
4
5
  MAJOR = 2
5
- MINOR = 6
6
- PATCH = 3
6
+ MINOR = 7
7
+ PATCH = 1
7
8
  BUILD = nil
8
9
 
9
10
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
@@ -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,11 +56,35 @@ 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)
@@ -66,7 +92,7 @@ module Mail
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
@@ -86,13 +112,18 @@ module Mail
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,16 +1,27 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Mail
4
5
  class Ruby19
5
6
  class StrictCharsetEncoder
6
7
  def encode(string, charset)
7
- string.force_encoding(Mail::Ruby19.pick_encoding(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
8
14
  end
9
15
  end
10
16
 
11
17
  class BestEffortCharsetEncoder
12
18
  def encode(string, charset)
13
- string.force_encoding(pick_encoding(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
14
25
  end
15
26
 
16
27
  private
@@ -31,7 +42,7 @@ module Mail
31
42
  class << self
32
43
  attr_accessor :charset_encoder
33
44
  end
34
- self.charset_encoder = StrictCharsetEncoder.new
45
+ self.charset_encoder = BestEffortCharsetEncoder.new
35
46
 
36
47
  # Escapes any parenthesis in a string that are unescaped this uses
37
48
  # a Ruby 1.9.1 regexp feature of negative look behind
@@ -58,6 +69,9 @@ module Mail
58
69
  end
59
70
 
60
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
61
75
  str.unpack( 'm' ).first
62
76
  end
63
77
 
@@ -73,6 +87,35 @@ module Mail
73
87
  klass.const_get( string )
74
88
  end
75
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
+
76
119
  def Ruby19.b_value_encode(str, encoding = nil)
77
120
  encoding = str.encoding.to_s
78
121
  [Ruby19.encode_base64(str), encoding]
@@ -85,8 +128,7 @@ module Mail
85
128
  str = Ruby19.decode_base64(match[2])
86
129
  str = charset_encoder.encode(str, charset)
87
130
  end
88
- decoded = str.encode(Encoding::UTF_8, :invalid => :replace, :replace => "")
89
- decoded.valid_encoding? ? decoded : decoded.encode(Encoding::UTF_16LE, :invalid => :replace, :replace => "").encode(Encoding::UTF_8)
131
+ transcode_to_scrubbed_utf8(str)
90
132
  rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
91
133
  warn "Encoding conversion failed #{$!}"
92
134
  str.dup.force_encoding(Encoding::UTF_8)
@@ -110,8 +152,7 @@ module Mail
110
152
  # jruby/jruby#829 which subtly changes String#encode semantics.
111
153
  str.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::ASCII_8BIT
112
154
  end
113
- decoded = str.encode(Encoding::UTF_8, :invalid => :replace, :replace => "")
114
- decoded.valid_encoding? ? decoded : decoded.encode(Encoding::UTF_16LE, :invalid => :replace, :replace => "").encode(Encoding::UTF_8)
155
+ transcode_to_scrubbed_utf8(str)
115
156
  rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
116
157
  warn "Encoding conversion failed #{$!}"
117
158
  str.dup.force_encoding(Encoding::UTF_8)
@@ -120,7 +161,10 @@ module Mail
120
161
  def Ruby19.param_decode(str, encoding)
121
162
  str = uri_parser.unescape(str)
122
163
  str = charset_encoder.encode(str, encoding) if encoding
123
- str
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)
124
168
  end
125
169
 
126
170
  def Ruby19.param_encode(str)
@@ -140,37 +184,38 @@ module Mail
140
184
  # Encoding.list.map { |e| [e.to_s.upcase == pick_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
141
185
  # Encoding.list.map { |e| [e.to_s == pick_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
142
186
  def Ruby19.pick_encoding(charset)
143
- case charset
187
+ charset = charset.to_s
188
+ encoding = case charset.downcase
144
189
 
145
190
  # ISO-8859-8-I etc. http://en.wikipedia.org/wiki/ISO-8859-8-I
146
- when /^iso-?8859-(\d+)(-i)?$/i
191
+ when /^iso[-_]?8859-(\d+)(-i)?$/
147
192
  "ISO-8859-#{$1}"
148
193
 
149
194
  # ISO-8859-15, ISO-2022-JP and alike
150
- when /iso-?(\d{4})-?(\w{1,2})/i
195
+ when /^iso[-_]?(\d{4})-?(\w{1,2})$/
151
196
  "ISO-#{$1}-#{$2}"
152
197
 
153
198
  # "ISO-2022-JP-KDDI" and alike
154
- when /iso-?(\d{4})-?(\w{1,2})-?(\w*)/i
199
+ when /^iso[-_]?(\d{4})-?(\w{1,2})-?(\w*)$/
155
200
  "ISO-#{$1}-#{$2}-#{$3}"
156
201
 
157
202
  # UTF-8, UTF-32BE and alike
158
- when /utf[\-_]?(\d{1,2})?(\w{1,2})/i
203
+ when /^utf[\-_]?(\d{1,2})?(\w{1,2})$/
159
204
  "UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
160
205
 
161
206
  # Windows-1252 and alike
162
- when /Windows-?(.*)/i
207
+ when /^windows-?(.*)$/
163
208
  "Windows-#{$1}"
164
209
 
165
- when /^8bit$/
210
+ when '8bit'
166
211
  Encoding::ASCII_8BIT
167
212
 
168
213
  # alternatives/misspellings of us-ascii seen in the wild
169
- when /^iso-?646(-us)?$/i, /us=ascii/i
214
+ when /^iso[-_]?646(-us)?$/, 'us=ascii'
170
215
  Encoding::ASCII
171
216
 
172
217
  # Microsoft-specific alias for MACROMAN
173
- when /^macintosh$/i
218
+ when 'macintosh'
174
219
  Encoding::MACROMAN
175
220
 
176
221
  # Microsoft-specific alias for CP949 (Korean)
@@ -182,12 +227,52 @@ module Mail
182
227
  Encoding::Shift_JIS
183
228
 
184
229
  # GB2312 (Chinese charset) is a subset of GB18030 (its replacement)
185
- when /gb2312/i
230
+ when 'gb2312'
186
231
  Encoding::GB18030
187
232
 
233
+ when 'cp-850'
234
+ Encoding::CP850
235
+
236
+ when 'latin2'
237
+ Encoding::ISO_8859_2
238
+
188
239
  else
189
240
  charset
190
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
191
276
  end
192
277
  end
193
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,20 +27,11 @@ 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
36
  require 'mail/constants'
45
37
  require 'mail/utilities'
@@ -86,6 +78,7 @@ module Mail # :doc:
86
78
  require 'mail/encodings/unix_to_unix'
87
79
 
88
80
  require 'mail/matchers/has_sent_mail'
81
+ require 'mail/matchers/attachment_matchers.rb'
89
82
 
90
83
  # Finally... require all the Mail.methods
91
84
  require 'mail/mail'