mail 2.6.4 → 2.9.0

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 (180) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +208 -156
  3. data/lib/mail/attachments_list.rb +13 -10
  4. data/lib/mail/body.rb +96 -107
  5. data/lib/mail/configuration.rb +2 -0
  6. data/lib/mail/constants.rb +27 -5
  7. data/lib/mail/elements/address.rb +61 -50
  8. data/lib/mail/elements/address_list.rb +11 -19
  9. data/lib/mail/elements/content_disposition_element.rb +9 -16
  10. data/lib/mail/elements/content_location_element.rb +6 -11
  11. data/lib/mail/elements/content_transfer_encoding_element.rb +6 -11
  12. data/lib/mail/elements/content_type_element.rb +16 -23
  13. data/lib/mail/elements/date_time_element.rb +7 -15
  14. data/lib/mail/elements/envelope_from_element.rb +22 -23
  15. data/lib/mail/elements/message_ids_element.rb +18 -13
  16. data/lib/mail/elements/mime_version_element.rb +7 -15
  17. data/lib/mail/elements/phrase_list.rb +12 -10
  18. data/lib/mail/elements/received_element.rb +27 -19
  19. data/lib/mail/encodings/7bit.rb +9 -14
  20. data/lib/mail/encodings/8bit.rb +2 -21
  21. data/lib/mail/encodings/base64.rb +11 -12
  22. data/lib/mail/encodings/binary.rb +3 -22
  23. data/lib/mail/encodings/identity.rb +24 -0
  24. data/lib/mail/encodings/quoted_printable.rb +6 -6
  25. data/lib/mail/encodings/transfer_encoding.rb +38 -29
  26. data/lib/mail/encodings/unix_to_unix.rb +4 -2
  27. data/lib/mail/encodings.rb +83 -56
  28. data/lib/mail/envelope.rb +11 -14
  29. data/lib/mail/field.rb +181 -130
  30. data/lib/mail/field_list.rb +61 -8
  31. data/lib/mail/fields/bcc_field.rb +33 -52
  32. data/lib/mail/fields/cc_field.rb +27 -49
  33. data/lib/mail/fields/comments_field.rb +26 -37
  34. data/lib/mail/fields/common_address_field.rb +162 -0
  35. data/lib/mail/fields/common_date_field.rb +56 -0
  36. data/lib/mail/fields/common_field.rb +77 -0
  37. data/lib/mail/fields/common_message_id_field.rb +41 -0
  38. data/lib/mail/fields/content_description_field.rb +6 -14
  39. data/lib/mail/fields/content_disposition_field.rb +11 -38
  40. data/lib/mail/fields/content_id_field.rb +23 -51
  41. data/lib/mail/fields/content_location_field.rb +10 -25
  42. data/lib/mail/fields/content_transfer_encoding_field.rb +30 -31
  43. data/lib/mail/fields/content_type_field.rb +53 -84
  44. data/lib/mail/fields/date_field.rb +22 -52
  45. data/lib/mail/fields/from_field.rb +27 -49
  46. data/lib/mail/fields/in_reply_to_field.rb +37 -49
  47. data/lib/mail/fields/keywords_field.rb +17 -31
  48. data/lib/mail/fields/message_id_field.rb +24 -71
  49. data/lib/mail/fields/mime_version_field.rb +18 -30
  50. data/lib/mail/fields/named_structured_field.rb +10 -0
  51. data/lib/mail/fields/named_unstructured_field.rb +10 -0
  52. data/lib/mail/fields/optional_field.rb +9 -8
  53. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +13 -11
  54. data/lib/mail/fields/received_field.rb +42 -57
  55. data/lib/mail/fields/references_field.rb +34 -49
  56. data/lib/mail/fields/reply_to_field.rb +27 -49
  57. data/lib/mail/fields/resent_bcc_field.rb +27 -49
  58. data/lib/mail/fields/resent_cc_field.rb +27 -49
  59. data/lib/mail/fields/resent_date_field.rb +4 -30
  60. data/lib/mail/fields/resent_from_field.rb +27 -49
  61. data/lib/mail/fields/resent_message_id_field.rb +4 -29
  62. data/lib/mail/fields/resent_sender_field.rb +26 -56
  63. data/lib/mail/fields/resent_to_field.rb +27 -49
  64. data/lib/mail/fields/return_path_field.rb +49 -54
  65. data/lib/mail/fields/sender_field.rb +33 -55
  66. data/lib/mail/fields/structured_field.rb +2 -30
  67. data/lib/mail/fields/subject_field.rb +8 -11
  68. data/lib/mail/fields/to_field.rb +27 -49
  69. data/lib/mail/fields/unstructured_field.rb +31 -47
  70. data/lib/mail/fields.rb +9 -0
  71. data/lib/mail/header.rb +71 -110
  72. data/lib/mail/mail.rb +34 -37
  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 +126 -127
  76. data/lib/mail/multibyte/chars.rb +24 -181
  77. data/lib/mail/multibyte/unicode.rb +11 -11
  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 +15 -18
  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 +125 -68
  85. data/lib/mail/network/delivery_methods/smtp_connection.rb +11 -16
  86. data/lib/mail/network/delivery_methods/test_mailer.rb +12 -13
  87. data/lib/mail/network/retriever_methods/base.rb +13 -13
  88. data/lib/mail/network/retriever_methods/imap.rb +25 -9
  89. data/lib/mail/network/retriever_methods/pop3.rb +25 -23
  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 +33228 -116
  94. data/lib/mail/parsers/address_lists_parser.rl +183 -0
  95. data/lib/mail/parsers/content_disposition_parser.rb +885 -49
  96. data/lib/mail/parsers/content_disposition_parser.rl +93 -0
  97. data/lib/mail/parsers/content_location_parser.rb +812 -23
  98. data/lib/mail/parsers/content_location_parser.rl +82 -0
  99. data/lib/mail/parsers/content_transfer_encoding_parser.rb +512 -21
  100. data/lib/mail/parsers/content_transfer_encoding_parser.rl +75 -0
  101. data/lib/mail/parsers/content_type_parser.rb +1039 -55
  102. data/lib/mail/parsers/content_type_parser.rl +94 -0
  103. data/lib/mail/parsers/date_time_parser.rb +880 -25
  104. data/lib/mail/parsers/date_time_parser.rl +73 -0
  105. data/lib/mail/parsers/envelope_from_parser.rb +3672 -40
  106. data/lib/mail/parsers/envelope_from_parser.rl +93 -0
  107. data/lib/mail/parsers/message_ids_parser.rb +5149 -25
  108. data/lib/mail/parsers/message_ids_parser.rl +97 -0
  109. data/lib/mail/parsers/mime_version_parser.rb +500 -26
  110. data/lib/mail/parsers/mime_version_parser.rl +72 -0
  111. data/lib/mail/parsers/phrase_lists_parser.rb +873 -22
  112. data/lib/mail/parsers/phrase_lists_parser.rl +94 -0
  113. data/lib/mail/parsers/received_parser.rb +8779 -43
  114. data/lib/mail/parsers/received_parser.rl +95 -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 +25 -29
  127. data/lib/mail/parts_list.rb +62 -6
  128. data/lib/mail/smtp_envelope.rb +57 -0
  129. data/lib/mail/utilities.rb +361 -74
  130. data/lib/mail/version.rb +2 -2
  131. data/lib/mail/yaml.rb +30 -0
  132. data/lib/mail.rb +4 -37
  133. metadata +125 -67
  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/check_delivery_params.rb +0 -21
  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 -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,11 @@ 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 ||= if URI.const_defined?(:DEFAULT_PARSER)
169
+ defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER
170
+ else
171
+ URI
172
+ end
141
173
  end
142
174
 
143
175
  # Matches two objects with their to_s values case insensitively
@@ -178,9 +210,9 @@ module Mail
178
210
  # Example:
179
211
  #
180
212
  # string = :resent_from_field
181
- # dasherize ( string ) #=> 'resent_from_field'
213
+ # dasherize( string ) #=> 'resent-from-field'
182
214
  def dasherize( str )
183
- str.to_s.tr(UNDERSCORE, HYPHEN)
215
+ str.to_s.tr(Constants::UNDERSCORE, Constants::HYPHEN)
184
216
  end
185
217
 
186
218
  # Swaps out all hyphens (-) for underscores (_) good for stringing to symbols
@@ -191,89 +223,344 @@ module Mail
191
223
  # string = :resent_from_field
192
224
  # underscoreize ( string ) #=> 'resent_from_field'
193
225
  def underscoreize( str )
194
- str.to_s.downcase.tr(HYPHEN, UNDERSCORE)
226
+ str.to_s.downcase.tr(Constants::HYPHEN, Constants::UNDERSCORE)
227
+ end
228
+
229
+ def map_lines( str, &block )
230
+ str.each_line.map(&block)
231
+ end
232
+
233
+ def map_with_index( enum, &block )
234
+ enum.each_with_index.map(&block)
235
+ end
236
+
237
+ def self.binary_unsafe_to_lf(string) #:nodoc:
238
+ string.gsub(/\r\n|\r/, Constants::LF)
239
+ end
240
+
241
+ TO_CRLF_REGEX =
242
+ # This 1.9 only regex can save a reasonable amount of time (~20%)
243
+ # by not matching "\r\n" so the string is returned unchanged in
244
+ # the common case.
245
+ Regexp.new("(?<!\r)\n|\r(?!\n)")
246
+
247
+ def self.binary_unsafe_to_crlf(string) #:nodoc:
248
+ string.gsub(TO_CRLF_REGEX, Constants::CRLF)
249
+ end
250
+
251
+ def self.safe_for_line_ending_conversion?(string) #:nodoc:
252
+ if string.encoding == Encoding::BINARY
253
+ string.ascii_only?
254
+ else
255
+ string.valid_encoding?
256
+ end
195
257
  end
196
258
 
197
- if RUBY_VERSION <= '1.8.6'
259
+ # Convert line endings to \n unless the string is binary. Used for
260
+ # sendmail delivery and for decoding 8bit Content-Transfer-Encoding.
261
+ def self.to_lf(string)
262
+ string = string.to_s
263
+ if safe_for_line_ending_conversion? string
264
+ binary_unsafe_to_lf string
265
+ else
266
+ string
267
+ end
268
+ end
198
269
 
199
- def map_lines( str, &block )
200
- results = []
201
- str.each_line do |line|
202
- results << yield(line)
270
+ # Convert line endings to \r\n unless the string is binary. Used for
271
+ # encoding 8bit and base64 Content-Transfer-Encoding and for convenience
272
+ # when parsing emails with \n line endings instead of the required \r\n.
273
+ def self.to_crlf(string)
274
+ string = string.to_s
275
+ if safe_for_line_ending_conversion? string
276
+ binary_unsafe_to_crlf string
277
+ else
278
+ string
279
+ end
280
+ end
281
+
282
+ # Returns true if the object is considered blank.
283
+ # A blank includes things like '', ' ', nil,
284
+ # and arrays and hashes that have nothing in them.
285
+ #
286
+ # This logic is mostly shared with ActiveSupport's blank?
287
+ def blank?(value)
288
+ if value.kind_of?(NilClass)
289
+ true
290
+ elsif value.kind_of?(String)
291
+ value !~ /\S/
292
+ else
293
+ value.respond_to?(:empty?) ? value.empty? : !value
294
+ end
295
+ end
296
+
297
+ def generate_message_id
298
+ "<#{Mail.random_tag}@#{::Socket.gethostname}.mail>"
299
+ end
300
+
301
+ class StrictCharsetEncoder
302
+ def encode(string, charset)
303
+ case charset
304
+ when /utf-?7/i
305
+ Mail::Utilities.decode_utf7(string)
306
+ else
307
+ string.force_encoding(Mail::Utilities.pick_encoding(charset))
203
308
  end
204
- results
205
309
  end
310
+ end
206
311
 
207
- def map_with_index( enum, &block )
208
- results = []
209
- enum.each_with_index do |token, i|
210
- results[i] = yield(token, i)
312
+ class BestEffortCharsetEncoder
313
+ def encode(string, charset)
314
+ case charset
315
+ when /utf-?7/i
316
+ Mail::Utilities.decode_utf7(string)
317
+ else
318
+ string.force_encoding(pick_encoding(charset))
211
319
  end
212
- results
213
320
  end
214
321
 
215
- else
322
+ private
216
323
 
217
- def map_lines( str, &block )
218
- str.each_line.map(&block)
324
+ def pick_encoding(charset)
325
+ charset = case charset
326
+ when /ansi_x3.110-1983/
327
+ 'ISO-8859-1'
328
+ when /Windows-?1258/i # Windows-1258 is similar to 1252
329
+ "Windows-1252"
330
+ else
331
+ charset
332
+ end
333
+ Mail::Utilities.pick_encoding(charset)
219
334
  end
335
+ end
220
336
 
221
- def map_with_index( enum, &block )
222
- enum.each_with_index.map(&block)
223
- end
337
+ class << self
338
+ attr_accessor :charset_encoder
339
+ end
340
+ self.charset_encoder = BestEffortCharsetEncoder.new
224
341
 
342
+ # Escapes any parenthesis in a string that are unescaped this uses
343
+ # a Ruby 1.9.1 regexp feature of negative look behind
344
+ def Utilities.escape_paren( str )
345
+ re = /(?<!\\)([\(\)])/ # Only match unescaped parens
346
+ str.gsub(re) { |s| '\\' + s }
225
347
  end
226
348
 
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
349
+ def Utilities.paren( str )
350
+ str = ::Mail::Utilities.unparen( str )
351
+ str = escape_paren( str )
352
+ '(' + str + ')'
353
+ end
233
354
 
234
- def self.to_lf(input)
235
- input.kind_of?(String) ? input.to_str.encode(input.encoding, :universal_newline => true) : ''
236
- end
355
+ def Utilities.escape_bracket( str )
356
+ re = /(?<!\\)([\<\>])/ # Only match unescaped brackets
357
+ str.gsub(re) { |s| '\\' + s }
358
+ end
237
359
 
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) : ''
360
+ def Utilities.bracket( str )
361
+ str = ::Mail::Utilities.unbracket( str )
362
+ str = escape_bracket( str )
363
+ '<' + str + '>'
364
+ end
365
+
366
+ def Utilities.decode_base64(str)
367
+ if !str.end_with?("=") && str.length % 4 != 0
368
+ str = str.ljust((str.length + 3) & ~3, "=")
240
369
  end
370
+ str.unpack1( 'm' )
371
+ end
241
372
 
242
- else
373
+ def Utilities.encode_base64(str)
374
+ [str].pack( 'm' )
375
+ end
243
376
 
244
- def self.to_lf(input)
245
- input.kind_of?(String) ? input.to_str.gsub(/\r\n|\r/, LF) : ''
377
+ def Utilities.has_constant?(klass, string)
378
+ klass.const_defined?( string, false )
379
+ end
380
+
381
+ def Utilities.get_constant(klass, string)
382
+ klass.const_get( string )
383
+ end
384
+
385
+ def Utilities.transcode_charset(str, from_encoding, to_encoding = Encoding::UTF_8)
386
+ to_encoding = Encoding.find(to_encoding)
387
+ replacement_char = to_encoding == Encoding::UTF_8 ? '�' : '?'
388
+ charset_encoder.encode(str.dup, from_encoding).encode(to_encoding, :undef => :replace, :invalid => :replace, :replace => replacement_char)
389
+ end
390
+
391
+ # From Ruby stdlib Net::IMAP
392
+ def Utilities.encode_utf7(string)
393
+ string.gsub(/(&)|[^\x20-\x7e]+/) do
394
+ if $1
395
+ "&-"
396
+ else
397
+ base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
398
+ "&" + base64.delete("=").tr("/", ",") + "-"
399
+ end
400
+ end.force_encoding(Encoding::ASCII_8BIT)
401
+ end
402
+
403
+ def Utilities.decode_utf7(utf7)
404
+ utf7.gsub(/&([^-]+)?-/n) do
405
+ if $1
406
+ ($1.tr(",", "/") + "===").unpack1("m").encode(Encoding::UTF_8, Encoding::UTF_16BE)
407
+ else
408
+ "&"
409
+ end
246
410
  end
411
+ end
247
412
 
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/
413
+ def Utilities.b_value_encode(str, encoding = nil)
414
+ encoding = str.encoding.to_s
415
+ [Utilities.encode_base64(str), encoding]
416
+ end
417
+
418
+ def Utilities.b_value_decode(str)
419
+ match = str.match(/\=\?(.+)?\?[Bb]\?(.*)\?\=/m)
420
+ if match
421
+ charset = match[1]
422
+ str = Utilities.decode_base64(match[2])
423
+ str = charset_encoder.encode(str, charset)
255
424
  end
425
+ transcode_to_scrubbed_utf8(str)
426
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError, Encoding::InvalidByteSequenceError
427
+ warn "WARNING: Encoding conversion failed #{$!}"
428
+ str.dup.force_encoding(Encoding::UTF_8)
429
+ end
256
430
 
257
- def self.to_crlf(input)
258
- input.kind_of?(String) ? input.to_str.gsub(CRLF_REGEX, CRLF) : ''
431
+ def Utilities.q_value_encode(str, encoding = nil)
432
+ encoding = str.encoding.to_s
433
+ [Encodings::QuotedPrintable.encode(str), encoding]
434
+ end
435
+
436
+ def Utilities.q_value_decode(str)
437
+ match = str.match(/\=\?(.+)?\?[Qq]\?(.*)\?\=/m)
438
+ if match
439
+ charset = match[1]
440
+ string = match[2].gsub(/_/, '=20')
441
+ # Remove trailing = if it exists in a Q encoding
442
+ string = string.sub(/\=$/, '')
443
+ str = Encodings::QuotedPrintable.decode(string)
444
+ str = charset_encoder.encode(str, charset)
445
+ # We assume that binary strings hold utf-8 directly to work around
446
+ # jruby/jruby#829 which subtly changes String#encode semantics.
447
+ str.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::ASCII_8BIT
259
448
  end
449
+ transcode_to_scrubbed_utf8(str)
450
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
451
+ warn "WARNING: Encoding conversion failed #{$!}"
452
+ str.dup.force_encoding(Encoding::UTF_8)
453
+ end
260
454
 
455
+ def Utilities.param_decode(str, encoding)
456
+ str = uri_parser.unescape(str)
457
+ str = charset_encoder.encode(str, encoding) if encoding
458
+ transcode_to_scrubbed_utf8(str)
459
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
460
+ warn "WARNING: Encoding conversion failed #{$!}"
461
+ str.dup.force_encoding(Encoding::UTF_8)
261
462
  end
262
463
 
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.
464
+ def Utilities.param_encode(str)
465
+ encoding = str.encoding.to_s.downcase
466
+ language = Configuration.instance.param_encode_language
467
+ "#{encoding}'#{language}'#{uri_parser.escape(str)}"
468
+ end
469
+
470
+ def Utilities.uri_parser
471
+ defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER
472
+ end
473
+
474
+ # Pick a Ruby encoding corresponding to the message charset. Most
475
+ # charsets have a Ruby encoding, but some need manual aliasing here.
266
476
  #
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/
477
+ # TODO: add this as a test somewhere:
478
+ # Encoding.list.map { |e| [e.to_s.upcase == pick_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
479
+ # Encoding.list.map { |e| [e.to_s == pick_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
480
+ def Utilities.pick_encoding(charset)
481
+ charset = charset.to_s
482
+ encoding = case charset.downcase
483
+
484
+ # ISO-8859-8-I etc. http://en.wikipedia.org/wiki/ISO-8859-8-I
485
+ when /^iso[-_]?8859-(\d+)(-i)?$/
486
+ "ISO-8859-#{$1}"
487
+
488
+ # ISO-8859-15, ISO-2022-JP and alike
489
+ when /^iso[-_]?(\d{4})-?(\w{1,2})$/
490
+ "ISO-#{$1}-#{$2}"
491
+
492
+ # "ISO-2022-JP-KDDI" and alike
493
+ when /^iso[-_]?(\d{4})-?(\w{1,2})-?(\w*)$/
494
+ "ISO-#{$1}-#{$2}-#{$3}"
495
+
496
+ # UTF-8, UTF-32BE and alike
497
+ when /^utf[\-_]?(\d{1,2})?(\w{1,2})$/
498
+ "UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
499
+
500
+ # Windows-1252 and alike
501
+ when /^windows-?(.*)$/
502
+ "Windows-#{$1}"
503
+
504
+ when '8bit'
505
+ Encoding::ASCII_8BIT
506
+
507
+ # alternatives/misspellings of us-ascii seen in the wild
508
+ when /^iso[-_]?646(-us)?$/, 'us=ascii'
509
+ Encoding::ASCII
510
+
511
+ # Microsoft-specific alias for MACROMAN
512
+ when 'macintosh'
513
+ Encoding::MACROMAN
514
+
515
+ # Microsoft-specific alias for CP949 (Korean)
516
+ when 'ks_c_5601-1987'
517
+ Encoding::CP949
518
+
519
+ # Wrongly written Shift_JIS (Japanese)
520
+ when 'shift-jis'
521
+ Encoding::Shift_JIS
522
+
523
+ # GB2312 (Chinese charset) is a subset of GB18030 (its replacement)
524
+ when 'gb2312'
525
+ Encoding::GB18030
526
+
527
+ when 'cp-850'
528
+ Encoding::CP850
529
+
530
+ when 'latin2'
531
+ Encoding::ISO_8859_2
532
+
273
533
  else
274
- value.respond_to?(:empty?) ? value.empty? : !value
534
+ charset
275
535
  end
536
+
537
+ convert_to_encoding(encoding)
538
+ end
539
+
540
+ def Utilities.string_byteslice(str, *args)
541
+ str.byteslice(*args)
276
542
  end
277
543
 
544
+ class << self
545
+ private
546
+
547
+ def convert_to_encoding(encoding)
548
+ if encoding.is_a?(Encoding)
549
+ encoding
550
+ else
551
+ # Fall back to ASCII for charsets that Ruby doesn't recognize
552
+ begin
553
+ Encoding.find(encoding)
554
+ rescue ArgumentError
555
+ Encoding::BINARY
556
+ end
557
+ end
558
+ end
559
+
560
+ def transcode_to_scrubbed_utf8(str)
561
+ decoded = str.encode(Encoding::UTF_8, :undef => :replace, :invalid => :replace, :replace => "�")
562
+ decoded.valid_encoding? ? decoded : decoded.encode(Encoding::UTF_16LE, :invalid => :replace, :replace => "�").encode(Encoding::UTF_8)
563
+ end
564
+ end
278
565
  end
279
566
  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 = 9
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'
@@ -79,9 +48,7 @@ module Mail # :doc:
79
48
  require 'mail/field'
80
49
  require 'mail/field_list'
81
50
 
82
- require 'mail/envelope'
83
-
84
- register_autoload :Parsers, "mail/parsers"
51
+ register_autoload :Envelope, 'mail/envelope'
85
52
 
86
53
  # Autoload header field elements and transfer encodings.
87
54
  require 'mail/elements'
@@ -91,7 +58,7 @@ module Mail # :doc:
91
58
  require 'mail/encodings/unix_to_unix'
92
59
 
93
60
  require 'mail/matchers/has_sent_mail'
94
- require 'mail/matchers/attachment_matchers.rb'
61
+ require 'mail/matchers/attachment_matchers'
95
62
 
96
63
  # Finally... require all the Mail.methods
97
64
  require 'mail/mail'