mail 2.6.1 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +150 -107
  4. data/lib/mail/attachments_list.rb +13 -10
  5. data/lib/mail/body.rb +104 -90
  6. data/lib/mail/check_delivery_params.rb +55 -10
  7. data/lib/mail/configuration.rb +3 -0
  8. data/lib/mail/constants.rb +79 -0
  9. data/lib/mail/elements/address.rb +96 -108
  10. data/lib/mail/elements/address_list.rb +13 -30
  11. data/lib/mail/elements/content_disposition_element.rb +10 -16
  12. data/lib/mail/elements/content_location_element.rb +9 -13
  13. data/lib/mail/elements/content_transfer_encoding_element.rb +7 -11
  14. data/lib/mail/elements/content_type_element.rb +17 -23
  15. data/lib/mail/elements/date_time_element.rb +8 -15
  16. data/lib/mail/elements/envelope_from_element.rb +23 -23
  17. data/lib/mail/elements/message_ids_element.rb +22 -15
  18. data/lib/mail/elements/mime_version_element.rb +8 -15
  19. data/lib/mail/elements/phrase_list.rb +13 -10
  20. data/lib/mail/elements/received_element.rb +28 -19
  21. data/lib/mail/elements.rb +1 -0
  22. data/lib/mail/encodings/7bit.rb +10 -14
  23. data/lib/mail/encodings/8bit.rb +5 -18
  24. data/lib/mail/encodings/base64.rb +15 -10
  25. data/lib/mail/encodings/binary.rb +4 -22
  26. data/lib/mail/encodings/identity.rb +24 -0
  27. data/lib/mail/encodings/quoted_printable.rb +13 -7
  28. data/lib/mail/encodings/transfer_encoding.rb +47 -28
  29. data/lib/mail/encodings/unix_to_unix.rb +20 -0
  30. data/lib/mail/encodings.rb +102 -92
  31. data/lib/mail/envelope.rb +12 -14
  32. data/lib/mail/field.rb +121 -85
  33. data/lib/mail/field_list.rb +62 -8
  34. data/lib/mail/fields/bcc_field.rb +42 -48
  35. data/lib/mail/fields/cc_field.rb +29 -50
  36. data/lib/mail/fields/comments_field.rb +28 -37
  37. data/lib/mail/fields/common_address_field.rb +170 -0
  38. data/lib/mail/fields/common_date_field.rb +58 -0
  39. data/lib/mail/fields/common_field.rb +77 -0
  40. data/lib/mail/fields/common_message_id_field.rb +42 -0
  41. data/lib/mail/fields/content_description_field.rb +8 -14
  42. data/lib/mail/fields/content_disposition_field.rb +20 -44
  43. data/lib/mail/fields/content_id_field.rb +25 -51
  44. data/lib/mail/fields/content_location_field.rb +12 -25
  45. data/lib/mail/fields/content_transfer_encoding_field.rb +32 -31
  46. data/lib/mail/fields/content_type_field.rb +51 -80
  47. data/lib/mail/fields/date_field.rb +24 -52
  48. data/lib/mail/fields/from_field.rb +29 -50
  49. data/lib/mail/fields/in_reply_to_field.rb +39 -49
  50. data/lib/mail/fields/keywords_field.rb +19 -32
  51. data/lib/mail/fields/message_id_field.rb +26 -71
  52. data/lib/mail/fields/mime_version_field.rb +20 -30
  53. data/lib/mail/fields/named_structured_field.rb +11 -0
  54. data/lib/mail/fields/named_unstructured_field.rb +11 -0
  55. data/lib/mail/fields/optional_field.rb +10 -7
  56. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +16 -13
  57. data/lib/mail/fields/received_field.rb +44 -57
  58. data/lib/mail/fields/references_field.rb +36 -49
  59. data/lib/mail/fields/reply_to_field.rb +29 -50
  60. data/lib/mail/fields/resent_bcc_field.rb +29 -50
  61. data/lib/mail/fields/resent_cc_field.rb +29 -50
  62. data/lib/mail/fields/resent_date_field.rb +6 -30
  63. data/lib/mail/fields/resent_from_field.rb +29 -50
  64. data/lib/mail/fields/resent_message_id_field.rb +6 -29
  65. data/lib/mail/fields/resent_sender_field.rb +28 -57
  66. data/lib/mail/fields/resent_to_field.rb +29 -50
  67. data/lib/mail/fields/return_path_field.rb +51 -55
  68. data/lib/mail/fields/sender_field.rb +35 -56
  69. data/lib/mail/fields/structured_field.rb +4 -30
  70. data/lib/mail/fields/subject_field.rb +10 -11
  71. data/lib/mail/fields/to_field.rb +29 -50
  72. data/lib/mail/fields/unstructured_field.rb +36 -50
  73. data/lib/mail/fields.rb +1 -0
  74. data/lib/mail/header.rb +73 -110
  75. data/lib/mail/indifferent_hash.rb +1 -0
  76. data/lib/mail/mail.rb +6 -11
  77. data/lib/mail/matchers/attachment_matchers.rb +44 -0
  78. data/lib/mail/matchers/has_sent_mail.rb +53 -9
  79. data/lib/mail/message.rb +132 -136
  80. data/lib/mail/multibyte/chars.rb +24 -180
  81. data/lib/mail/multibyte/unicode.rb +31 -26
  82. data/lib/mail/multibyte/utils.rb +27 -43
  83. data/lib/mail/multibyte.rb +56 -16
  84. data/lib/mail/network/delivery_methods/exim.rb +9 -11
  85. data/lib/mail/network/delivery_methods/file_delivery.rb +14 -16
  86. data/lib/mail/network/delivery_methods/logger_delivery.rb +34 -0
  87. data/lib/mail/network/delivery_methods/sendmail.rb +68 -24
  88. data/lib/mail/network/delivery_methods/smtp.rb +77 -54
  89. data/lib/mail/network/delivery_methods/smtp_connection.rb +5 -9
  90. data/lib/mail/network/delivery_methods/test_mailer.rb +9 -9
  91. data/lib/mail/network/retriever_methods/base.rb +9 -8
  92. data/lib/mail/network/retriever_methods/imap.rb +21 -7
  93. data/lib/mail/network/retriever_methods/pop3.rb +6 -3
  94. data/lib/mail/network/retriever_methods/test_retriever.rb +4 -2
  95. data/lib/mail/network.rb +2 -0
  96. data/lib/mail/parser_tools.rb +15 -0
  97. data/lib/mail/parsers/address_lists_parser.rb +33226 -116
  98. data/lib/mail/parsers/address_lists_parser.rl +179 -0
  99. data/lib/mail/parsers/content_disposition_parser.rb +883 -49
  100. data/lib/mail/parsers/content_disposition_parser.rl +89 -0
  101. data/lib/mail/parsers/content_location_parser.rb +810 -23
  102. data/lib/mail/parsers/content_location_parser.rl +78 -0
  103. data/lib/mail/parsers/content_transfer_encoding_parser.rb +510 -21
  104. data/lib/mail/parsers/content_transfer_encoding_parser.rl +71 -0
  105. data/lib/mail/parsers/content_type_parser.rb +1031 -47
  106. data/lib/mail/parsers/content_type_parser.rl +90 -0
  107. data/lib/mail/parsers/date_time_parser.rb +879 -24
  108. data/lib/mail/parsers/date_time_parser.rl +69 -0
  109. data/lib/mail/parsers/envelope_from_parser.rb +3670 -40
  110. data/lib/mail/parsers/envelope_from_parser.rl +89 -0
  111. data/lib/mail/parsers/message_ids_parser.rb +5147 -25
  112. data/lib/mail/parsers/message_ids_parser.rl +93 -0
  113. data/lib/mail/parsers/mime_version_parser.rb +498 -26
  114. data/lib/mail/parsers/mime_version_parser.rl +68 -0
  115. data/lib/mail/parsers/phrase_lists_parser.rb +872 -21
  116. data/lib/mail/parsers/phrase_lists_parser.rl +90 -0
  117. data/lib/mail/parsers/received_parser.rb +8777 -42
  118. data/lib/mail/parsers/received_parser.rl +91 -0
  119. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  120. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  121. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  122. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  123. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  124. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  125. data/lib/mail/parsers/rfc5322.rl +74 -0
  126. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  127. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  128. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  129. data/lib/mail/parsers.rb +12 -25
  130. data/lib/mail/part.rb +11 -12
  131. data/lib/mail/parts_list.rb +88 -14
  132. data/lib/mail/smtp_envelope.rb +57 -0
  133. data/lib/mail/utilities.rb +377 -40
  134. data/lib/mail/values/unicode_tables.dat +0 -0
  135. data/lib/mail/version.rb +8 -15
  136. data/lib/mail/yaml.rb +30 -0
  137. data/lib/mail.rb +9 -32
  138. metadata +138 -94
  139. data/CHANGELOG.rdoc +0 -752
  140. data/CONTRIBUTING.md +0 -60
  141. data/Dependencies.txt +0 -2
  142. data/Gemfile +0 -15
  143. data/Rakefile +0 -29
  144. data/TODO.rdoc +0 -9
  145. data/VERSION +0 -4
  146. data/lib/mail/core_extensions/nil.rb +0 -19
  147. data/lib/mail/core_extensions/object.rb +0 -13
  148. data/lib/mail/core_extensions/smtp.rb +0 -24
  149. data/lib/mail/core_extensions/string/access.rb +0 -145
  150. data/lib/mail/core_extensions/string/multibyte.rb +0 -78
  151. data/lib/mail/core_extensions/string.rb +0 -43
  152. data/lib/mail/fields/common/address_container.rb +0 -16
  153. data/lib/mail/fields/common/common_address.rb +0 -135
  154. data/lib/mail/fields/common/common_date.rb +0 -35
  155. data/lib/mail/fields/common/common_field.rb +0 -57
  156. data/lib/mail/fields/common/common_message_id.rb +0 -48
  157. data/lib/mail/multibyte/exceptions.rb +0 -8
  158. data/lib/mail/parsers/ragel/common.rl +0 -184
  159. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  160. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  161. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  162. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  163. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  164. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  165. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  166. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  167. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  168. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  169. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  170. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  171. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  172. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2129
  173. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  174. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  175. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  176. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  177. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  178. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  179. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  180. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  181. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  182. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  183. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  184. data/lib/mail/parsers/ragel/ruby.rb +0 -39
  185. data/lib/mail/parsers/ragel.rb +0 -17
  186. data/lib/mail/patterns.rb +0 -37
  187. data/lib/mail/version_specific/ruby_1_8.rb +0 -119
  188. data/lib/mail/version_specific/ruby_1_9.rb +0 -159
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mail
4
+ class SmtpEnvelope #:nodoc:
5
+ # Reasonable cap on address length to avoid SMTP line length
6
+ # overflow on old SMTP servers.
7
+ MAX_ADDRESS_BYTESIZE = 2000
8
+
9
+ attr_reader :from, :to, :message
10
+
11
+ def initialize(mail)
12
+ self.from = mail.smtp_envelope_from
13
+ self.to = mail.smtp_envelope_to
14
+ self.message = mail.encoded
15
+ end
16
+
17
+ def from=(addr)
18
+ if Utilities.blank? addr
19
+ raise ArgumentError, "SMTP From address may not be blank: #{addr.inspect}"
20
+ end
21
+
22
+ @from = validate_addr 'From', addr
23
+ end
24
+
25
+ def to=(addr)
26
+ if Utilities.blank?(addr)
27
+ raise ArgumentError, "SMTP To address may not be blank: #{addr.inspect}"
28
+ end
29
+
30
+ @to = Array(addr).map do |addr|
31
+ validate_addr 'To', addr
32
+ end
33
+ end
34
+
35
+ def message=(message)
36
+ if Utilities.blank?(message)
37
+ raise ArgumentError, 'SMTP message may not be blank'
38
+ end
39
+
40
+ @message = message
41
+ end
42
+
43
+
44
+ private
45
+ def validate_addr(addr_name, addr)
46
+ if addr.bytesize > MAX_ADDRESS_BYTESIZE
47
+ raise ArgumentError, "SMTP #{addr_name} address may not exceed #{MAX_ADDRESS_BYTESIZE} bytes: #{addr.inspect}"
48
+ end
49
+
50
+ if /[\r\n]/ =~ addr
51
+ raise ArgumentError, "SMTP #{addr_name} address may not contain CR or LF line breaks: #{addr.inspect}"
52
+ end
53
+
54
+ addr
55
+ end
56
+ end
57
+ end
@@ -1,11 +1,15 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/constants'
4
+ require 'socket'
5
+
2
6
  module Mail
3
7
  module Utilities
4
- include Patterns
8
+ extend self
5
9
 
6
10
  # Returns true if the string supplied is free from characters not allowed as an ATOM
7
11
  def atom_safe?( str )
8
- not ATOM_UNSAFE === str
12
+ not Constants::ATOM_UNSAFE === str
9
13
  end
10
14
 
11
15
  # If the string supplied has ATOM unsafe characters in it, will return the string quoted
@@ -17,30 +21,38 @@ module Mail
17
21
  # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
18
22
  # in double quotes, otherwise returns the string unmodified
19
23
  def quote_phrase( str )
20
- if RUBY_VERSION >= '1.9'
24
+ if str.respond_to?(:force_encoding)
21
25
  original_encoding = str.encoding
22
- str.force_encoding('ASCII-8BIT')
23
- if (PHRASE_UNSAFE === str)
24
- quoted_str = dquote(str).force_encoding(original_encoding)
25
- str.force_encoding(original_encoding)
26
- quoted_str
26
+ ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
27
+ if Constants::PHRASE_UNSAFE === ascii_str
28
+ dquote(ascii_str).force_encoding(original_encoding)
27
29
  else
28
- str.force_encoding(original_encoding)
30
+ str
29
31
  end
30
32
  else
31
- (PHRASE_UNSAFE === str) ? dquote(str) : str
33
+ Constants::PHRASE_UNSAFE === str ? dquote(str) : str
32
34
  end
33
35
  end
34
36
 
35
37
  # Returns true if the string supplied is free from characters not allowed as a TOKEN
36
38
  def token_safe?( str )
37
- not TOKEN_UNSAFE === str
39
+ not Constants::TOKEN_UNSAFE === str
38
40
  end
39
41
 
40
42
  # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
41
43
  # in double quotes, otherwise returns the string unmodified
42
44
  def quote_token( str )
43
- 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
44
56
  end
45
57
 
46
58
  # Wraps supplied string in double quotes and applies \-escaping as necessary,
@@ -69,19 +81,32 @@ module Mail
69
81
  # unqoute(string) #=> 'This is "a string"'
70
82
  def unquote( str )
71
83
  if str =~ /^"(.*?)"$/
72
- $1.gsub(/\\(.)/, '\1')
84
+ unescape($1)
73
85
  else
74
86
  str
75
87
  end
76
88
  end
77
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
+
78
103
  # Wraps a string in parenthesis and escapes any that are in the string itself.
79
104
  #
80
105
  # Example:
81
106
  #
82
107
  # paren( 'This is a string' ) #=> '(This is a string)'
83
108
  def paren( str )
84
- RubyVer.paren( str )
109
+ Utilities.paren( str )
85
110
  end
86
111
 
87
112
  # Unwraps a string from being wrapped in parenthesis
@@ -91,8 +116,11 @@ module Mail
91
116
  # str = '(This is a string)'
92
117
  # unparen( str ) #=> 'This is a string'
93
118
  def unparen( str )
94
- match = str.match(/^\((.*?)\)$/)
95
- match ? match[1] : str
119
+ if str.start_with?('(') && str.end_with?(')')
120
+ str.slice(1..-2)
121
+ else
122
+ str
123
+ end
96
124
  end
97
125
 
98
126
  # Wraps a string in angle brackets and escapes any that are in the string itself
@@ -101,7 +129,7 @@ module Mail
101
129
  #
102
130
  # bracket( 'This is a string' ) #=> '<This is a string>'
103
131
  def bracket( str )
104
- RubyVer.bracket( str )
132
+ Utilities.bracket( str )
105
133
  end
106
134
 
107
135
  # Unwraps a string from being wrapped in parenthesis
@@ -111,8 +139,11 @@ module Mail
111
139
  # str = '<This is a string>'
112
140
  # unbracket( str ) #=> 'This is a string'
113
141
  def unbracket( str )
114
- match = str.match(/^\<(.*?)\>$/)
115
- match ? match[1] : str
142
+ if str.start_with?('<') && str.end_with?('>')
143
+ str.slice(1..-2)
144
+ else
145
+ str
146
+ end
116
147
  end
117
148
 
118
149
  # Escape parenthesies in a string
@@ -122,7 +153,7 @@ module Mail
122
153
  # str = 'This is (a) string'
123
154
  # escape_paren( str ) #=> 'This is \(a\) string'
124
155
  def escape_paren( str )
125
- RubyVer.escape_paren( str )
156
+ Utilities.escape_paren( str )
126
157
  end
127
158
 
128
159
  def uri_escape( str )
@@ -134,7 +165,7 @@ module Mail
134
165
  end
135
166
 
136
167
  def uri_parser
137
- @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
168
+ @uri_parser ||= URI.const_defined?(:DEFAULT_PARSER) ? URI::DEFAULT_PARSER : URI
138
169
  end
139
170
 
140
171
  # Matches two objects with their to_s values case insensitively
@@ -175,9 +206,9 @@ module Mail
175
206
  # Example:
176
207
  #
177
208
  # string = :resent_from_field
178
- # dasherize ( string ) #=> 'resent_from_field'
209
+ # dasherize( string ) #=> 'resent-from-field'
179
210
  def dasherize( str )
180
- str.to_s.gsub('_', '-')
211
+ str.to_s.tr(Constants::UNDERSCORE, Constants::HYPHEN)
181
212
  end
182
213
 
183
214
  # Swaps out all hyphens (-) for underscores (_) good for stringing to symbols
@@ -188,38 +219,344 @@ module Mail
188
219
  # string = :resent_from_field
189
220
  # underscoreize ( string ) #=> 'resent_from_field'
190
221
  def underscoreize( str )
191
- str.to_s.downcase.gsub('-', '_')
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
264
+ end
265
+
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
192
291
  end
193
292
 
194
- if RUBY_VERSION <= '1.8.6'
293
+ def generate_message_id
294
+ "<#{Mail.random_tag}@#{::Socket.gethostname}.mail>"
295
+ end
195
296
 
196
- def map_lines( str, &block )
197
- results = []
198
- str.each_line do |line|
199
- results << yield(line)
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))
200
304
  end
201
- results
202
305
  end
306
+ end
203
307
 
204
- def map_with_index( enum, &block )
205
- results = []
206
- enum.each_with_index do |token, i|
207
- 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))
208
315
  end
209
- results
210
316
  end
211
317
 
212
- else
318
+ private
213
319
 
214
- def map_lines( str, &block )
215
- 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)
216
330
  end
331
+ end
332
+
333
+ class << self
334
+ attr_accessor :charset_encoder
335
+ end
336
+ self.charset_encoder = BestEffortCharsetEncoder.new
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 }
343
+ end
344
+
345
+ def Utilities.paren( str )
346
+ str = ::Mail::Utilities.unparen( str )
347
+ str = escape_paren( str )
348
+ '(' + str + ')'
349
+ end
350
+
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
217
361
 
218
- def map_with_index( enum, &block )
219
- enum.each_with_index.map(&block)
362
+ def Utilities.decode_base64(str)
363
+ if !str.end_with?("=") && str.length % 4 != 0
364
+ str = str.ljust((str.length + 3) & ~3, "=")
220
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
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
221
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
406
+ end
222
407
  end
223
408
 
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)
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
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
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
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)
458
+ end
459
+
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.
472
+ #
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
+
529
+ else
530
+ charset
531
+ end
532
+
533
+ convert_to_encoding(encoding)
534
+ end
535
+
536
+ def Utilities.string_byteslice(str, *args)
537
+ str.byteslice(*args)
538
+ end
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
224
561
  end
225
562
  end
Binary file
data/lib/mail/version.rb CHANGED
@@ -1,24 +1,17 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
  module Mail
3
3
  module VERSION
4
-
5
- version = {}
6
- File.read(File.join(File.dirname(__FILE__), '../', '../', 'VERSION')).each_line do |line|
7
- type, value = line.chomp.split(":")
8
- next if type =~ /^\s+$/ || value =~ /^\s+$/
9
- version[type] = value
10
- end
11
-
12
- MAJOR = version['major']
13
- MINOR = version['minor']
14
- PATCH = version['patch']
15
- BUILD = version['build']
4
+
5
+ MAJOR = 2
6
+ MINOR = 8
7
+ PATCH = 1
8
+ BUILD = nil
16
9
 
17
10
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
18
-
11
+
19
12
  def self.version
20
13
  STRING
21
14
  end
22
-
15
+
23
16
  end
24
17
  end
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