mail 2.6.6 → 2.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (180) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +134 -119
  4. data/lib/mail/attachments_list.rb +10 -9
  5. data/lib/mail/body.rb +73 -84
  6. data/lib/mail/check_delivery_params.rb +28 -21
  7. data/lib/mail/configuration.rb +2 -0
  8. data/lib/mail/constants.rb +27 -5
  9. data/lib/mail/elements/address.rb +53 -47
  10. data/lib/mail/elements/address_list.rb +11 -19
  11. data/lib/mail/elements/content_disposition_element.rb +9 -16
  12. data/lib/mail/elements/content_location_element.rb +6 -11
  13. data/lib/mail/elements/content_transfer_encoding_element.rb +6 -11
  14. data/lib/mail/elements/content_type_element.rb +16 -23
  15. data/lib/mail/elements/date_time_element.rb +7 -15
  16. data/lib/mail/elements/envelope_from_element.rb +22 -23
  17. data/lib/mail/elements/message_ids_element.rb +18 -13
  18. data/lib/mail/elements/mime_version_element.rb +7 -15
  19. data/lib/mail/elements/phrase_list.rb +12 -10
  20. data/lib/mail/elements/received_element.rb +27 -19
  21. data/lib/mail/encodings/7bit.rb +9 -14
  22. data/lib/mail/encodings/8bit.rb +2 -21
  23. data/lib/mail/encodings/base64.rb +11 -12
  24. data/lib/mail/encodings/binary.rb +3 -22
  25. data/lib/mail/encodings/identity.rb +24 -0
  26. data/lib/mail/encodings/quoted_printable.rb +6 -6
  27. data/lib/mail/encodings/transfer_encoding.rb +38 -29
  28. data/lib/mail/encodings/unix_to_unix.rb +3 -1
  29. data/lib/mail/encodings.rb +81 -54
  30. data/lib/mail/envelope.rb +11 -14
  31. data/lib/mail/field.rb +119 -98
  32. data/lib/mail/field_list.rb +60 -7
  33. data/lib/mail/fields/bcc_field.rb +34 -52
  34. data/lib/mail/fields/cc_field.rb +28 -49
  35. data/lib/mail/fields/comments_field.rb +27 -37
  36. data/lib/mail/fields/common_address_field.rb +170 -0
  37. data/lib/mail/fields/common_date_field.rb +58 -0
  38. data/lib/mail/fields/common_field.rb +77 -0
  39. data/lib/mail/fields/common_message_id_field.rb +42 -0
  40. data/lib/mail/fields/content_description_field.rb +7 -14
  41. data/lib/mail/fields/content_disposition_field.rb +13 -38
  42. data/lib/mail/fields/content_id_field.rb +24 -51
  43. data/lib/mail/fields/content_location_field.rb +11 -25
  44. data/lib/mail/fields/content_transfer_encoding_field.rb +31 -31
  45. data/lib/mail/fields/content_type_field.rb +50 -80
  46. data/lib/mail/fields/date_field.rb +23 -52
  47. data/lib/mail/fields/from_field.rb +28 -49
  48. data/lib/mail/fields/in_reply_to_field.rb +38 -49
  49. data/lib/mail/fields/keywords_field.rb +18 -31
  50. data/lib/mail/fields/message_id_field.rb +25 -71
  51. data/lib/mail/fields/mime_version_field.rb +19 -30
  52. data/lib/mail/fields/named_structured_field.rb +11 -0
  53. data/lib/mail/fields/named_unstructured_field.rb +11 -0
  54. data/lib/mail/fields/optional_field.rb +9 -7
  55. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +13 -11
  56. data/lib/mail/fields/received_field.rb +43 -57
  57. data/lib/mail/fields/references_field.rb +35 -49
  58. data/lib/mail/fields/reply_to_field.rb +28 -49
  59. data/lib/mail/fields/resent_bcc_field.rb +28 -49
  60. data/lib/mail/fields/resent_cc_field.rb +28 -49
  61. data/lib/mail/fields/resent_date_field.rb +5 -30
  62. data/lib/mail/fields/resent_from_field.rb +28 -49
  63. data/lib/mail/fields/resent_message_id_field.rb +5 -29
  64. data/lib/mail/fields/resent_sender_field.rb +27 -56
  65. data/lib/mail/fields/resent_to_field.rb +28 -49
  66. data/lib/mail/fields/return_path_field.rb +50 -54
  67. data/lib/mail/fields/sender_field.rb +34 -55
  68. data/lib/mail/fields/structured_field.rb +3 -30
  69. data/lib/mail/fields/subject_field.rb +9 -11
  70. data/lib/mail/fields/to_field.rb +28 -49
  71. data/lib/mail/fields/unstructured_field.rb +32 -47
  72. data/lib/mail/header.rb +71 -110
  73. data/lib/mail/mail.rb +2 -10
  74. data/lib/mail/matchers/attachment_matchers.rb +15 -0
  75. data/lib/mail/matchers/has_sent_mail.rb +21 -1
  76. data/lib/mail/message.rb +113 -117
  77. data/lib/mail/multibyte/chars.rb +21 -178
  78. data/lib/mail/multibyte/unicode.rb +10 -10
  79. data/lib/mail/multibyte/utils.rb +26 -43
  80. data/lib/mail/multibyte.rb +55 -16
  81. data/lib/mail/network/delivery_methods/exim.rb +5 -4
  82. data/lib/mail/network/delivery_methods/file_delivery.rb +11 -10
  83. data/lib/mail/network/delivery_methods/logger_delivery.rb +34 -0
  84. data/lib/mail/network/delivery_methods/sendmail.rb +62 -21
  85. data/lib/mail/network/delivery_methods/smtp.rb +75 -50
  86. data/lib/mail/network/delivery_methods/smtp_connection.rb +3 -4
  87. data/lib/mail/network/delivery_methods/test_mailer.rb +4 -2
  88. data/lib/mail/network/retriever_methods/base.rb +8 -8
  89. data/lib/mail/network/retriever_methods/imap.rb +20 -7
  90. data/lib/mail/network/retriever_methods/pop3.rb +5 -3
  91. data/lib/mail/network/retriever_methods/test_retriever.rb +2 -1
  92. data/lib/mail/network.rb +1 -0
  93. data/lib/mail/parser_tools.rb +15 -0
  94. data/lib/mail/parsers/address_lists_parser.rb +33225 -116
  95. data/lib/mail/parsers/address_lists_parser.rl +179 -0
  96. data/lib/mail/parsers/content_disposition_parser.rb +882 -49
  97. data/lib/mail/parsers/content_disposition_parser.rl +89 -0
  98. data/lib/mail/parsers/content_location_parser.rb +809 -23
  99. data/lib/mail/parsers/content_location_parser.rl +78 -0
  100. data/lib/mail/parsers/content_transfer_encoding_parser.rb +509 -21
  101. data/lib/mail/parsers/content_transfer_encoding_parser.rl +71 -0
  102. data/lib/mail/parsers/content_type_parser.rb +1037 -56
  103. data/lib/mail/parsers/content_type_parser.rl +90 -0
  104. data/lib/mail/parsers/date_time_parser.rb +877 -25
  105. data/lib/mail/parsers/date_time_parser.rl +69 -0
  106. data/lib/mail/parsers/envelope_from_parser.rb +3669 -40
  107. data/lib/mail/parsers/envelope_from_parser.rl +89 -0
  108. data/lib/mail/parsers/message_ids_parser.rb +5146 -25
  109. data/lib/mail/parsers/message_ids_parser.rl +93 -0
  110. data/lib/mail/parsers/mime_version_parser.rb +497 -26
  111. data/lib/mail/parsers/mime_version_parser.rl +68 -0
  112. data/lib/mail/parsers/phrase_lists_parser.rb +870 -22
  113. data/lib/mail/parsers/phrase_lists_parser.rl +90 -0
  114. data/lib/mail/parsers/received_parser.rb +8776 -43
  115. data/lib/mail/parsers/received_parser.rl +91 -0
  116. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  117. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  118. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  119. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  120. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  121. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  122. data/lib/mail/parsers/rfc5322.rl +74 -0
  123. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  124. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  125. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  126. data/lib/mail/parsers.rb +11 -25
  127. data/lib/mail/part.rb +6 -10
  128. data/lib/mail/parts_list.rb +62 -6
  129. data/lib/mail/smtp_envelope.rb +57 -0
  130. data/lib/mail/utilities.rb +343 -74
  131. data/lib/mail/version.rb +2 -2
  132. data/lib/mail/yaml.rb +30 -0
  133. data/lib/mail.rb +5 -35
  134. metadata +111 -66
  135. data/CHANGELOG.rdoc +0 -803
  136. data/CONTRIBUTING.md +0 -60
  137. data/Dependencies.txt +0 -2
  138. data/Gemfile +0 -14
  139. data/Rakefile +0 -29
  140. data/TODO.rdoc +0 -9
  141. data/lib/mail/core_extensions/smtp.rb +0 -25
  142. data/lib/mail/core_extensions/string/access.rb +0 -146
  143. data/lib/mail/core_extensions/string/multibyte.rb +0 -79
  144. data/lib/mail/core_extensions/string.rb +0 -21
  145. data/lib/mail/fields/common/address_container.rb +0 -17
  146. data/lib/mail/fields/common/common_address.rb +0 -136
  147. data/lib/mail/fields/common/common_date.rb +0 -36
  148. data/lib/mail/fields/common/common_field.rb +0 -61
  149. data/lib/mail/fields/common/common_message_id.rb +0 -49
  150. data/lib/mail/multibyte/exceptions.rb +0 -9
  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 -18
  179. data/lib/mail/version_specific/ruby_1_8.rb +0 -126
  180. data/lib/mail/version_specific/ruby_1_9.rb +0 -226
@@ -1,16 +1,15 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+ require 'mail/constants'
4
+ require 'socket'
5
+
3
6
  module Mail
4
7
  module Utilities
5
-
6
- LF = "\n"
7
- CRLF = "\r\n"
8
-
9
- include Constants
8
+ extend self
10
9
 
11
10
  # Returns true if the string supplied is free from characters not allowed as an ATOM
12
11
  def atom_safe?( str )
13
- not ATOM_UNSAFE === str
12
+ not Constants::ATOM_UNSAFE === str
14
13
  end
15
14
 
16
15
  # If the string supplied has ATOM unsafe characters in it, will return the string quoted
@@ -22,28 +21,38 @@ module Mail
22
21
  # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
23
22
  # in double quotes, otherwise returns the string unmodified
24
23
  def quote_phrase( str )
25
- if RUBY_VERSION >= '1.9'
24
+ if str.respond_to?(:force_encoding)
26
25
  original_encoding = str.encoding
27
- ascii_str = str.dup.force_encoding('ASCII-8BIT')
28
- if (PHRASE_UNSAFE === ascii_str)
26
+ ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
27
+ if Constants::PHRASE_UNSAFE === ascii_str
29
28
  dquote(ascii_str).force_encoding(original_encoding)
30
29
  else
31
30
  str
32
31
  end
33
32
  else
34
- (PHRASE_UNSAFE === str) ? dquote(str) : str
33
+ Constants::PHRASE_UNSAFE === str ? dquote(str) : str
35
34
  end
36
35
  end
37
36
 
38
37
  # Returns true if the string supplied is free from characters not allowed as a TOKEN
39
38
  def token_safe?( str )
40
- not TOKEN_UNSAFE === str
39
+ not Constants::TOKEN_UNSAFE === str
41
40
  end
42
41
 
43
42
  # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
44
43
  # in double quotes, otherwise returns the string unmodified
45
44
  def quote_token( str )
46
- 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
47
56
  end
48
57
 
49
58
  # Wraps supplied string in double quotes and applies \-escaping as necessary,
@@ -90,7 +99,6 @@ module Mail
90
99
  def unescape( str )
91
100
  str.gsub(/\\(.)/, '\1')
92
101
  end
93
- module_function :unescape
94
102
 
95
103
  # Wraps a string in parenthesis and escapes any that are in the string itself.
96
104
  #
@@ -98,7 +106,7 @@ module Mail
98
106
  #
99
107
  # paren( 'This is a string' ) #=> '(This is a string)'
100
108
  def paren( str )
101
- RubyVer.paren( str )
109
+ Utilities.paren( str )
102
110
  end
103
111
 
104
112
  # Unwraps a string from being wrapped in parenthesis
@@ -108,8 +116,11 @@ module Mail
108
116
  # str = '(This is a string)'
109
117
  # unparen( str ) #=> 'This is a string'
110
118
  def unparen( str )
111
- match = str.match(/^\((.*?)\)$/)
112
- match ? match[1] : str
119
+ if str.start_with?('(') && str.end_with?(')')
120
+ str.slice(1..-2)
121
+ else
122
+ str
123
+ end
113
124
  end
114
125
 
115
126
  # Wraps a string in angle brackets and escapes any that are in the string itself
@@ -118,7 +129,7 @@ module Mail
118
129
  #
119
130
  # bracket( 'This is a string' ) #=> '<This is a string>'
120
131
  def bracket( str )
121
- RubyVer.bracket( str )
132
+ Utilities.bracket( str )
122
133
  end
123
134
 
124
135
  # Unwraps a string from being wrapped in parenthesis
@@ -128,8 +139,11 @@ module Mail
128
139
  # str = '<This is a string>'
129
140
  # unbracket( str ) #=> 'This is a string'
130
141
  def unbracket( str )
131
- match = str.match(/^\<(.*?)\>$/)
132
- match ? match[1] : str
142
+ if str.start_with?('<') && str.end_with?('>')
143
+ str.slice(1..-2)
144
+ else
145
+ str
146
+ end
133
147
  end
134
148
 
135
149
  # Escape parenthesies in a string
@@ -139,7 +153,7 @@ module Mail
139
153
  # str = 'This is (a) string'
140
154
  # escape_paren( str ) #=> 'This is \(a\) string'
141
155
  def escape_paren( str )
142
- RubyVer.escape_paren( str )
156
+ Utilities.escape_paren( str )
143
157
  end
144
158
 
145
159
  def uri_escape( str )
@@ -151,7 +165,7 @@ module Mail
151
165
  end
152
166
 
153
167
  def uri_parser
154
- @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
168
+ @uri_parser ||= URI.const_defined?(:DEFAULT_PARSER) ? URI::DEFAULT_PARSER : URI
155
169
  end
156
170
 
157
171
  # Matches two objects with their to_s values case insensitively
@@ -192,9 +206,9 @@ module Mail
192
206
  # Example:
193
207
  #
194
208
  # string = :resent_from_field
195
- # dasherize ( string ) #=> 'resent_from_field'
209
+ # dasherize( string ) #=> 'resent-from-field'
196
210
  def dasherize( str )
197
- str.to_s.tr(UNDERSCORE, HYPHEN)
211
+ str.to_s.tr(Constants::UNDERSCORE, Constants::HYPHEN)
198
212
  end
199
213
 
200
214
  # Swaps out all hyphens (-) for underscores (_) good for stringing to symbols
@@ -205,89 +219,344 @@ module Mail
205
219
  # string = :resent_from_field
206
220
  # underscoreize ( string ) #=> 'resent_from_field'
207
221
  def underscoreize( str )
208
- str.to_s.downcase.tr(HYPHEN, UNDERSCORE)
222
+ str.to_s.downcase.tr(Constants::HYPHEN, Constants::UNDERSCORE)
223
+ end
224
+
225
+ def map_lines( str, &block )
226
+ str.each_line.map(&block)
227
+ end
228
+
229
+ def map_with_index( enum, &block )
230
+ enum.each_with_index.map(&block)
231
+ end
232
+
233
+ def self.binary_unsafe_to_lf(string) #:nodoc:
234
+ string.gsub(/\r\n|\r/, Constants::LF)
235
+ end
236
+
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)")
242
+
243
+ def self.binary_unsafe_to_crlf(string) #:nodoc:
244
+ string.gsub(TO_CRLF_REGEX, Constants::CRLF)
245
+ end
246
+
247
+ def self.safe_for_line_ending_conversion?(string) #:nodoc:
248
+ if string.encoding == Encoding::BINARY
249
+ string.ascii_only?
250
+ else
251
+ string.valid_encoding?
252
+ end
253
+ end
254
+
255
+ # Convert line endings to \n unless the string is binary. Used for
256
+ # sendmail delivery and for decoding 8bit Content-Transfer-Encoding.
257
+ def self.to_lf(string)
258
+ string = string.to_s
259
+ if safe_for_line_ending_conversion? string
260
+ binary_unsafe_to_lf string
261
+ else
262
+ string
263
+ end
209
264
  end
210
265
 
211
- if RUBY_VERSION <= '1.8.6'
266
+ # Convert line endings to \r\n unless the string is binary. Used for
267
+ # encoding 8bit and base64 Content-Transfer-Encoding and for convenience
268
+ # when parsing emails with \n line endings instead of the required \r\n.
269
+ def self.to_crlf(string)
270
+ string = string.to_s
271
+ if safe_for_line_ending_conversion? string
272
+ binary_unsafe_to_crlf string
273
+ else
274
+ string
275
+ end
276
+ end
212
277
 
213
- def map_lines( str, &block )
214
- results = []
215
- str.each_line do |line|
216
- results << yield(line)
278
+ # Returns true if the object is considered blank.
279
+ # A blank includes things like '', ' ', nil,
280
+ # and arrays and hashes that have nothing in them.
281
+ #
282
+ # This logic is mostly shared with ActiveSupport's blank?
283
+ def blank?(value)
284
+ if value.kind_of?(NilClass)
285
+ true
286
+ elsif value.kind_of?(String)
287
+ value !~ /\S/
288
+ else
289
+ value.respond_to?(:empty?) ? value.empty? : !value
290
+ end
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))
217
304
  end
218
- results
219
305
  end
306
+ end
220
307
 
221
- def map_with_index( enum, &block )
222
- results = []
223
- enum.each_with_index do |token, i|
224
- results[i] = yield(token, i)
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))
225
315
  end
226
- results
227
316
  end
228
317
 
229
- else
318
+ private
230
319
 
231
- def map_lines( str, &block )
232
- str.each_line.map(&block)
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)
233
330
  end
331
+ end
234
332
 
235
- def map_with_index( enum, &block )
236
- enum.each_with_index.map(&block)
237
- end
333
+ class << self
334
+ attr_accessor :charset_encoder
335
+ end
336
+ self.charset_encoder = BestEffortCharsetEncoder.new
238
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 }
239
343
  end
240
344
 
241
- # Test String#encode works correctly with line endings.
242
- # Some versions of Ruby (e.g. MRI <1.9, JRuby, Rubinius) line ending
243
- # normalization does not work correctly or did not have #encode.
244
- if ("\r".encode(:universal_newline => true) rescue nil) == LF &&
245
- (LF.encode(:crlf_newline => true) rescue nil) == CRLF
246
- # Using String#encode is better performing than Regexp
345
+ def Utilities.paren( str )
346
+ str = ::Mail::Utilities.unparen( str )
347
+ str = escape_paren( str )
348
+ '(' + str + ')'
349
+ end
247
350
 
248
- def self.to_lf(input)
249
- input.kind_of?(String) ? input.to_str.encode(input.encoding, :universal_newline => true) : ''
250
- end
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
251
361
 
252
- def self.to_crlf(input)
253
- input.kind_of?(String) ? input.to_str.encode(input.encoding, :universal_newline => true).encode!(input.encoding, :crlf_newline => true) : ''
362
+ def Utilities.decode_base64(str)
363
+ if !str.end_with?("=") && str.length % 4 != 0
364
+ str = str.ljust((str.length + 3) & ~3, "=")
254
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
255
376
 
256
- else
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
257
398
 
258
- def self.to_lf(input)
259
- input.kind_of?(String) ? input.to_str.gsub(/\r\n|\r/, LF) : ''
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
260
406
  end
407
+ end
261
408
 
262
- if RUBY_VERSION >= '1.9'
263
- # This 1.9 only regex can save a reasonable amount of time (~20%)
264
- # by not matching "\r\n" so the string is returned unchanged in
265
- # the common case.
266
- CRLF_REGEX = Regexp.new("(?<!\r)\n|\r(?!\n)")
267
- else
268
- CRLF_REGEX = /\n|\r\n|\r/
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)
269
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
270
431
 
271
- def self.to_crlf(input)
272
- input.kind_of?(String) ? input.to_str.gsub(CRLF_REGEX, CRLF) : ''
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
273
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
274
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)
275
458
  end
276
459
 
277
- # Returns true if the object is considered blank.
278
- # A blank includes things like '', ' ', nil,
279
- # and arrays and hashes that have nothing in them.
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.
280
472
  #
281
- # This logic is mostly shared with ActiveSupport's blank?
282
- def self.blank?(value)
283
- if value.kind_of?(NilClass)
284
- true
285
- elsif value.kind_of?(String)
286
- value !~ /\S/
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
+
287
529
  else
288
- value.respond_to?(:empty?) ? value.empty? : !value
530
+ charset
289
531
  end
532
+
533
+ convert_to_encoding(encoding)
534
+ end
535
+
536
+ def Utilities.string_byteslice(str, *args)
537
+ str.byteslice(*args)
290
538
  end
291
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
292
561
  end
293
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 = 6
7
- PATCH = 6
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
@@ -7,44 +7,13 @@ module Mail # :doc:
7
7
 
8
8
  require 'uri'
9
9
  require 'net/smtp'
10
-
11
- begin
12
- # Use mime/types/columnar if available, for reduced memory usage
13
- require 'mime/types/columnar'
14
- rescue LoadError
15
- require 'mime/types'
16
- end
17
-
18
- if RUBY_VERSION <= '1.8.6'
19
- begin
20
- require 'tlsmail'
21
- rescue LoadError
22
- raise "You need to install tlsmail if you are using ruby <= 1.8.6"
23
- end
24
- end
25
-
26
- if RUBY_VERSION >= "1.9.0"
27
- require 'mail/version_specific/ruby_1_9'
28
- RubyVer = Ruby19
29
- else
30
- require 'mail/version_specific/ruby_1_8'
31
- RubyVer = Ruby18
32
- end
10
+ require 'mini_mime'
33
11
 
34
12
  require 'mail/version'
35
13
 
36
- require 'mail/core_extensions/string'
37
- require 'mail/core_extensions/smtp' if RUBY_VERSION < '1.9.3'
38
14
  require 'mail/indifferent_hash'
39
15
 
40
- # Only load our multibyte extensions if AS is not already loaded
41
- if defined?(ActiveSupport)
42
- require 'active_support/inflector'
43
- else
44
- require 'mail/core_extensions/string/access'
45
- require 'mail/core_extensions/string/multibyte'
46
- require 'mail/multibyte'
47
- end
16
+ require 'mail/multibyte'
48
17
 
49
18
  require 'mail/constants'
50
19
  require 'mail/utilities'
@@ -81,8 +50,6 @@ module Mail # :doc:
81
50
 
82
51
  require 'mail/envelope'
83
52
 
84
- register_autoload :Parsers, "mail/parsers"
85
-
86
53
  # Autoload header field elements and transfer encodings.
87
54
  require 'mail/elements'
88
55
  require 'mail/encodings'
@@ -93,6 +60,9 @@ module Mail # :doc:
93
60
  require 'mail/matchers/has_sent_mail'
94
61
  require 'mail/matchers/attachment_matchers.rb'
95
62
 
63
+ # Deprecated will be removed in 3.0 release
64
+ require 'mail/check_delivery_params'
65
+
96
66
  # Finally... require all the Mail.methods
97
67
  require 'mail/mail'
98
68
  end