mail 2.5.2 → 2.6.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 (151) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +78 -0
  3. data/CONTRIBUTING.md +17 -4
  4. data/Dependencies.txt +0 -1
  5. data/Gemfile +10 -21
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +50 -29
  8. data/Rakefile +11 -22
  9. data/VERSION +4 -0
  10. data/lib/mail/attachments_list.rb +2 -2
  11. data/lib/mail/body.rb +5 -5
  12. data/lib/mail/check_delivery_params.rb +12 -22
  13. data/lib/mail/core_extensions/object.rb +8 -8
  14. data/lib/mail/core_extensions/smtp.rb +12 -13
  15. data/lib/mail/core_extensions/string.rb +16 -6
  16. data/lib/mail/elements/address.rb +43 -79
  17. data/lib/mail/elements/address_list.rb +19 -42
  18. data/lib/mail/elements/content_disposition_element.rb +3 -7
  19. data/lib/mail/elements/content_location_element.rb +2 -6
  20. data/lib/mail/elements/content_transfer_encoding_element.rb +3 -10
  21. data/lib/mail/elements/content_type_element.rb +4 -8
  22. data/lib/mail/elements/date_time_element.rb +3 -7
  23. data/lib/mail/elements/envelope_from_element.rb +18 -13
  24. data/lib/mail/elements/message_ids_element.rb +1 -6
  25. data/lib/mail/elements/mime_version_element.rb +3 -7
  26. data/lib/mail/elements/phrase_list.rb +2 -7
  27. data/lib/mail/elements/received_element.rb +3 -7
  28. data/lib/mail/encodings/quoted_printable.rb +4 -3
  29. data/lib/mail/encodings.rb +60 -28
  30. data/lib/mail/envelope.rb +0 -5
  31. data/lib/mail/field.rb +63 -28
  32. data/lib/mail/field_list.rb +18 -18
  33. data/lib/mail/fields/bcc_field.rb +2 -2
  34. data/lib/mail/fields/cc_field.rb +2 -2
  35. data/lib/mail/fields/comments_field.rb +1 -1
  36. data/lib/mail/fields/common/common_address.rb +26 -21
  37. data/lib/mail/fields/common/common_date.rb +0 -7
  38. data/lib/mail/fields/common/common_field.rb +5 -5
  39. data/lib/mail/fields/content_id_field.rb +1 -2
  40. data/lib/mail/fields/content_transfer_encoding_field.rb +2 -8
  41. data/lib/mail/fields/content_type_field.rb +1 -1
  42. data/lib/mail/fields/date_field.rb +14 -14
  43. data/lib/mail/fields/from_field.rb +2 -2
  44. data/lib/mail/fields/in_reply_to_field.rb +2 -1
  45. data/lib/mail/fields/message_id_field.rb +2 -3
  46. data/lib/mail/fields/references_field.rb +2 -1
  47. data/lib/mail/fields/reply_to_field.rb +2 -2
  48. data/lib/mail/fields/resent_bcc_field.rb +2 -2
  49. data/lib/mail/fields/resent_cc_field.rb +2 -2
  50. data/lib/mail/fields/resent_from_field.rb +2 -2
  51. data/lib/mail/fields/resent_sender_field.rb +3 -3
  52. data/lib/mail/fields/resent_to_field.rb +2 -2
  53. data/lib/mail/fields/sender_field.rb +8 -8
  54. data/lib/mail/fields/to_field.rb +2 -2
  55. data/lib/mail/fields/unstructured_field.rb +8 -2
  56. data/lib/mail/header.rb +13 -23
  57. data/lib/mail/mail.rb +12 -0
  58. data/lib/mail/matchers/has_sent_mail.rb +34 -1
  59. data/lib/mail/message.rb +151 -48
  60. data/lib/mail/multibyte/chars.rb +2 -2
  61. data/lib/mail/multibyte/unicode.rb +6 -4
  62. data/lib/mail/network/delivery_methods/exim.rb +1 -6
  63. data/lib/mail/network/delivery_methods/file_delivery.rb +1 -1
  64. data/lib/mail/network/delivery_methods/sendmail.rb +32 -10
  65. data/lib/mail/network/delivery_methods/smtp.rb +31 -34
  66. data/lib/mail/network/delivery_methods/smtp_connection.rb +6 -6
  67. data/lib/mail/network/delivery_methods/test_mailer.rb +2 -2
  68. data/lib/mail/network/retriever_methods/imap.rb +18 -13
  69. data/lib/mail/parsers/address_lists_parser.rb +132 -0
  70. data/lib/mail/parsers/content_disposition_parser.rb +67 -0
  71. data/lib/mail/parsers/content_location_parser.rb +35 -0
  72. data/lib/mail/parsers/content_transfer_encoding_parser.rb +33 -0
  73. data/lib/mail/parsers/content_type_parser.rb +64 -0
  74. data/lib/mail/parsers/date_time_parser.rb +36 -0
  75. data/lib/mail/parsers/envelope_from_parser.rb +45 -0
  76. data/lib/mail/parsers/message_ids_parser.rb +39 -0
  77. data/lib/mail/parsers/mime_version_parser.rb +41 -0
  78. data/lib/mail/parsers/phrase_lists_parser.rb +33 -0
  79. data/lib/mail/parsers/ragel/common.rl +184 -0
  80. data/lib/mail/parsers/ragel/date_time.rl +30 -0
  81. data/lib/mail/parsers/ragel/parser_info.rb +61 -0
  82. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +14864 -0
  83. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +37 -0
  84. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +751 -0
  85. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +37 -0
  86. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +614 -0
  87. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +37 -0
  88. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +447 -0
  89. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +37 -0
  90. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +825 -0
  91. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +37 -0
  92. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +817 -0
  93. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +37 -0
  94. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +2129 -0
  95. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +37 -0
  96. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +1570 -0
  97. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +37 -0
  98. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +440 -0
  99. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +37 -0
  100. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +564 -0
  101. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +37 -0
  102. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +51 -0
  103. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +5144 -0
  104. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +37 -0
  105. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +37 -0
  106. data/lib/mail/parsers/ragel/ruby.rb +29 -0
  107. data/lib/mail/parsers/ragel.rb +17 -0
  108. data/lib/mail/parsers/received_parser.rb +47 -0
  109. data/lib/mail/parsers.rb +26 -0
  110. data/lib/mail/part.rb +6 -2
  111. data/lib/mail/parts_list.rb +5 -3
  112. data/lib/mail/patterns.rb +4 -1
  113. data/lib/mail/utilities.rb +28 -18
  114. data/lib/mail/version.rb +1 -1
  115. data/lib/mail/version_specific/ruby_1_8.rb +6 -2
  116. data/lib/mail/version_specific/ruby_1_9.rb +61 -21
  117. data/lib/mail.rb +1 -2
  118. metadata +112 -73
  119. data/lib/VERSION +0 -4
  120. data/lib/load_parsers.rb +0 -41
  121. data/lib/mail/core_extensions/shell_escape.rb +0 -56
  122. data/lib/mail/parsers/address_lists.rb +0 -64
  123. data/lib/mail/parsers/address_lists.treetop +0 -19
  124. data/lib/mail/parsers/content_disposition.rb +0 -535
  125. data/lib/mail/parsers/content_disposition.treetop +0 -46
  126. data/lib/mail/parsers/content_location.rb +0 -139
  127. data/lib/mail/parsers/content_location.treetop +0 -20
  128. data/lib/mail/parsers/content_transfer_encoding.rb +0 -162
  129. data/lib/mail/parsers/content_transfer_encoding.treetop +0 -20
  130. data/lib/mail/parsers/content_type.rb +0 -967
  131. data/lib/mail/parsers/content_type.treetop +0 -68
  132. data/lib/mail/parsers/date_time.rb +0 -114
  133. data/lib/mail/parsers/date_time.treetop +0 -11
  134. data/lib/mail/parsers/envelope_from.rb +0 -194
  135. data/lib/mail/parsers/envelope_from.treetop +0 -32
  136. data/lib/mail/parsers/message_ids.rb +0 -45
  137. data/lib/mail/parsers/message_ids.treetop +0 -15
  138. data/lib/mail/parsers/mime_version.rb +0 -144
  139. data/lib/mail/parsers/mime_version.treetop +0 -19
  140. data/lib/mail/parsers/phrase_lists.rb +0 -45
  141. data/lib/mail/parsers/phrase_lists.treetop +0 -15
  142. data/lib/mail/parsers/received.rb +0 -71
  143. data/lib/mail/parsers/received.treetop +0 -11
  144. data/lib/mail/parsers/rfc2045.rb +0 -464
  145. data/lib/mail/parsers/rfc2045.treetop +0 -36
  146. data/lib/mail/parsers/rfc2822.rb +0 -5397
  147. data/lib/mail/parsers/rfc2822.treetop +0 -410
  148. data/lib/mail/parsers/rfc2822_obsolete.rb +0 -3768
  149. data/lib/mail/parsers/rfc2822_obsolete.treetop +0 -241
  150. data/lib/tasks/corpus.rake +0 -125
  151. data/lib/tasks/treetop.rake +0 -10
@@ -1,11 +1,11 @@
1
1
  # encoding: utf-8
2
2
  module Mail
3
3
  class Address
4
-
4
+
5
5
  include Mail::Utilities
6
6
 
7
7
  # Mail::Address handles all email addresses in Mail. It takes an email address string
8
- # and parses it, breaking it down into it's component parts and allowing you to get the
8
+ # and parses it, breaking it down into its component parts and allowing you to get the
9
9
  # address, comments, display name, name, local part, domain part and fully formatted
10
10
  # address.
11
11
  #
@@ -21,26 +21,24 @@ module Mail
21
21
  # a.comments #=> ['My email address']
22
22
  # a.to_s #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
23
23
  def initialize(value = nil)
24
- @output_type = nil
25
- @tree = nil
26
- @raw_text = value
27
- case
28
- when value.nil?
24
+ @output_type = :decode
25
+ if value.nil?
29
26
  @parsed = false
27
+ @data = nil
30
28
  return
31
29
  else
32
30
  parse(value)
33
31
  end
34
32
  end
35
33
 
36
- # Returns the raw imput of the passed in string, this is before it is passed
34
+ # Returns the raw input of the passed in string, this is before it is passed
37
35
  # by the parser.
38
36
  def raw
39
- @raw_text
37
+ @data.raw
40
38
  end
41
39
 
42
40
  # Returns a correctly formatted address for the email going out. If given
43
- # an incorrectly formatted address as input, Mail::Address will do it's best
41
+ # an incorrectly formatted address as input, Mail::Address will do its best
44
42
  # to format it correctly. This includes quoting display names as needed and
45
43
  # putting the address in angle brackets etc.
46
44
  #
@@ -48,13 +46,14 @@ module Mail
48
46
  # a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
49
47
  def format
50
48
  parse unless @parsed
51
- case
52
- when tree.nil?
49
+ if @data.nil?
53
50
  ''
54
- when display_name
51
+ elsif display_name
55
52
  [quote_phrase(display_name), "<#{address}>", format_comments].compact.join(" ")
56
- else
53
+ elsif address
57
54
  [address, format_comments].compact.join(" ")
55
+ else
56
+ raw
58
57
  end
59
58
  end
60
59
 
@@ -104,7 +103,7 @@ module Mail
104
103
  # a.local #=> 'mikel'
105
104
  def local
106
105
  parse unless @parsed
107
- "#{obs_domain_list}#{get_local.strip}" if get_local
106
+ "#{@data.obs_domain_list}#{get_local.strip}" if get_local
108
107
  end
109
108
 
110
109
  # Returns the domain part (the right hand side of the @ sign in the email address) of
@@ -172,29 +171,24 @@ module Mail
172
171
 
173
172
  def parse(value = nil)
174
173
  @parsed = true
175
- case
176
- when value.nil?
174
+
175
+ case value
176
+ when NilClass
177
+ @data = nil
177
178
  nil
178
- when value.class == String
179
- self.tree = Mail::AddressList.new(value).address_nodes.first
180
- else
181
- self.tree = value
179
+ when Mail::Parsers::AddressStruct
180
+ @data = value
181
+ when String
182
+ @raw_text = value
183
+ if value.blank?
184
+ @data = nil
185
+ else
186
+ address_list = Mail::Parsers::AddressListsParser.new.parse(value)
187
+ @data = address_list.addresses.first
188
+ end
182
189
  end
183
190
  end
184
191
 
185
-
186
- def get_domain
187
- if tree.respond_to?(:angle_addr) && tree.angle_addr.respond_to?(:addr_spec) && tree.angle_addr.addr_spec.respond_to?(:domain)
188
- @domain_text ||= tree.angle_addr.addr_spec.domain.text_value.strip
189
- elsif tree.respond_to?(:domain)
190
- @domain_text ||= tree.domain.text_value.strip
191
- elsif tree.respond_to?(:addr_spec) && tree.addr_spec.respond_to?(:domain)
192
- tree.addr_spec.domain.text_value.strip
193
- else
194
- nil
195
- end
196
- end
197
-
198
192
  def strip_all_comments(string)
199
193
  unless comments.blank?
200
194
  comments.each do |comment|
@@ -207,7 +201,7 @@ module Mail
207
201
  def strip_domain_comments(value)
208
202
  unless comments.blank?
209
203
  comments.each do |comment|
210
- if get_domain && get_domain.include?("(#{comment})")
204
+ if @data.domain && @data.domain.include?("(#{comment})")
211
205
  value = value.gsub("(#{comment})", '')
212
206
  end
213
207
  end
@@ -215,20 +209,11 @@ module Mail
215
209
  value.to_s.strip
216
210
  end
217
211
 
218
- def get_comments
219
- if tree.respond_to?(:comments)
220
- @comments = tree.comments.map { |c| unparen(c.text_value.to_str) }
221
- else
222
- @comments = []
223
- end
224
- end
225
-
226
212
  def get_display_name
227
- if tree.respond_to?(:display_name)
228
- name = unquote(tree.display_name.text_value.strip)
229
- str = strip_all_comments(name.to_s)
230
- elsif comments
231
- if domain
213
+ if @data.display_name
214
+ str = strip_all_comments(@data.display_name.to_s)
215
+ elsif @data.comments
216
+ if @data.domain
232
217
  str = strip_domain_comments(format_comments)
233
218
  else
234
219
  str = nil
@@ -261,15 +246,6 @@ module Mail
261
246
  end
262
247
  end
263
248
 
264
- # Provides access to the Treetop parse tree for this address
265
- def tree
266
- @tree
267
- end
268
-
269
- def tree=(value)
270
- @tree = value
271
- end
272
-
273
249
  def format_comments
274
250
  if comments
275
251
  comment_text = comments.map {|c| escape_paren(c) }.join(' ').squeeze(" ")
@@ -278,29 +254,17 @@ module Mail
278
254
  nil
279
255
  end
280
256
  end
281
-
282
- def obs_domain_list
283
- if tree.respond_to?(:angle_addr)
284
- obs = tree.angle_addr.elements.select { |e| e.respond_to?(:obs_domain_list) }
285
- !obs.empty? ? obs.first.text_value : nil
286
- else
287
- nil
288
- end
289
- end
290
-
257
+
291
258
  def get_local
292
- case
293
- when tree.respond_to?(:local_dot_atom_text)
294
- tree.local_dot_atom_text.text_value
295
- when tree.respond_to?(:angle_addr) && tree.angle_addr.respond_to?(:addr_spec) && tree.angle_addr.addr_spec.respond_to?(:local_part)
296
- tree.angle_addr.addr_spec.local_part.text_value
297
- when tree.respond_to?(:addr_spec) && tree.addr_spec.respond_to?(:local_part)
298
- tree.addr_spec.local_part.text_value
299
- else
300
- tree && tree.respond_to?(:local_part) ? tree.local_part.text_value : nil
301
- end
259
+ @data && @data.local
260
+ end
261
+
262
+ def get_domain
263
+ @data && @data.domain
302
264
  end
303
265
 
304
-
266
+ def get_comments
267
+ @data && @data.comments
268
+ end
305
269
  end
306
- end
270
+ end
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
  module Mail
3
3
  class AddressList # :nodoc:
4
-
4
+
5
5
  # Mail::AddressList is the class that parses To, From and other address fields from
6
6
  # emails passed into Mail.
7
7
  #
@@ -18,57 +18,34 @@ module Mail
18
18
  # a.addresses #=> [#<Mail::Address:14943130 Address: |ada@test.lindsaar.net...
19
19
  # a.group_names #=> ["My Group"]
20
20
  def initialize(string)
21
- if string.blank?
22
- @address_nodes = []
23
- return self
24
- end
25
- parser = Mail::AddressListsParser.new
26
- if tree = parser.parse(string)
27
- @address_nodes = tree.addresses
28
- else
29
- raise Mail::Field::ParseError.new(AddressListsParser, string, parser.failure_reason)
30
- end
21
+ @addresses_grouped_by_group = nil
22
+ @address_list = Parsers::AddressListsParser.new.parse(string)
31
23
  end
32
24
 
33
25
  # Returns a list of address objects from the parsed line
34
26
  def addresses
35
- @addresses ||= get_addresses.map do |address_tree|
36
- Mail::Address.new(address_tree)
27
+ @addresses ||= @address_list.addresses.map do |address_data|
28
+ Mail::Address.new(address_data)
37
29
  end
38
30
  end
39
-
40
- # Returns a list of all recipient syntax trees that are not part of a group
41
- def individual_recipients # :nodoc:
42
- @individual_recipients ||= @address_nodes - group_recipients
43
- end
44
-
45
- # Returns a list of all recipient syntax trees that are part of a group
46
- def group_recipients # :nodoc:
47
- @group_recipients ||= @address_nodes.select { |an| an.respond_to?(:group_name) }
31
+
32
+ def addresses_grouped_by_group
33
+ return @addresses_grouped_by_group if @addresses_grouped_by_group
34
+
35
+ @addresses_grouped_by_group = {}
36
+
37
+ @address_list.addresses.each do |address_data|
38
+ if group = address_data.group
39
+ @addresses_grouped_by_group[group] ||= []
40
+ @addresses_grouped_by_group[group] << Mail::Address.new(address_data)
41
+ end
42
+ end
43
+ @addresses_grouped_by_group
48
44
  end
49
45
 
50
46
  # Returns the names as an array of strings of all groups
51
47
  def group_names # :nodoc:
52
- group_recipients.map { |g| g.group_name.text_value }
53
- end
54
-
55
- # Returns a list of address syntax trees
56
- def address_nodes # :nodoc:
57
- @address_nodes
58
- end
59
-
60
- private
61
-
62
- def get_addresses
63
- (individual_recipients + group_recipients.map { |g| get_group_addresses(g) }).flatten
64
- end
65
-
66
- def get_group_addresses(g)
67
- if g.group_list.respond_to?(:addresses)
68
- g.group_list.addresses
69
- else
70
- []
71
- end
48
+ @address_list.group_names
72
49
  end
73
50
  end
74
51
  end
@@ -5,13 +5,9 @@ module Mail
5
5
  include Mail::Utilities
6
6
 
7
7
  def initialize( string )
8
- parser = Mail::ContentDispositionParser.new
9
- if tree = parser.parse(cleaned(string))
10
- @disposition_type = tree.disposition_type.text_value.downcase
11
- @parameters = tree.parameters
12
- else
13
- raise Mail::Field::ParseError.new(ContentDispositionElement, string, parser.failure_reason)
14
- end
8
+ content_disposition = Mail::Parsers::ContentDispositionParser.new.parse(cleaned(string))
9
+ @disposition_type = content_disposition.disposition_type
10
+ @parameters = content_disposition.parameters
15
11
  end
16
12
 
17
13
  def disposition_type
@@ -5,12 +5,8 @@ module Mail
5
5
  include Mail::Utilities
6
6
 
7
7
  def initialize( string )
8
- parser = Mail::ContentLocationParser.new
9
- if tree = parser.parse(string)
10
- @location = tree.location.text_value
11
- else
12
- raise Mail::Field::ParseError.new(ContentLocationElement, string, parser.failure_reason)
13
- end
8
+ content_location = Mail::Parsers::ContentLocationParser.new.parse(string)
9
+ @location = content_location.location
14
10
  end
15
11
 
16
12
  def location
@@ -4,16 +4,9 @@ module Mail
4
4
 
5
5
  include Mail::Utilities
6
6
 
7
- def initialize( string )
8
- parser = Mail::ContentTransferEncodingParser.new
9
- case
10
- when string.blank?
11
- @encoding = ''
12
- when tree = parser.parse(string.to_s.downcase)
13
- @encoding = tree.encoding.text_value
14
- else
15
- raise Mail::Field::ParseError.new(ContentTransferEncodingElement, string, parser.failure_reason)
16
- end
7
+ def initialize(string)
8
+ content_transfer_encoding = Mail::Parsers::ContentTransferEncodingParser.new.parse(string)
9
+ @encoding = content_transfer_encoding.encoding
17
10
  end
18
11
 
19
12
  def encoding
@@ -5,14 +5,10 @@ module Mail
5
5
  include Mail::Utilities
6
6
 
7
7
  def initialize( string )
8
- parser = Mail::ContentTypeParser.new
9
- if tree = parser.parse(cleaned(string))
10
- @main_type = tree.main_type.text_value.downcase
11
- @sub_type = tree.sub_type.text_value.downcase
12
- @parameters = tree.parameters
13
- else
14
- raise Mail::Field::ParseError.new(ContentTypeElement, string, parser.failure_reason)
15
- end
8
+ content_type = Mail::Parsers::ContentTypeParser.new.parse(cleaned(string))
9
+ @main_type = content_type.main_type
10
+ @sub_type = content_type.sub_type
11
+ @parameters = content_type.parameters
16
12
  end
17
13
 
18
14
  def main_type
@@ -5,13 +5,9 @@ module Mail
5
5
  include Mail::Utilities
6
6
 
7
7
  def initialize( string )
8
- parser = Mail::DateTimeParser.new
9
- if tree = parser.parse(string)
10
- @date_string = tree.date.text_value
11
- @time_string = tree.time.text_value
12
- else
13
- raise Mail::Field::ParseError.new(DateTimeElement, string, parser.failure_reason)
14
- end
8
+ date_time = Mail::Parsers::DateTimeParser.new.parse(string)
9
+ @date_string = date_time.date_string
10
+ @time_string = date_time.time_string
15
11
  end
16
12
 
17
13
  def date_string
@@ -5,17 +5,9 @@ module Mail
5
5
  include Mail::Utilities
6
6
 
7
7
  def initialize( string )
8
- parser = Mail::EnvelopeFromParser.new
9
- if @tree = parser.parse(string)
10
- @address = tree.addr_spec.text_value.strip
11
- @date_time = ::DateTime.parse("#{tree.ctime_date.text_value}")
12
- else
13
- raise Mail::Field::ParseError.new(EnvelopeFromElement, string, parser.failure_reason)
14
- end
15
- end
16
-
17
- def tree
18
- @tree
8
+ @envelope_from = Mail::Parsers::EnvelopeFromParser.new.parse(string)
9
+ @address = @envelope_from.address
10
+ @date_time = ::DateTime.parse(@envelope_from.ctime_date)
19
11
  end
20
12
 
21
13
  def date_time
@@ -26,8 +18,21 @@ module Mail
26
18
  @address
27
19
  end
28
20
 
29
- def to_s(*args)
30
- "#{@info}; #{@date_time.to_s(*args)}"
21
+ # RFC 4155:
22
+ # a timestamp indicating the UTC date and time when the message
23
+ # was originally received, conformant with the syntax of the
24
+ # traditional UNIX 'ctime' output sans timezone (note that the
25
+ # use of UTC precludes the need for a timezone indicator);
26
+ def formatted_date_time
27
+ if @date_time.respond_to?(:ctime)
28
+ @date_time.ctime
29
+ else
30
+ @date_time.strftime '%a %b %e %T %Y'
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ "#{@address} #{formatted_date_time}"
31
36
  end
32
37
 
33
38
  end
@@ -5,12 +5,7 @@ module Mail
5
5
  include Mail::Utilities
6
6
 
7
7
  def initialize(string)
8
- parser = Mail::MessageIdsParser.new
9
- if tree = parser.parse(string)
10
- @message_ids = tree.message_ids.map { |msg_id| clean_msg_id(msg_id.text_value) }
11
- else
12
- raise Mail::Field::ParseError.new(MessageIdsElement, string, parser.failure_reason)
13
- end
8
+ @message_ids = Mail::Parsers::MessageIdsParser.new.parse(string).message_ids.map { |msg_id| clean_msg_id(msg_id) }
14
9
  end
15
10
 
16
11
  def message_ids
@@ -5,13 +5,9 @@ module Mail
5
5
  include Mail::Utilities
6
6
 
7
7
  def initialize( string )
8
- parser = Mail::MimeVersionParser.new
9
- if tree = parser.parse(string)
10
- @major = tree.major.text_value
11
- @minor = tree.minor.text_value
12
- else
13
- raise Mail::Field::ParseError.new(MimeVersionElement, string, parser.failure_reason)
14
- end
8
+ mime_version = Mail::Parsers::MimeVersionParser.new.parse(string)
9
+ @major = mime_version.major
10
+ @minor = mime_version.minor
15
11
  end
16
12
 
17
13
  def major
@@ -5,16 +5,11 @@ module Mail
5
5
  include Mail::Utilities
6
6
 
7
7
  def initialize(string)
8
- parser = Mail::PhraseListsParser.new
9
- if tree = parser.parse(string)
10
- @phrases = tree.phrases
11
- else
12
- raise Mail::Field::ParseError.new(PhraseList, string, parser.failure_reason)
13
- end
8
+ @phrase_lists = Mail::Parsers::PhraseListsParser.new.parse(string)
14
9
  end
15
10
 
16
11
  def phrases
17
- @phrases.map { |p| unquote(p.text_value) }
12
+ @phrase_lists.phrases.map { |p| unquote(p) }
18
13
  end
19
14
 
20
15
  end
@@ -5,13 +5,9 @@ module Mail
5
5
  include Mail::Utilities
6
6
 
7
7
  def initialize( string )
8
- parser = Mail::ReceivedParser.new
9
- if tree = parser.parse(string)
10
- @date_time = ::DateTime.parse("#{tree.date_time.date.text_value} #{tree.date_time.time.text_value}")
11
- @info = tree.name_val_list.text_value
12
- else
13
- raise Mail::Field::ParseError.new(ReceivedElement, string, parser.failure_reason)
14
- end
8
+ received = Mail::Parsers::ReceivedParser.new.parse(string)
9
+ @date_time = ::DateTime.parse("#{received.date} #{received.time}")
10
+ @info = received.info
15
11
  end
16
12
 
17
13
  def date_time
@@ -12,13 +12,14 @@ module Mail
12
12
  EightBit.can_encode? str
13
13
  end
14
14
 
15
- # Decode the string from Quoted-Printable
15
+ # Decode the string from Quoted-Printable. Cope with hard line breaks
16
+ # that were incorrectly encoded as hex instead of literal CRLF.
16
17
  def self.decode(str)
17
- str.unpack("M*").first
18
+ str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first.to_lf
18
19
  end
19
20
 
20
21
  def self.encode(str)
21
- [str].pack("M").to_crlf
22
+ [str.to_lf].pack("M").to_crlf
22
23
  end
23
24
 
24
25
  def self.cost(str)
@@ -53,7 +53,7 @@ module Mail
53
53
  # Encodes a parameter value using URI Escaping, note the language field 'en' can
54
54
  # be set using Mail::Configuration, like so:
55
55
  #
56
- # Mail.defaults.do
56
+ # Mail.defaults do
57
57
  # param_encode_language 'jp'
58
58
  # end
59
59
  #
@@ -114,41 +114,40 @@ module Mail
114
114
  # String has to be of the format =?<encoding>?[QB]?<string>?=
115
115
  def Encodings.value_decode(str)
116
116
  # Optimization: If there's no encoded-words in the string, just return it
117
- return str unless str.index("=?")
117
+ return str unless str =~ /\=\?[^?]+\?[QB]\?[^?]+?\?\=/xmi
118
118
 
119
- str = str.gsub(/\?=(\s*)=\?/, '?==?') # Remove whitespaces between 'encoded-word's
119
+ lines = collapse_adjacent_encodings(str)
120
120
 
121
121
  # Split on white-space boundaries with capture, so we capture the white-space as well
122
- str.split(/([ \t])/).map do |text|
123
- if text.index('=?') .nil?
124
- text
125
- else
126
- # Join QP encoded-words that are adjacent to avoid decoding partial chars
127
- text.gsub!(/\?\=\=\?.+?\?[Qq]\?/m, '') if text =~ /\?==\?/
128
-
129
- # Search for occurences of quoted strings or plain strings
130
- text.scan(/( # Group around entire regex to include it in matches
131
- \=\?[^?]+\?([QB])\?[^?]+?\?\= # Quoted String with subgroup for encoding method
132
- | # or
133
- .+?(?=\=\?|$) # Plain String
134
- )/xmi).map do |matches|
135
- string, method = *matches
136
- if method == 'b' || method == 'B'
137
- b_value_decode(string)
138
- elsif method == 'q' || method == 'Q'
139
- q_value_decode(string)
140
- else
141
- string
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
142
141
  end
143
142
  end
144
143
  end
145
- end.join("")
144
+ end.flatten.join("")
146
145
  end
147
146
 
148
147
  # Takes an encoded string of the format =?<encoding>?[QB]?<string>?=
149
148
  def Encodings.unquote_and_convert_to(str, to_encoding)
150
149
  output = value_decode( str ).to_s # output is already converted to UTF-8
151
-
150
+
152
151
  if 'utf8' == to_encoding.to_s.downcase.gsub("-", "")
153
152
  output
154
153
  elsif to_encoding
@@ -157,7 +156,7 @@ module Mail
157
156
  output.encode(to_encoding)
158
157
  else
159
158
  require 'iconv'
160
- Iconv.iconv(to_encoding, 'UTF-8', output).first
159
+ Iconv.iconv(to_encoding, 'UTF-8', output).first
161
160
  end
162
161
  rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
163
162
  # the 'from' parameter specifies a charset other than what the text
@@ -194,8 +193,8 @@ module Mail
194
193
  if word.ascii_only?
195
194
  word
196
195
  else
197
- previous_non_ascii = tokens[i-1] && !tokens[i-1].ascii_only?
198
- if previous_non_ascii
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?
199
198
  word = " #{word}"
200
199
  end
201
200
  Encodings.b_value_encode(word, charset)
@@ -268,5 +267,38 @@ module Mail
268
267
  def Encodings.find_encoding(str)
269
268
  RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
270
269
  end
270
+
271
+ # 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
279
+ end
280
+
281
+ # When the encoded string consists of multiple lines, lines with the same
282
+ # encoding (Q or B) can be joined together.
283
+ #
284
+ # String has to be of the format =?<encoding>?[QB]?<string>?=
285
+ def Encodings.collapse_adjacent_encodings(str)
286
+ lines = str.split(/(\?=)\s*(=\?)/).each_slice(2).map(&:join)
287
+ results = []
288
+ previous_encoding = nil
289
+
290
+ lines.each do |line|
291
+ encoding = split_value_encoding_from_string(line)
292
+
293
+ if encoding == previous_encoding
294
+ line = results.pop + line
295
+ end
296
+
297
+ previous_encoding = encoding
298
+ results << line
299
+ end
300
+
301
+ results
302
+ end
271
303
  end
272
304
  end
data/lib/mail/envelope.rb CHANGED
@@ -14,11 +14,6 @@ module Mail
14
14
  super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
15
15
  end
16
16
 
17
- def tree
18
- @element ||= Mail::EnvelopeFromElement.new(value)
19
- @tree ||= @element.tree
20
- end
21
-
22
17
  def element
23
18
  @element ||= Mail::EnvelopeFromElement.new(value)
24
19
  end