mail 2.6.1 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +92 -80
  4. data/lib/mail/attachments_list.rb +11 -5
  5. data/lib/mail/body.rb +81 -44
  6. data/lib/mail/check_delivery_params.rb +50 -10
  7. data/lib/mail/configuration.rb +3 -0
  8. data/lib/mail/{patterns.rb → constants.rb} +26 -6
  9. data/lib/mail/core_extensions/smtp.rb +20 -16
  10. data/lib/mail/core_extensions/string.rb +1 -27
  11. data/lib/mail/elements/address.rb +81 -93
  12. data/lib/mail/elements/address_list.rb +12 -29
  13. data/lib/mail/elements/content_disposition_element.rb +9 -15
  14. data/lib/mail/elements/content_location_element.rb +8 -12
  15. data/lib/mail/elements/content_transfer_encoding_element.rb +6 -10
  16. data/lib/mail/elements/content_type_element.rb +9 -19
  17. data/lib/mail/elements/date_time_element.rb +7 -14
  18. data/lib/mail/elements/envelope_from_element.rb +15 -21
  19. data/lib/mail/elements/message_ids_element.rb +12 -14
  20. data/lib/mail/elements/mime_version_element.rb +7 -14
  21. data/lib/mail/elements/phrase_list.rb +7 -9
  22. data/lib/mail/elements/received_element.rb +10 -15
  23. data/lib/mail/elements.rb +1 -0
  24. data/lib/mail/encodings/7bit.rb +6 -15
  25. data/lib/mail/encodings/8bit.rb +5 -18
  26. data/lib/mail/encodings/base64.rb +15 -10
  27. data/lib/mail/encodings/binary.rb +4 -22
  28. data/lib/mail/encodings/identity.rb +24 -0
  29. data/lib/mail/encodings/quoted_printable.rb +13 -7
  30. data/lib/mail/encodings/transfer_encoding.rb +47 -28
  31. data/lib/mail/encodings/unix_to_unix.rb +20 -0
  32. data/lib/mail/encodings.rb +121 -82
  33. data/lib/mail/envelope.rb +2 -1
  34. data/lib/mail/field.rb +114 -62
  35. data/lib/mail/field_list.rb +2 -1
  36. data/lib/mail/fields/bcc_field.rb +17 -5
  37. data/lib/mail/fields/cc_field.rb +2 -2
  38. data/lib/mail/fields/comments_field.rb +2 -1
  39. data/lib/mail/fields/common/address_container.rb +3 -2
  40. data/lib/mail/fields/common/common_address.rb +40 -14
  41. data/lib/mail/fields/common/common_date.rb +2 -1
  42. data/lib/mail/fields/common/common_field.rb +6 -11
  43. data/lib/mail/fields/common/common_message_id.rb +3 -2
  44. data/lib/mail/fields/common/parameter_hash.rb +5 -4
  45. data/lib/mail/fields/content_description_field.rb +2 -1
  46. data/lib/mail/fields/content_disposition_field.rb +14 -13
  47. data/lib/mail/fields/content_id_field.rb +5 -4
  48. data/lib/mail/fields/content_location_field.rb +3 -2
  49. data/lib/mail/fields/content_transfer_encoding_field.rb +3 -2
  50. data/lib/mail/fields/content_type_field.rb +7 -11
  51. data/lib/mail/fields/date_field.rb +4 -4
  52. data/lib/mail/fields/from_field.rb +2 -2
  53. data/lib/mail/fields/in_reply_to_field.rb +2 -1
  54. data/lib/mail/fields/keywords_field.rb +3 -3
  55. data/lib/mail/fields/message_id_field.rb +3 -2
  56. data/lib/mail/fields/mime_version_field.rb +4 -3
  57. data/lib/mail/fields/optional_field.rb +5 -1
  58. data/lib/mail/fields/received_field.rb +5 -4
  59. data/lib/mail/fields/references_field.rb +2 -1
  60. data/lib/mail/fields/reply_to_field.rb +2 -2
  61. data/lib/mail/fields/resent_bcc_field.rb +2 -2
  62. data/lib/mail/fields/resent_cc_field.rb +2 -2
  63. data/lib/mail/fields/resent_date_field.rb +2 -2
  64. data/lib/mail/fields/resent_from_field.rb +2 -2
  65. data/lib/mail/fields/resent_message_id_field.rb +2 -1
  66. data/lib/mail/fields/resent_sender_field.rb +2 -2
  67. data/lib/mail/fields/resent_to_field.rb +2 -2
  68. data/lib/mail/fields/return_path_field.rb +2 -2
  69. data/lib/mail/fields/sender_field.rb +2 -2
  70. data/lib/mail/fields/structured_field.rb +1 -0
  71. data/lib/mail/fields/subject_field.rb +2 -1
  72. data/lib/mail/fields/to_field.rb +2 -2
  73. data/lib/mail/fields/unstructured_field.rb +28 -10
  74. data/lib/mail/fields.rb +1 -0
  75. data/lib/mail/header.rb +18 -14
  76. data/lib/mail/indifferent_hash.rb +1 -0
  77. data/lib/mail/mail.rb +6 -11
  78. data/lib/mail/matchers/attachment_matchers.rb +29 -0
  79. data/lib/mail/matchers/has_sent_mail.rb +53 -9
  80. data/lib/mail/message.rb +99 -89
  81. data/lib/mail/multibyte/chars.rb +32 -30
  82. data/lib/mail/multibyte/unicode.rb +31 -26
  83. data/lib/mail/multibyte/utils.rb +1 -0
  84. data/lib/mail/multibyte.rb +65 -15
  85. data/lib/mail/network/delivery_methods/exim.rb +7 -10
  86. data/lib/mail/network/delivery_methods/file_delivery.rb +5 -8
  87. data/lib/mail/network/delivery_methods/logger_delivery.rb +37 -0
  88. data/lib/mail/network/delivery_methods/sendmail.rb +17 -11
  89. data/lib/mail/network/delivery_methods/smtp.rb +60 -53
  90. data/lib/mail/network/delivery_methods/smtp_connection.rb +11 -6
  91. data/lib/mail/network/delivery_methods/test_mailer.rb +6 -8
  92. data/lib/mail/network/retriever_methods/base.rb +1 -0
  93. data/lib/mail/network/retriever_methods/imap.rb +19 -5
  94. data/lib/mail/network/retriever_methods/pop3.rb +4 -1
  95. data/lib/mail/network/retriever_methods/test_retriever.rb +2 -1
  96. data/lib/mail/network.rb +2 -0
  97. data/lib/mail/parser_tools.rb +15 -0
  98. data/lib/mail/parsers/address_lists_parser.rb +33208 -104
  99. data/lib/mail/parsers/address_lists_parser.rl +172 -0
  100. data/lib/mail/parsers/content_disposition_parser.rb +877 -49
  101. data/lib/mail/parsers/content_disposition_parser.rl +82 -0
  102. data/lib/mail/parsers/content_location_parser.rb +804 -23
  103. data/lib/mail/parsers/content_location_parser.rl +71 -0
  104. data/lib/mail/parsers/content_transfer_encoding_parser.rb +502 -19
  105. data/lib/mail/parsers/content_transfer_encoding_parser.rl +64 -0
  106. data/lib/mail/parsers/content_type_parser.rb +1024 -46
  107. data/lib/mail/parsers/content_type_parser.rl +83 -0
  108. data/lib/mail/parsers/date_time_parser.rb +872 -23
  109. data/lib/mail/parsers/date_time_parser.rl +62 -0
  110. data/lib/mail/parsers/envelope_from_parser.rb +3570 -34
  111. data/lib/mail/parsers/envelope_from_parser.rl +82 -0
  112. data/lib/mail/parsers/message_ids_parser.rb +2840 -25
  113. data/lib/mail/parsers/message_ids_parser.rl +82 -0
  114. data/lib/mail/parsers/mime_version_parser.rb +492 -26
  115. data/lib/mail/parsers/mime_version_parser.rl +61 -0
  116. data/lib/mail/parsers/phrase_lists_parser.rb +862 -17
  117. data/lib/mail/parsers/phrase_lists_parser.rl +83 -0
  118. data/lib/mail/parsers/received_parser.rb +8765 -36
  119. data/lib/mail/parsers/received_parser.rl +84 -0
  120. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  121. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  122. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  123. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  124. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  125. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  126. data/lib/mail/parsers/rfc5322.rl +59 -0
  127. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  128. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  129. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  130. data/lib/mail/parsers.rb +17 -24
  131. data/lib/mail/part.rb +8 -5
  132. data/lib/mail/parts_list.rb +31 -14
  133. data/lib/mail/utilities.rb +112 -13
  134. data/lib/mail/values/unicode_tables.dat +0 -0
  135. data/lib/mail/version.rb +8 -15
  136. data/lib/mail/version_specific/ruby_1_8.rb +52 -8
  137. data/lib/mail/version_specific/ruby_1_9.rb +143 -24
  138. data/lib/mail.rb +8 -14
  139. metadata +71 -81
  140. data/CHANGELOG.rdoc +0 -752
  141. data/CONTRIBUTING.md +0 -60
  142. data/Dependencies.txt +0 -2
  143. data/Gemfile +0 -15
  144. data/Rakefile +0 -29
  145. data/TODO.rdoc +0 -9
  146. data/VERSION +0 -4
  147. data/lib/mail/core_extensions/nil.rb +0 -19
  148. data/lib/mail/core_extensions/object.rb +0 -13
  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/multibyte/exceptions.rb +0 -8
  152. data/lib/mail/parsers/ragel/common.rl +0 -184
  153. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  154. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  155. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  156. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  157. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  158. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  159. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  160. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  161. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  162. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  163. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  164. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  165. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  166. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2129
  167. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  168. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  169. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  170. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  171. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  172. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  173. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  174. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  175. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  176. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  177. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  178. data/lib/mail/parsers/ragel/ruby.rb +0 -39
  179. data/lib/mail/parsers/ragel.rb +0 -17
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Mail
4
5
  # Raised when attempting to decode an unknown encoding type
@@ -6,8 +7,7 @@ module Mail
6
7
  end
7
8
 
8
9
  module Encodings
9
-
10
- include Mail::Patterns
10
+ include Mail::Constants
11
11
  extend Mail::Utilities
12
12
 
13
13
  @transfer_encodings = {}
@@ -18,7 +18,7 @@ module Mail
18
18
  #
19
19
  # Encodings.register "base64", Mail::Encodings::Base64
20
20
  def Encodings.register(name, cls)
21
- @transfer_encodings[get_name(name)] = cls
21
+ @transfer_encodings[get_name(name)] = cls
22
22
  end
23
23
 
24
24
  # Is the encoding we want defined?
@@ -26,8 +26,8 @@ module Mail
26
26
  # Example:
27
27
  #
28
28
  # Encodings.defined?(:base64) #=> true
29
- def Encodings.defined?( str )
30
- @transfer_encodings.include? get_name(str)
29
+ def Encodings.defined?(name)
30
+ @transfer_encodings.include? get_name(name)
31
31
  end
32
32
 
33
33
  # Gets a defined encoding type, QuotedPrintable or Base64 for now.
@@ -38,16 +38,24 @@ module Mail
38
38
  # Example:
39
39
  #
40
40
  # Encodings.get_encoding(:base64) #=> Mail::Encodings::Base64
41
- def Encodings.get_encoding( str )
42
- @transfer_encodings[get_name(str)]
41
+ def Encodings.get_encoding(name)
42
+ @transfer_encodings[get_name(name)]
43
43
  end
44
44
 
45
45
  def Encodings.get_all
46
46
  @transfer_encodings.values
47
47
  end
48
48
 
49
- def Encodings.get_name(enc)
50
- enc = enc.to_s.gsub("-", "_").downcase
49
+ def Encodings.get_name(name)
50
+ underscoreize(name).downcase
51
+ end
52
+
53
+ def Encodings.transcode_charset(str, from_charset, to_charset = 'UTF-8')
54
+ if from_charset
55
+ RubyVer.transcode_charset str, from_charset, to_charset
56
+ else
57
+ str
58
+ end
51
59
  end
52
60
 
53
61
  # Encodes a parameter value using URI Escaping, note the language field 'en' can
@@ -114,34 +122,19 @@ module Mail
114
122
  # String has to be of the format =?<encoding>?[QB]?<string>?=
115
123
  def Encodings.value_decode(str)
116
124
  # Optimization: If there's no encoded-words in the string, just return it
117
- return str unless str =~ /\=\?[^?]+\?[QB]\?[^?]+?\?\=/xmi
125
+ return str unless str =~ ENCODED_VALUE
118
126
 
119
127
  lines = collapse_adjacent_encodings(str)
120
128
 
121
129
  # Split on white-space boundaries with capture, so we capture the white-space as well
122
- lines.map do |line|
123
- line.split(/([ \t])/).map do |text|
124
- if text.index('=?').nil?
125
- text
126
- else
127
- # Search for occurences of quoted strings or plain strings
128
- text.scan(/( # Group around entire regex to include it in matches
129
- \=\?[^?]+\?([QB])\?[^?]+?\?\= # Quoted String with subgroup for encoding method
130
- | # or
131
- .+?(?=\=\?|$) # Plain String
132
- )/xmi).map do |matches|
133
- string, method = *matches
134
- if method == 'b' || method == 'B'
135
- b_value_decode(string)
136
- elsif method == 'q' || method == 'Q'
137
- q_value_decode(string)
138
- else
139
- string
140
- end
141
- end
130
+ lines.each do |line|
131
+ line.gsub!(ENCODED_VALUE) do |string|
132
+ case $2
133
+ when *B_VALUES then b_value_decode(string)
134
+ when *Q_VALUES then q_value_decode(string)
142
135
  end
143
136
  end
144
- end.flatten.join("")
137
+ end.join("")
145
138
  end
146
139
 
147
140
  # Takes an encoded string of the format =?<encoding>?[QB]?<string>?=
@@ -174,32 +167,51 @@ module Mail
174
167
 
175
168
  def Encodings.address_encode(address, charset = 'utf-8')
176
169
  if address.is_a?(Array)
177
- # loop back through for each element
178
170
  address.compact.map { |a| Encodings.address_encode(a, charset) }.join(", ")
179
- else
180
- # find any word boundary that is not ascii and encode it
181
- encode_non_usascii(address, charset) if address
171
+ elsif address
172
+ encode_non_usascii(address, charset)
182
173
  end
183
174
  end
184
175
 
185
176
  def Encodings.encode_non_usascii(address, charset)
186
177
  return address if address.ascii_only? or charset.nil?
187
- us_ascii = %Q{\x00-\x7f}
188
- # Encode any non usascii strings embedded inside of quotes
189
- address = address.gsub(/(".*?[^#{us_ascii}].*?")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
190
- # Then loop through all remaining items and encode as needed
191
- tokens = address.split(/\s/)
192
- map_with_index(tokens) do |word, i|
193
- if word.ascii_only?
194
- word
195
- else
196
- previous_non_ascii = i>0 && tokens[i-1] && !tokens[i-1].ascii_only?
197
- if previous_non_ascii #why are we adding an extra space here?
198
- word = " #{word}"
178
+
179
+ # With KCODE=u we can't use regexps on other encodings. Go ASCII.
180
+ with_ascii_kcode do
181
+ # Encode all strings embedded inside of quotes
182
+ address = address.gsub(/("[^"]*[^\/]")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
183
+
184
+ # Then loop through all remaining items and encode as needed
185
+ tokens = address.split(/\s/)
186
+
187
+ map_with_index(tokens) do |word, i|
188
+ if word.ascii_only?
189
+ word
190
+ else
191
+ previous_non_ascii = i>0 && tokens[i-1] && !tokens[i-1].ascii_only?
192
+ if previous_non_ascii #why are we adding an extra space here?
193
+ word = " #{word}"
194
+ end
195
+ Encodings.b_value_encode(word, charset)
199
196
  end
200
- Encodings.b_value_encode(word, charset)
197
+ end.join(' ')
198
+ end
199
+ end
200
+
201
+ if RUBY_VERSION < '1.9'
202
+ # With KCODE=u we can't use regexps on other encodings. Go ASCII.
203
+ def Encodings.with_ascii_kcode #:nodoc:
204
+ if $KCODE
205
+ $KCODE, original_kcode = '', $KCODE
201
206
  end
202
- end.join(' ')
207
+ yield
208
+ ensure
209
+ $KCODE = original_kcode if original_kcode
210
+ end
211
+ else
212
+ def Encodings.with_ascii_kcode #:nodoc:
213
+ yield
214
+ end
203
215
  end
204
216
 
205
217
  # Encode a string with Base64 Encoding and returns it ready to be inserted
@@ -209,12 +221,15 @@ module Mail
209
221
  #
210
222
  # Encodings.b_value_encode('This is あ string', 'UTF-8')
211
223
  # #=> "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?="
212
- def Encodings.b_value_encode(encoded_str, encoding = nil)
213
- return encoded_str if encoded_str.to_s.ascii_only?
214
- string, encoding = RubyVer.b_value_encode(encoded_str, encoding)
215
- map_lines(string) do |str|
216
- "=?#{encoding}?B?#{str.chomp}?="
217
- end.join(" ")
224
+ def Encodings.b_value_encode(string, encoding = nil)
225
+ if string.to_s.ascii_only?
226
+ string
227
+ else
228
+ Encodings.each_base64_chunk_byterange(string, 60).map do |chunk|
229
+ str, encoding = RubyVer.b_value_encode(chunk, encoding)
230
+ "=?#{encoding}?B?#{str.chomp}?="
231
+ end.join(" ")
232
+ end
218
233
  end
219
234
 
220
235
  # Encode a string with Quoted-Printable Encoding and returns it ready to be inserted
@@ -255,50 +270,74 @@ module Mail
255
270
  RubyVer.q_value_decode(str)
256
271
  end
257
272
 
258
- def Encodings.split_encoding_from_string( str )
259
- match = str.match(/\=\?([^?]+)?\?[QB]\?(.+)?\?\=/mi)
260
- if match
261
- match[1]
262
- else
263
- nil
264
- end
265
- end
266
-
267
273
  def Encodings.find_encoding(str)
268
274
  RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
269
275
  end
270
276
 
271
277
  # Gets the encoding type (Q or B) from the string.
272
- def Encodings.split_value_encoding_from_string(str)
273
- match = str.match(/\=\?[^?]+?\?([QB])\?(.+)?\?\=/mi)
274
- if match
275
- match[1]
276
- else
277
- nil
278
- end
278
+ def Encodings.value_encoding_from_string(str)
279
+ str[ENCODED_VALUE, 1]
279
280
  end
280
281
 
281
- # When the encoded string consists of multiple lines, lines with the same
282
- # encoding (Q or B) can be joined together.
282
+ # Split header line into proper encoded and unencoded parts.
283
283
  #
284
284
  # String has to be of the format =?<encoding>?[QB]?<string>?=
285
+ #
286
+ # Omit unencoded space after an encoded-word.
285
287
  def Encodings.collapse_adjacent_encodings(str)
286
- lines = str.split(/(\?=)\s*(=\?)/).each_slice(2).map(&:join)
287
288
  results = []
288
- previous_encoding = nil
289
+ last_encoded = nil # Track whether to preserve or drop whitespace
289
290
 
290
- lines.each do |line|
291
- encoding = split_value_encoding_from_string(line)
291
+ lines = str.split(FULL_ENCODED_VALUE)
292
+ lines.each_slice(2) do |unencoded, encoded|
293
+ if last_encoded = encoded
294
+ if !Utilities.blank?(unencoded) || (!last_encoded && unencoded != EMPTY)
295
+ results << unencoded
296
+ end
292
297
 
293
- if encoding == previous_encoding
294
- line = results.pop + line
298
+ results << encoded
299
+ else
300
+ results << unencoded
295
301
  end
296
-
297
- previous_encoding = encoding
298
- results << line
299
302
  end
300
303
 
301
304
  results
302
305
  end
306
+
307
+ # Partition the string into bounded-size chunks without splitting
308
+ # multibyte characters.
309
+ def Encodings.each_base64_chunk_byterange(str, max_bytesize_per_base64_chunk, &block)
310
+ raise "size per chunk must be multiple of 4" if (max_bytesize_per_base64_chunk % 4).nonzero?
311
+
312
+ if block_given?
313
+ max_bytesize = ((3 * max_bytesize_per_base64_chunk) / 4.0).floor
314
+ each_chunk_byterange(str, max_bytesize, &block)
315
+ else
316
+ enum_for :each_base64_chunk_byterange, str, max_bytesize_per_base64_chunk
317
+ end
318
+ end
319
+
320
+ # Partition the string into bounded-size chunks without splitting
321
+ # multibyte characters.
322
+ def Encodings.each_chunk_byterange(str, max_bytesize_per_chunk)
323
+ return enum_for(:each_chunk_byterange, str, max_bytesize_per_chunk) unless block_given?
324
+
325
+ offset = 0
326
+ chunksize = 0
327
+
328
+ str.each_char do |chr|
329
+ charsize = chr.bytesize
330
+
331
+ if chunksize + charsize > max_bytesize_per_chunk
332
+ yield RubyVer.string_byteslice(str, offset, chunksize)
333
+ offset += chunksize
334
+ chunksize = charsize
335
+ else
336
+ chunksize += charsize
337
+ end
338
+ end
339
+
340
+ yield RubyVer.string_byteslice(str, offset, chunksize)
341
+ end
303
342
  end
304
343
  end
data/lib/mail/envelope.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Mail Envelope
4
5
  #
@@ -11,7 +12,7 @@ module Mail
11
12
  class Envelope < StructuredField
12
13
 
13
14
  def initialize(*args)
14
- super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
15
+ super(FIELD_NAME, args.last.to_s)
15
16
  end
16
17
 
17
18
  def element
data/lib/mail/field.rb CHANGED
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  require 'mail/fields'
3
+ require 'mail/constants'
2
4
 
3
5
  # encoding: utf-8
4
6
  module Mail
@@ -82,9 +84,31 @@ module Mail
82
84
 
83
85
  def initialize(element, value, reason)
84
86
  @element = element
85
- @value = value
86
- @reason = reason
87
- super("#{element} can not parse |#{value}|\nReason was: #{reason}")
87
+ @value = to_utf8(value)
88
+ @reason = to_utf8(reason)
89
+ super("#{@element} can not parse |#{@value}|: #{@reason}")
90
+ end
91
+
92
+ private
93
+ def to_utf8(text)
94
+ if text.respond_to?(:force_encoding)
95
+ text.dup.force_encoding(Encoding::UTF_8)
96
+ else
97
+ text
98
+ end
99
+ end
100
+ end
101
+
102
+ class NilParseError < ParseError #:nodoc:
103
+ def initialize(element)
104
+ super element, nil, 'nil is invalid'
105
+ end
106
+ end
107
+
108
+ class IncompleteParseError < ParseError #:nodoc:
109
+ def initialize(element, original_text, unparsed_index)
110
+ parsed_text = to_utf8(original_text[0...unparsed_index])
111
+ super element, original_text, "Only able to parse up to #{parsed_text.inspect}"
88
112
  end
89
113
  end
90
114
 
@@ -92,41 +116,64 @@ module Mail
92
116
  class SyntaxError < FieldError #:nodoc:
93
117
  end
94
118
 
95
- # Accepts a string:
96
- #
97
- # Field.new("field-name: field data")
98
- #
99
- # Or name, value pair:
100
- #
101
- # Field.new("field-name", "value")
119
+ class << self
120
+ # Parse a field from a raw header line:
121
+ #
122
+ # Mail::Field.parse("field-name: field data")
123
+ # # => #<Mail::Field …>
124
+ def parse(field, charset = nil)
125
+ name, value = split(field)
126
+ if name && value
127
+ new name, value, charset
128
+ end
129
+ end
130
+
131
+ def split(raw_field) #:nodoc:
132
+ if raw_field.index(Constants::COLON)
133
+ name, value = raw_field.split(Constants::COLON, 2)
134
+ name.rstrip!
135
+ if name =~ /\A#{Constants::FIELD_NAME}\z/
136
+ [ name.rstrip, value.strip ]
137
+ else
138
+ Kernel.warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: invalid header name syntax: #{name.inspect}"
139
+ nil
140
+ end
141
+ else
142
+ raw_field.strip
143
+ end
144
+ rescue => error
145
+ warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: #{error.class}: #{error.message}"
146
+ nil
147
+ end
148
+ end
149
+
150
+ attr_reader :unparsed_value
151
+
152
+ # Create a field by name and optional value:
102
153
  #
103
- # Or a name by itself:
154
+ # Mail::Field.new("field-name", "value")
155
+ # # => #<Mail::Field …>
104
156
  #
105
- # Field.new("field-name")
157
+ # Values that aren't strings or arrays are coerced to Strings with `#to_s`.
106
158
  #
107
- # Note, does not want a terminating carriage return. Returns
108
- # self appropriately parsed. If value is not a string, then
109
- # it will be passed through as is, for example, content-type
110
- # field can accept an array with the type and a hash of
111
- # parameters:
159
+ # Mail::Field.new("field-name", 1234)
160
+ # # => #<Mail::Field …>
112
161
  #
113
- # Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
162
+ # Mail::Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
163
+ # # => #<Mail::Field …>
114
164
  def initialize(name, value = nil, charset = 'utf-8')
115
165
  case
116
- when name =~ /:/ # Field.new("field-name: field data")
117
- @charset = value.blank? ? charset : value
118
- @name = name[FIELD_PREFIX]
119
- @raw_value = name
120
- @value = nil
121
- when name !~ /:/ && value.blank? # Field.new("field-name")
166
+ when name.index(COLON)
167
+ Kernel.warn 'Passing an unparsed header field to Mail::Field.new is deprecated and will be removed in Mail 2.8.0. Use Mail::Field.parse instead.'
168
+ @name, @unparsed_value = self.class.split(name)
169
+ @charset = Utilities.blank?(value) ? charset : value
170
+ when Utilities.blank?(value)
122
171
  @name = name
123
- @value = nil
124
- @raw_value = nil
172
+ @unparsed_value = nil
125
173
  @charset = charset
126
- else # Field.new("field-name", "value")
174
+ else
127
175
  @name = name
128
- @value = value
129
- @raw_value = nil
176
+ @unparsed_value = value
130
177
  @charset = charset
131
178
  end
132
179
  @name = FIELD_NAME_MAP[@name.to_s.downcase] || @name
@@ -137,8 +184,7 @@ module Mail
137
184
  end
138
185
 
139
186
  def field
140
- _, @value = split(@raw_value) if @raw_value && !@value
141
- @field ||= create_field(@name, @value, @charset)
187
+ @field ||= create_field(@name, @unparsed_value, @charset)
142
188
  end
143
189
 
144
190
  def name
@@ -168,15 +214,19 @@ module Mail
168
214
  end
169
215
 
170
216
  def same( other )
217
+ return false unless other.kind_of?(self.class)
171
218
  match_to_s(other.name, self.name)
172
219
  end
173
220
 
221
+ def ==( other )
222
+ return false unless other.kind_of?(self.class)
223
+ match_to_s(other.name, self.name) && match_to_s(other.value, self.value)
224
+ end
225
+
174
226
  def responsible_for?( val )
175
227
  name.to_s.casecmp(val.to_s) == 0
176
228
  end
177
229
 
178
- alias_method :==, :same
179
-
180
230
  def <=>( other )
181
231
  self.field_order_id <=> other.field_order_id
182
232
  end
@@ -189,6 +239,16 @@ module Mail
189
239
  field.send(name, *args, &block)
190
240
  end
191
241
 
242
+ if RUBY_VERSION >= '1.9.2'
243
+ def respond_to_missing?(method_name, include_private)
244
+ field.respond_to?(method_name, include_private) || super
245
+ end
246
+ else
247
+ def respond_to?(method_name, include_private = false)
248
+ field.respond_to?(method_name, include_private) || super
249
+ end
250
+ end
251
+
192
252
  FIELD_ORDER = %w[ return-path received
193
253
  resent-date resent-from resent-sender resent-to
194
254
  resent-cc resent-bcc resent-message-id
@@ -202,11 +262,26 @@ module Mail
202
262
 
203
263
  private
204
264
 
205
- def split(raw_field)
206
- match_data = raw_field.mb_chars.match(FIELD_SPLIT)
207
- [match_data[1].to_s.mb_chars.strip, match_data[2].to_s.mb_chars.strip.to_s]
208
- rescue
209
- STDERR.puts "WARNING: Could not parse (and so ignoring) '#{raw_field}'"
265
+ def create_field(name, value, charset)
266
+ new_field(name, value, charset)
267
+ rescue Mail::Field::ParseError => e
268
+ field = Mail::UnstructuredField.new(name, value)
269
+ field.errors << [name, value, e]
270
+ field
271
+ end
272
+
273
+ def new_field(name, value, charset)
274
+ value = unfold(value) if value.is_a?(String)
275
+
276
+ if klass = field_class_for(name)
277
+ klass.new(value, charset)
278
+ else
279
+ OptionalField.new(name, value, charset)
280
+ end
281
+ end
282
+
283
+ def field_class_for(name)
284
+ FIELDS_MAP[name.to_s.downcase]
210
285
  end
211
286
 
212
287
  # 2.2.3. Long Header Fields
@@ -218,30 +293,7 @@ module Mail
218
293
  # treated in its unfolded form for further syntactic and semantic
219
294
  # evaluation.
220
295
  def unfold(string)
221
- string.gsub(/[\r\n \t]+/m, ' ')
222
- end
223
-
224
- def create_field(name, value, charset)
225
- value = unfold(value) if value.is_a?(String)
226
-
227
- begin
228
- new_field(name, value, charset)
229
- rescue Mail::Field::ParseError => e
230
- field = Mail::UnstructuredField.new(name, value)
231
- field.errors << [name, value, e]
232
- field
233
- end
234
- end
235
-
236
- def new_field(name, value, charset)
237
- lower_case_name = name.to_s.downcase
238
- if field_klass = FIELDS_MAP[lower_case_name]
239
- field_klass.new(value, charset)
240
- else
241
- OptionalField.new(name, value, charset)
242
- end
296
+ string.gsub(/#{Constants::CRLF}(#{Constants::WSP})/m, '\1')
243
297
  end
244
-
245
298
  end
246
-
247
299
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
 
4
5
  # Field List class provides an enhanced array that keeps a list of
@@ -19,7 +20,7 @@ module Mail
19
20
  hi = size
20
21
 
21
22
  while lo < hi
22
- mid = (lo + hi) / 2
23
+ mid = (lo + hi).div(2)
23
24
  if new_field < self[mid]
24
25
  hi = mid
25
26
  else
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Blind Carbon Copy Field
4
5
  #
@@ -36,16 +37,27 @@ module Mail
36
37
  FIELD_NAME = 'bcc'
37
38
  CAPITALIZED_FIELD = 'Bcc'
38
39
 
39
- def initialize(value = '', charset = 'utf-8')
40
+ def initialize(value = nil, charset = 'utf-8')
40
41
  @charset = charset
41
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
- self.parse
42
+ super(CAPITALIZED_FIELD, value, charset)
43
43
  self
44
44
  end
45
45
 
46
- # Bcc field should never be :encoded
46
+ def include_in_headers=(include_in_headers)
47
+ @include_in_headers = include_in_headers
48
+ end
49
+
50
+ def include_in_headers
51
+ defined?(@include_in_headers) ? @include_in_headers : self.include_in_headers = false
52
+ end
53
+
54
+ # Bcc field should not be :encoded by default
47
55
  def encoded
48
- ''
56
+ if include_in_headers
57
+ do_encode(CAPITALIZED_FIELD)
58
+ else
59
+ ''
60
+ end
49
61
  end
50
62
 
51
63
  def decoded
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Carbon Copy Field
4
5
  #
@@ -38,8 +39,7 @@ module Mail
38
39
 
39
40
  def initialize(value = nil, charset = 'utf-8')
40
41
  self.charset = charset
41
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
- self.parse
42
+ super(CAPITALIZED_FIELD, value, charset)
43
43
  self
44
44
  end
45
45
 
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Comments Field
4
5
  #
@@ -32,7 +33,7 @@ module Mail
32
33
 
33
34
  def initialize(value = nil, charset = 'utf-8')
34
35
  @charset = charset
35
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value))
36
+ super(CAPITALIZED_FIELD, value)
36
37
  self.parse
37
38
  self
38
39
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Mail
2
3
 
3
4
  class AddressContainer < Array
@@ -7,10 +8,10 @@ module Mail
7
8
  super(list)
8
9
  end
9
10
 
10
- def << (address)
11
+ def <<(address)
11
12
  @field << address
12
13
  end
13
14
 
14
15
  end
15
16
 
16
- end
17
+ end