mail 2.6.4 → 2.8.0

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/README.md +111 -118
  3. data/lib/mail/attachments_list.rb +11 -10
  4. data/lib/mail/body.rb +73 -84
  5. data/lib/mail/check_delivery_params.rb +54 -10
  6. data/lib/mail/configuration.rb +2 -0
  7. data/lib/mail/constants.rb +27 -5
  8. data/lib/mail/elements/address.rb +61 -50
  9. data/lib/mail/elements/address_list.rb +11 -19
  10. data/lib/mail/elements/content_disposition_element.rb +9 -16
  11. data/lib/mail/elements/content_location_element.rb +6 -11
  12. data/lib/mail/elements/content_transfer_encoding_element.rb +6 -11
  13. data/lib/mail/elements/content_type_element.rb +16 -23
  14. data/lib/mail/elements/date_time_element.rb +7 -15
  15. data/lib/mail/elements/envelope_from_element.rb +22 -23
  16. data/lib/mail/elements/message_ids_element.rb +18 -13
  17. data/lib/mail/elements/mime_version_element.rb +7 -15
  18. data/lib/mail/elements/phrase_list.rb +12 -10
  19. data/lib/mail/elements/received_element.rb +27 -19
  20. data/lib/mail/encodings/7bit.rb +9 -14
  21. data/lib/mail/encodings/8bit.rb +2 -21
  22. data/lib/mail/encodings/base64.rb +11 -12
  23. data/lib/mail/encodings/binary.rb +3 -22
  24. data/lib/mail/encodings/identity.rb +24 -0
  25. data/lib/mail/encodings/quoted_printable.rb +6 -6
  26. data/lib/mail/encodings/transfer_encoding.rb +38 -29
  27. data/lib/mail/encodings/unix_to_unix.rb +3 -1
  28. data/lib/mail/encodings.rb +81 -54
  29. data/lib/mail/envelope.rb +11 -14
  30. data/lib/mail/field.rb +119 -98
  31. data/lib/mail/field_list.rb +60 -7
  32. data/lib/mail/fields/bcc_field.rb +34 -52
  33. data/lib/mail/fields/cc_field.rb +28 -49
  34. data/lib/mail/fields/comments_field.rb +27 -37
  35. data/lib/mail/fields/common_address_field.rb +170 -0
  36. data/lib/mail/fields/common_date_field.rb +58 -0
  37. data/lib/mail/fields/common_field.rb +77 -0
  38. data/lib/mail/fields/common_message_id_field.rb +42 -0
  39. data/lib/mail/fields/content_description_field.rb +7 -14
  40. data/lib/mail/fields/content_disposition_field.rb +13 -38
  41. data/lib/mail/fields/content_id_field.rb +24 -51
  42. data/lib/mail/fields/content_location_field.rb +11 -25
  43. data/lib/mail/fields/content_transfer_encoding_field.rb +31 -31
  44. data/lib/mail/fields/content_type_field.rb +50 -80
  45. data/lib/mail/fields/date_field.rb +23 -52
  46. data/lib/mail/fields/from_field.rb +28 -49
  47. data/lib/mail/fields/in_reply_to_field.rb +38 -49
  48. data/lib/mail/fields/keywords_field.rb +18 -31
  49. data/lib/mail/fields/message_id_field.rb +25 -71
  50. data/lib/mail/fields/mime_version_field.rb +19 -30
  51. data/lib/mail/fields/named_structured_field.rb +11 -0
  52. data/lib/mail/fields/named_unstructured_field.rb +11 -0
  53. data/lib/mail/fields/optional_field.rb +9 -7
  54. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +13 -11
  55. data/lib/mail/fields/received_field.rb +43 -57
  56. data/lib/mail/fields/references_field.rb +35 -49
  57. data/lib/mail/fields/reply_to_field.rb +28 -49
  58. data/lib/mail/fields/resent_bcc_field.rb +28 -49
  59. data/lib/mail/fields/resent_cc_field.rb +28 -49
  60. data/lib/mail/fields/resent_date_field.rb +5 -30
  61. data/lib/mail/fields/resent_from_field.rb +28 -49
  62. data/lib/mail/fields/resent_message_id_field.rb +5 -29
  63. data/lib/mail/fields/resent_sender_field.rb +27 -56
  64. data/lib/mail/fields/resent_to_field.rb +28 -49
  65. data/lib/mail/fields/return_path_field.rb +50 -54
  66. data/lib/mail/fields/sender_field.rb +34 -55
  67. data/lib/mail/fields/structured_field.rb +3 -30
  68. data/lib/mail/fields/subject_field.rb +9 -11
  69. data/lib/mail/fields/to_field.rb +28 -49
  70. data/lib/mail/fields/unstructured_field.rb +32 -47
  71. data/lib/mail/header.rb +71 -110
  72. data/lib/mail/mail.rb +2 -10
  73. data/lib/mail/matchers/attachment_matchers.rb +15 -0
  74. data/lib/mail/matchers/has_sent_mail.rb +21 -1
  75. data/lib/mail/message.rb +113 -117
  76. data/lib/mail/multibyte/chars.rb +23 -180
  77. data/lib/mail/multibyte/unicode.rb +10 -10
  78. data/lib/mail/multibyte/utils.rb +26 -43
  79. data/lib/mail/multibyte.rb +55 -16
  80. data/lib/mail/network/delivery_methods/exim.rb +8 -11
  81. data/lib/mail/network/delivery_methods/file_delivery.rb +13 -16
  82. data/lib/mail/network/delivery_methods/logger_delivery.rb +34 -0
  83. data/lib/mail/network/delivery_methods/sendmail.rb +32 -35
  84. data/lib/mail/network/delivery_methods/smtp.rb +76 -54
  85. data/lib/mail/network/delivery_methods/smtp_connection.rb +4 -9
  86. data/lib/mail/network/delivery_methods/test_mailer.rb +8 -9
  87. data/lib/mail/network/retriever_methods/base.rb +8 -8
  88. data/lib/mail/network/retriever_methods/imap.rb +20 -7
  89. data/lib/mail/network/retriever_methods/pop3.rb +5 -3
  90. data/lib/mail/network/retriever_methods/test_retriever.rb +3 -2
  91. data/lib/mail/network.rb +1 -0
  92. data/lib/mail/parser_tools.rb +15 -0
  93. data/lib/mail/parsers/address_lists_parser.rb +33225 -116
  94. data/lib/mail/parsers/address_lists_parser.rl +179 -0
  95. data/lib/mail/parsers/content_disposition_parser.rb +882 -49
  96. data/lib/mail/parsers/content_disposition_parser.rl +89 -0
  97. data/lib/mail/parsers/content_location_parser.rb +809 -23
  98. data/lib/mail/parsers/content_location_parser.rl +78 -0
  99. data/lib/mail/parsers/content_transfer_encoding_parser.rb +509 -21
  100. data/lib/mail/parsers/content_transfer_encoding_parser.rl +71 -0
  101. data/lib/mail/parsers/content_type_parser.rb +1037 -56
  102. data/lib/mail/parsers/content_type_parser.rl +90 -0
  103. data/lib/mail/parsers/date_time_parser.rb +877 -25
  104. data/lib/mail/parsers/date_time_parser.rl +69 -0
  105. data/lib/mail/parsers/envelope_from_parser.rb +3669 -40
  106. data/lib/mail/parsers/envelope_from_parser.rl +89 -0
  107. data/lib/mail/parsers/message_ids_parser.rb +5146 -25
  108. data/lib/mail/parsers/message_ids_parser.rl +93 -0
  109. data/lib/mail/parsers/mime_version_parser.rb +497 -26
  110. data/lib/mail/parsers/mime_version_parser.rl +68 -0
  111. data/lib/mail/parsers/phrase_lists_parser.rb +870 -22
  112. data/lib/mail/parsers/phrase_lists_parser.rl +90 -0
  113. data/lib/mail/parsers/received_parser.rb +8776 -43
  114. data/lib/mail/parsers/received_parser.rl +91 -0
  115. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  116. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  117. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  118. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  119. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  120. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  121. data/lib/mail/parsers/rfc5322.rl +74 -0
  122. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  123. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  124. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  125. data/lib/mail/parsers.rb +11 -25
  126. data/lib/mail/part.rb +6 -10
  127. data/lib/mail/parts_list.rb +62 -6
  128. data/lib/mail/smtp_envelope.rb +57 -0
  129. data/lib/mail/utilities.rb +357 -74
  130. data/lib/mail/version.rb +2 -2
  131. data/lib/mail/yaml.rb +30 -0
  132. data/lib/mail.rb +5 -35
  133. metadata +111 -66
  134. data/CHANGELOG.rdoc +0 -787
  135. data/CONTRIBUTING.md +0 -60
  136. data/Dependencies.txt +0 -2
  137. data/Gemfile +0 -11
  138. data/Rakefile +0 -29
  139. data/TODO.rdoc +0 -9
  140. data/lib/mail/core_extensions/smtp.rb +0 -25
  141. data/lib/mail/core_extensions/string/access.rb +0 -146
  142. data/lib/mail/core_extensions/string/multibyte.rb +0 -79
  143. data/lib/mail/core_extensions/string.rb +0 -21
  144. data/lib/mail/fields/common/address_container.rb +0 -17
  145. data/lib/mail/fields/common/common_address.rb +0 -136
  146. data/lib/mail/fields/common/common_date.rb +0 -36
  147. data/lib/mail/fields/common/common_field.rb +0 -61
  148. data/lib/mail/fields/common/common_message_id.rb +0 -49
  149. data/lib/mail/multibyte/exceptions.rb +0 -9
  150. data/lib/mail/parsers/ragel/common.rl +0 -185
  151. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  152. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  153. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  154. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  155. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  156. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  157. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  158. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  159. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  160. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  161. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  162. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  163. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  164. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2149
  165. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  166. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  167. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  168. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  169. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  170. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  171. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  172. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  173. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  174. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  175. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  176. data/lib/mail/parsers/ragel/ruby.rb +0 -40
  177. data/lib/mail/parsers/ragel.rb +0 -18
  178. data/lib/mail/version_specific/ruby_1_8.rb +0 -126
  179. data/lib/mail/version_specific/ruby_1_9.rb +0 -223
@@ -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,
@@ -72,19 +81,32 @@ module Mail
72
81
  # unqoute(string) #=> 'This is "a string"'
73
82
  def unquote( str )
74
83
  if str =~ /^"(.*?)"$/
75
- $1.gsub(/\\(.)/, '\1')
84
+ unescape($1)
76
85
  else
77
86
  str
78
87
  end
79
88
  end
80
89
 
90
+ # Removes any \-escaping.
91
+ #
92
+ # Example:
93
+ #
94
+ # string = 'This is \"a string\"'
95
+ # unescape(string) #=> 'This is "a string"'
96
+ #
97
+ # string = '"This is \"a string\""'
98
+ # unescape(string) #=> '"This is "a string""'
99
+ def unescape( str )
100
+ str.gsub(/\\(.)/, '\1')
101
+ end
102
+
81
103
  # Wraps a string in parenthesis and escapes any that are in the string itself.
82
104
  #
83
105
  # Example:
84
106
  #
85
107
  # paren( 'This is a string' ) #=> '(This is a string)'
86
108
  def paren( str )
87
- RubyVer.paren( str )
109
+ Utilities.paren( str )
88
110
  end
89
111
 
90
112
  # Unwraps a string from being wrapped in parenthesis
@@ -94,8 +116,11 @@ module Mail
94
116
  # str = '(This is a string)'
95
117
  # unparen( str ) #=> 'This is a string'
96
118
  def unparen( str )
97
- match = str.match(/^\((.*?)\)$/)
98
- match ? match[1] : str
119
+ if str.start_with?('(') && str.end_with?(')')
120
+ str.slice(1..-2)
121
+ else
122
+ str
123
+ end
99
124
  end
100
125
 
101
126
  # Wraps a string in angle brackets and escapes any that are in the string itself
@@ -104,7 +129,7 @@ module Mail
104
129
  #
105
130
  # bracket( 'This is a string' ) #=> '<This is a string>'
106
131
  def bracket( str )
107
- RubyVer.bracket( str )
132
+ Utilities.bracket( str )
108
133
  end
109
134
 
110
135
  # Unwraps a string from being wrapped in parenthesis
@@ -114,8 +139,11 @@ module Mail
114
139
  # str = '<This is a string>'
115
140
  # unbracket( str ) #=> 'This is a string'
116
141
  def unbracket( str )
117
- match = str.match(/^\<(.*?)\>$/)
118
- match ? match[1] : str
142
+ if str.start_with?('<') && str.end_with?('>')
143
+ str.slice(1..-2)
144
+ else
145
+ str
146
+ end
119
147
  end
120
148
 
121
149
  # Escape parenthesies in a string
@@ -125,7 +153,7 @@ module Mail
125
153
  # str = 'This is (a) string'
126
154
  # escape_paren( str ) #=> 'This is \(a\) string'
127
155
  def escape_paren( str )
128
- RubyVer.escape_paren( str )
156
+ Utilities.escape_paren( str )
129
157
  end
130
158
 
131
159
  def uri_escape( str )
@@ -137,7 +165,7 @@ module Mail
137
165
  end
138
166
 
139
167
  def uri_parser
140
- @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
168
+ @uri_parser ||= URI.const_defined?(:DEFAULT_PARSER) ? URI::DEFAULT_PARSER : URI
141
169
  end
142
170
 
143
171
  # Matches two objects with their to_s values case insensitively
@@ -178,9 +206,9 @@ module Mail
178
206
  # Example:
179
207
  #
180
208
  # string = :resent_from_field
181
- # dasherize ( string ) #=> 'resent_from_field'
209
+ # dasherize( string ) #=> 'resent-from-field'
182
210
  def dasherize( str )
183
- str.to_s.tr(UNDERSCORE, HYPHEN)
211
+ str.to_s.tr(Constants::UNDERSCORE, Constants::HYPHEN)
184
212
  end
185
213
 
186
214
  # Swaps out all hyphens (-) for underscores (_) good for stringing to symbols
@@ -191,89 +219,344 @@ module Mail
191
219
  # string = :resent_from_field
192
220
  # underscoreize ( string ) #=> 'resent_from_field'
193
221
  def underscoreize( str )
194
- 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
195
253
  end
196
254
 
197
- if RUBY_VERSION <= '1.8.6'
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
264
+ end
198
265
 
199
- def map_lines( str, &block )
200
- results = []
201
- str.each_line do |line|
202
- results << yield(line)
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
277
+
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))
203
304
  end
204
- results
205
305
  end
306
+ end
206
307
 
207
- def map_with_index( enum, &block )
208
- results = []
209
- enum.each_with_index do |token, i|
210
- 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))
211
315
  end
212
- results
213
316
  end
214
317
 
215
- else
318
+ private
216
319
 
217
- def map_lines( str, &block )
218
- 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)
219
330
  end
331
+ end
220
332
 
221
- def map_with_index( enum, &block )
222
- enum.each_with_index.map(&block)
223
- end
333
+ class << self
334
+ attr_accessor :charset_encoder
335
+ end
336
+ self.charset_encoder = BestEffortCharsetEncoder.new
224
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 }
225
343
  end
226
344
 
227
- # Test String#encode works correctly with line endings.
228
- # Some versions of Ruby (e.g. MRI <1.9, JRuby, Rubinius) line ending
229
- # normalization does not work correctly or did not have #encode.
230
- if ("\r".encode(:universal_newline => true) rescue nil) == LF &&
231
- (LF.encode(:crlf_newline => true) rescue nil) == CRLF
232
- # 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
233
350
 
234
- def self.to_lf(input)
235
- input.kind_of?(String) ? input.to_str.encode(input.encoding, :universal_newline => true) : ''
236
- end
351
+ def Utilities.escape_bracket( str )
352
+ re = /(?<!\\)([\<\>])/ # Only match unescaped brackets
353
+ str.gsub(re) { |s| '\\' + s }
354
+ end
237
355
 
238
- def self.to_crlf(input)
239
- input.kind_of?(String) ? input.to_str.encode(input.encoding, :universal_newline => true).encode!(input.encoding, :crlf_newline => true) : ''
356
+ def Utilities.bracket( str )
357
+ str = ::Mail::Utilities.unbracket( str )
358
+ str = escape_bracket( str )
359
+ '<' + str + '>'
360
+ end
361
+
362
+ def Utilities.decode_base64(str)
363
+ if !str.end_with?("=") && str.length % 4 != 0
364
+ str = str.ljust((str.length + 3) & ~3, "=")
240
365
  end
366
+ str.unpack( 'm' ).first
367
+ end
241
368
 
242
- else
369
+ def Utilities.encode_base64(str)
370
+ [str].pack( 'm' )
371
+ end
243
372
 
244
- def self.to_lf(input)
245
- input.kind_of?(String) ? input.to_str.gsub(/\r\n|\r/, LF) : ''
373
+ def Utilities.has_constant?(klass, string)
374
+ klass.const_defined?( string, false )
375
+ end
376
+
377
+ def Utilities.get_constant(klass, string)
378
+ klass.const_get( string )
379
+ end
380
+
381
+ def Utilities.transcode_charset(str, from_encoding, to_encoding = Encoding::UTF_8)
382
+ to_encoding = Encoding.find(to_encoding)
383
+ replacement_char = to_encoding == Encoding::UTF_8 ? '�' : '?'
384
+ charset_encoder.encode(str.dup, from_encoding).encode(to_encoding, :undef => :replace, :invalid => :replace, :replace => replacement_char)
385
+ end
386
+
387
+ # From Ruby stdlib Net::IMAP
388
+ def Utilities.encode_utf7(string)
389
+ string.gsub(/(&)|[^\x20-\x7e]+/) do
390
+ if $1
391
+ "&-"
392
+ else
393
+ base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
394
+ "&" + base64.delete("=").tr("/", ",") + "-"
395
+ end
396
+ end.force_encoding(Encoding::ASCII_8BIT)
397
+ end
398
+
399
+ def Utilities.decode_utf7(utf7)
400
+ utf7.gsub(/&([^-]+)?-/n) do
401
+ if $1
402
+ ($1.tr(",", "/") + "===").unpack("m")[0].encode(Encoding::UTF_8, Encoding::UTF_16BE)
403
+ else
404
+ "&"
405
+ end
246
406
  end
407
+ end
247
408
 
248
- if RUBY_VERSION >= '1.9'
249
- # This 1.9 only regex can save a reasonable amount of time (~20%)
250
- # by not matching "\r\n" so the string is returned unchanged in
251
- # the common case.
252
- CRLF_REGEX = Regexp.new("(?<!\r)\n|\r(?!\n)")
253
- else
254
- 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)
255
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
256
426
 
257
- def self.to_crlf(input)
258
- input.kind_of?(String) ? input.to_str.gsub(CRLF_REGEX, CRLF) : ''
427
+ def Utilities.q_value_encode(str, encoding = nil)
428
+ encoding = str.encoding.to_s
429
+ [Encodings::QuotedPrintable.encode(str), encoding]
430
+ end
431
+
432
+ def Utilities.q_value_decode(str)
433
+ match = str.match(/\=\?(.+)?\?[Qq]\?(.*)\?\=/m)
434
+ if match
435
+ charset = match[1]
436
+ string = match[2].gsub(/_/, '=20')
437
+ # Remove trailing = if it exists in a Q encoding
438
+ string = string.sub(/\=$/, '')
439
+ str = Encodings::QuotedPrintable.decode(string)
440
+ str = charset_encoder.encode(str, charset)
441
+ # We assume that binary strings hold utf-8 directly to work around
442
+ # jruby/jruby#829 which subtly changes String#encode semantics.
443
+ str.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::ASCII_8BIT
259
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
260
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)
261
458
  end
262
459
 
263
- # Returns true if the object is considered blank.
264
- # A blank includes things like '', ' ', nil,
265
- # 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.
266
472
  #
267
- # This logic is mostly shared with ActiveSupport's blank?
268
- def self.blank?(value)
269
- if value.kind_of?(NilClass)
270
- true
271
- elsif value.kind_of?(String)
272
- 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
+
273
529
  else
274
- value.respond_to?(:empty?) ? value.empty? : !value
530
+ charset
275
531
  end
532
+
533
+ convert_to_encoding(encoding)
534
+ end
535
+
536
+ def Utilities.string_byteslice(str, *args)
537
+ str.byteslice(*args)
276
538
  end
277
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
278
561
  end
279
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 = 4
6
+ MINOR = 8
7
+ PATCH = 0
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