mail 2.6.1 → 2.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.rdoc +8 -1
- data/README.md +5 -5
- data/lib/mail.rb +3 -2
- data/lib/mail/body.rb +28 -4
- data/lib/mail/{patterns.rb → constants.rb} +22 -4
- data/lib/mail/core_extensions/string.rb +5 -2
- data/lib/mail/elements/address.rb +49 -72
- data/lib/mail/elements/address_list.rb +1 -11
- data/lib/mail/encodings.rb +11 -26
- data/lib/mail/encodings/unix_to_unix.rb +17 -0
- data/lib/mail/field.rb +2 -2
- data/lib/mail/field_list.rb +1 -1
- data/lib/mail/fields/common/common_field.rb +2 -1
- data/lib/mail/fields/common/parameter_hash.rb +3 -3
- data/lib/mail/fields/unstructured_field.rb +3 -3
- data/lib/mail/header.rb +3 -2
- data/lib/mail/mail.rb +3 -1
- data/lib/mail/matchers/has_sent_mail.rb +2 -2
- data/lib/mail/message.rb +10 -5
- data/lib/mail/parsers/content_type_parser.rb +4 -2
- data/lib/mail/parsers/ragel/common.rl +2 -1
- data/lib/mail/parsers/ragel/ruby.rb +4 -3
- data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +1439 -1419
- data/lib/mail/utilities.rb +3 -3
- data/lib/mail/version.rb +7 -15
- data/lib/mail/version_specific/ruby_1_8.rb +2 -2
- data/lib/mail/version_specific/ruby_1_9.rb +48 -14
- metadata +36 -36
- data/VERSION +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3af499471587d4cb917ab992eb4bb45f19b9579e
|
4
|
+
data.tar.gz: f05e6aaf887b9a79b9252e7edc2dd74b81f7cb6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75bb13148f71ffbbef303ca23f01b0c6c8a14aeea255bd528ba3ac0545c056b75ff3a420dc0578c7f15efd894a3367072496121c8479aa8ddc78e376c115168f
|
7
|
+
data.tar.gz: 0f26c569a7c210d3ee57d95cf5011f8ff4941f5c1cd054ef804f018aa829bec44db3fdde7ab2fe1a89056cf81a5e6d618735caaaf9ccf4e8810e4295256f012e
|
data/CHANGELOG.rdoc
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
== HEAD
|
2
2
|
|
3
|
-
|
3
|
+
== Version 2.6.3 - Mon Nov 3 23:53 +1100 2014 Mikel Lindsaar <mikel@reinteractive.net>
|
4
|
+
|
5
|
+
* #796 support uu encoding (grosser)
|
6
|
+
|
7
|
+
== Version 2.6.2 (Unreleased) - Wed Oct 22 13:42 -0500 2014 Benjamin Fleischer <github@benjaminfleischer.com>
|
4
8
|
|
5
9
|
Performance:
|
10
|
+
* #681 - fewer hotspot object allocations (srawlins)
|
11
|
+
* #815 - autoload parsers for load-time speed and memory usage (grosser)
|
6
12
|
|
7
13
|
Bugs:
|
14
|
+
* #736 - Mail.new copes with non-UTF8 messages marked as UTF8 (jeremy)
|
8
15
|
|
9
16
|
== Version 2.6.1 - Sun Jun 8 15:34 +1100 2014 Mikel Lindsaar <mikel@reinteractive.net>
|
10
17
|
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ Built from my experience with TMail, it is designed to be a pure ruby
|
|
15
15
|
implementation that makes generating, sending and parsing emails a no
|
16
16
|
brainer.
|
17
17
|
|
18
|
-
It is also designed
|
18
|
+
It is also designed from the ground up to work with the more modern versions
|
19
19
|
of Ruby. This is because Ruby > 1.9 handles text encodings much more wonderfully
|
20
20
|
than Ruby 1.8.x and so these features have been taken full advantage of in this
|
21
21
|
library allowing Mail to handle a lot more messages more cleanly than TMail.
|
@@ -44,10 +44,10 @@ Compatibility
|
|
44
44
|
|
45
45
|
Every Mail commit is tested by Travis on the [following platforms](https://github.com/mikel/mail/blob/master/.travis.yml)
|
46
46
|
|
47
|
-
* ruby-1.8.7
|
48
|
-
* ruby-1.9.2
|
49
|
-
* ruby-1.9.3
|
50
|
-
* ruby-2.0.0
|
47
|
+
* ruby-1.8.7 [ i686 ]
|
48
|
+
* ruby-1.9.2 [ x86_64 ]
|
49
|
+
* ruby-1.9.3 [ x86_64 ]
|
50
|
+
* ruby-2.0.0 [ x86_64 ]
|
51
51
|
* ruby-2.1.2 [ x86_64 ]
|
52
52
|
* ruby-head [ x86_64 ]
|
53
53
|
* jruby [ x86_64 ]
|
data/lib/mail.rb
CHANGED
@@ -41,7 +41,7 @@ module Mail # :doc:
|
|
41
41
|
require 'mail/multibyte'
|
42
42
|
end
|
43
43
|
|
44
|
-
require 'mail/
|
44
|
+
require 'mail/constants'
|
45
45
|
require 'mail/utilities'
|
46
46
|
require 'mail/configuration'
|
47
47
|
|
@@ -76,13 +76,14 @@ module Mail # :doc:
|
|
76
76
|
|
77
77
|
require 'mail/envelope'
|
78
78
|
|
79
|
-
|
79
|
+
register_autoload :Parsers, "mail/parsers"
|
80
80
|
|
81
81
|
# Autoload header field elements and transfer encodings.
|
82
82
|
require 'mail/elements'
|
83
83
|
require 'mail/encodings'
|
84
84
|
require 'mail/encodings/base64'
|
85
85
|
require 'mail/encodings/quoted_printable'
|
86
|
+
require 'mail/encodings/unix_to_unix'
|
86
87
|
|
87
88
|
require 'mail/matchers/has_sent_mail'
|
88
89
|
|
data/lib/mail/body.rb
CHANGED
@@ -254,18 +254,19 @@ module Mail
|
|
254
254
|
@parts = Mail::PartsList.new[val]
|
255
255
|
end
|
256
256
|
end
|
257
|
-
|
257
|
+
|
258
258
|
def split!(boundary)
|
259
259
|
self.boundary = boundary
|
260
|
-
parts =
|
260
|
+
parts = extract_parts
|
261
|
+
|
261
262
|
# Make the preamble equal to the preamble (if any)
|
262
263
|
self.preamble = parts[0].to_s.strip
|
263
264
|
# Make the epilogue equal to the epilogue (if any)
|
264
|
-
self.epilogue = parts[-1].to_s.
|
265
|
+
self.epilogue = parts[-1].to_s.strip
|
265
266
|
parts[1...-1].to_a.each { |part| @parts << Mail::Part.new(part) }
|
266
267
|
self
|
267
268
|
end
|
268
|
-
|
269
|
+
|
269
270
|
def only_us_ascii?
|
270
271
|
!(raw_source =~ /[^\x01-\x7f]/)
|
271
272
|
end
|
@@ -275,6 +276,29 @@ module Mail
|
|
275
276
|
end
|
276
277
|
|
277
278
|
private
|
279
|
+
|
280
|
+
# split parts by boundary, ignore first part if empty, append final part when closing boundary was missing
|
281
|
+
def extract_parts
|
282
|
+
parts_regex = /
|
283
|
+
(?: # non-capturing group
|
284
|
+
\A | # start of string OR
|
285
|
+
\r\n # line break
|
286
|
+
)
|
287
|
+
(
|
288
|
+
--#{Regexp.escape(boundary || "")} # boundary delimiter
|
289
|
+
(?:--)? # with non-capturing optional closing
|
290
|
+
)
|
291
|
+
(?=\s*$) # lookahead matching zero or more spaces followed by line-ending
|
292
|
+
/x
|
293
|
+
parts = raw_source.split(parts_regex).each_slice(2).to_a
|
294
|
+
parts.each_with_index { |(part, _), index| parts.delete_at(index) if index > 0 && part.blank? }
|
295
|
+
|
296
|
+
if parts.size > 1
|
297
|
+
final_separator = parts[-2][1]
|
298
|
+
parts << [""] if final_separator != "--#{boundary}--"
|
299
|
+
end
|
300
|
+
parts.map(&:first)
|
301
|
+
end
|
278
302
|
|
279
303
|
def crlf_boundary
|
280
304
|
"\r\n--#{boundary}\r\n"
|
@@ -1,20 +1,20 @@
|
|
1
1
|
# encoding: us-ascii
|
2
2
|
module Mail
|
3
|
-
module
|
3
|
+
module Constants
|
4
4
|
white_space = %Q|\x9\x20|
|
5
5
|
text = %Q|\x1-\x8\xB\xC\xE-\x7f|
|
6
6
|
field_name = %Q|\x21-\x39\x3b-\x7e|
|
7
7
|
qp_safe = %Q|\x20-\x3c\x3e-\x7e|
|
8
|
-
|
8
|
+
|
9
9
|
aspecial = %Q|()<>[]:;@\\,."| # RFC5322
|
10
10
|
tspecial = %Q|()<>@,;:\\"/[]?=| # RFC2045
|
11
11
|
sp = %Q| |
|
12
12
|
control = %Q|\x00-\x1f\x7f-\xff|
|
13
|
-
|
13
|
+
|
14
14
|
if control.respond_to?(:force_encoding)
|
15
15
|
control = control.force_encoding(Encoding::BINARY)
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
CRLF = /\r\n/
|
19
19
|
WSP = /[#{white_space}]/
|
20
20
|
FWS = /#{CRLF}#{WSP}*/
|
@@ -33,5 +33,23 @@ module Mail
|
|
33
33
|
ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{sp}]/n
|
34
34
|
PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
|
35
35
|
TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{sp}]/n
|
36
|
+
ENCODED_VALUE = /\=\?[^?]+\?([QB])\?[^?]*?\?\=/mi
|
37
|
+
|
38
|
+
EMPTY = ''
|
39
|
+
SPACE = ' '
|
40
|
+
UNDERSCORE = '_'
|
41
|
+
HYPHEN = '-'
|
42
|
+
COLON = ':'
|
43
|
+
ASTERISK = '*'
|
44
|
+
CR = "\r"
|
45
|
+
LF = "\n"
|
46
|
+
CR_ENCODED = "=0D"
|
47
|
+
LF_ENCODED = "=0A"
|
48
|
+
CAPITAL_M = 'M'
|
49
|
+
EQUAL_LF = "=\n"
|
50
|
+
NULL_SENDER = '<>'
|
51
|
+
|
52
|
+
Q_VALUES = ['Q','q']
|
53
|
+
B_VALUES = ['B','b']
|
36
54
|
end
|
37
55
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
class String #:nodoc:
|
3
3
|
|
4
|
+
CRLF = "\r\n"
|
5
|
+
LF = "\n"
|
6
|
+
|
4
7
|
if RUBY_VERSION >= '1.9'
|
5
8
|
# This 1.9 only regex can save a reasonable amount of time (~20%)
|
6
9
|
# by not matching "\r\n" so the string is returned unchanged in
|
@@ -11,11 +14,11 @@ class String #:nodoc:
|
|
11
14
|
end
|
12
15
|
|
13
16
|
def to_crlf
|
14
|
-
to_str.gsub(CRLF_REGEX,
|
17
|
+
to_str.gsub(CRLF_REGEX, CRLF)
|
15
18
|
end
|
16
19
|
|
17
20
|
def to_lf
|
18
|
-
to_str.gsub(/\r\n|\r/,
|
21
|
+
to_str.gsub(/\r\n|\r/, LF)
|
19
22
|
end
|
20
23
|
|
21
24
|
unless String.instance_methods(false).map {|m| m.to_sym}.include?(:blank?)
|
@@ -3,15 +3,15 @@ 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
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
|
+
#
|
12
12
|
# Mail::Address requires a correctly formatted email address per RFC2822 or RFC822. It
|
13
13
|
# handles all obsolete versions including obsolete domain routing on the local part.
|
14
|
-
#
|
14
|
+
#
|
15
15
|
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
|
16
16
|
# a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
|
17
17
|
# a.address #=> 'mikel@test.lindsaar.net'
|
@@ -25,12 +25,11 @@ module Mail
|
|
25
25
|
if value.nil?
|
26
26
|
@parsed = false
|
27
27
|
@data = nil
|
28
|
-
return
|
29
28
|
else
|
30
29
|
parse(value)
|
31
30
|
end
|
32
31
|
end
|
33
|
-
|
32
|
+
|
34
33
|
# Returns the raw input of the passed in string, this is before it is passed
|
35
34
|
# by the parser.
|
36
35
|
def raw
|
@@ -47,37 +46,37 @@ module Mail
|
|
47
46
|
def format
|
48
47
|
parse unless @parsed
|
49
48
|
if @data.nil?
|
50
|
-
|
49
|
+
EMPTY
|
51
50
|
elsif display_name
|
52
|
-
[quote_phrase(display_name), "<#{address}>", format_comments].compact.join(
|
51
|
+
[quote_phrase(display_name), "<#{address}>", format_comments].compact.join(SPACE)
|
53
52
|
elsif address
|
54
|
-
[address, format_comments].compact.join(
|
53
|
+
[address, format_comments].compact.join(SPACE)
|
55
54
|
else
|
56
55
|
raw
|
57
56
|
end
|
58
57
|
end
|
59
58
|
|
60
|
-
# Returns the address that is in the address itself. That is, the
|
59
|
+
# Returns the address that is in the address itself. That is, the
|
61
60
|
# local@domain string, without any angle brackets or the like.
|
62
|
-
#
|
61
|
+
#
|
63
62
|
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
|
64
63
|
# a.address #=> 'mikel@test.lindsaar.net'
|
65
64
|
def address
|
66
65
|
parse unless @parsed
|
67
66
|
domain ? "#{local}@#{domain}" : local
|
68
67
|
end
|
69
|
-
|
68
|
+
|
70
69
|
# Provides a way to assign an address to an already made Mail::Address object.
|
71
|
-
#
|
70
|
+
#
|
72
71
|
# a = Address.new
|
73
72
|
# a.address = 'Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>'
|
74
73
|
# a.address #=> 'mikel@test.lindsaar.net'
|
75
74
|
def address=(value)
|
76
75
|
parse(value)
|
77
76
|
end
|
78
|
-
|
77
|
+
|
79
78
|
# Returns the display name of the email address passed in.
|
80
|
-
#
|
79
|
+
#
|
81
80
|
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
|
82
81
|
# a.display_name #=> 'Mikel Lindsaar'
|
83
82
|
def display_name
|
@@ -85,9 +84,9 @@ module Mail
|
|
85
84
|
@display_name ||= get_display_name
|
86
85
|
Encodings.decode_encode(@display_name.to_s, @output_type) if @display_name
|
87
86
|
end
|
88
|
-
|
87
|
+
|
89
88
|
# Provides a way to assign a display name to an already made Mail::Address object.
|
90
|
-
#
|
89
|
+
#
|
91
90
|
# a = Address.new
|
92
91
|
# a.address = 'mikel@test.lindsaar.net'
|
93
92
|
# a.display_name = 'Mikel Lindsaar'
|
@@ -98,7 +97,7 @@ module Mail
|
|
98
97
|
|
99
98
|
# Returns the local part (the left hand side of the @ sign in the email address) of
|
100
99
|
# the address
|
101
|
-
#
|
100
|
+
#
|
102
101
|
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
|
103
102
|
# a.local #=> 'mikel'
|
104
103
|
def local
|
@@ -108,47 +107,43 @@ module Mail
|
|
108
107
|
|
109
108
|
# Returns the domain part (the right hand side of the @ sign in the email address) of
|
110
109
|
# the address
|
111
|
-
#
|
110
|
+
#
|
112
111
|
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
|
113
112
|
# a.domain #=> 'test.lindsaar.net'
|
114
113
|
def domain
|
115
114
|
parse unless @parsed
|
116
115
|
strip_all_comments(get_domain) if get_domain
|
117
116
|
end
|
118
|
-
|
117
|
+
|
119
118
|
# Returns an array of comments that are in the email, or an empty array if there
|
120
119
|
# are no comments
|
121
|
-
#
|
120
|
+
#
|
122
121
|
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
|
123
122
|
# a.comments #=> ['My email address']
|
124
123
|
def comments
|
125
124
|
parse unless @parsed
|
126
|
-
|
127
|
-
nil
|
128
|
-
else
|
129
|
-
get_comments.map { |c| c.squeeze(" ") }
|
130
|
-
end
|
125
|
+
get_comments.map { |c| c.squeeze(SPACE) } unless get_comments.empty?
|
131
126
|
end
|
132
|
-
|
127
|
+
|
133
128
|
# Sometimes an address will not have a display name, but might have the name
|
134
129
|
# as a comment field after the address. This returns that name if it exists.
|
135
|
-
#
|
130
|
+
#
|
136
131
|
# a = Address.new('mikel@test.lindsaar.net (Mikel Lindsaar)')
|
137
132
|
# a.name #=> 'Mikel Lindsaar'
|
138
133
|
def name
|
139
134
|
parse unless @parsed
|
140
135
|
get_name
|
141
136
|
end
|
142
|
-
|
137
|
+
|
143
138
|
# Returns the format of the address, or returns nothing
|
144
|
-
#
|
139
|
+
#
|
145
140
|
# a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
|
146
141
|
# a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
|
147
142
|
def to_s
|
148
143
|
parse unless @parsed
|
149
144
|
format
|
150
145
|
end
|
151
|
-
|
146
|
+
|
152
147
|
# Shows the Address object basic details, including the Address
|
153
148
|
# a = Address.new('Mikel (My email) <mikel@test.lindsaar.net>')
|
154
149
|
# a.inspect #=> "#<Mail::Address:14184910 Address: |Mikel <mikel@test.lindsaar.net> (My email)| >"
|
@@ -156,43 +151,42 @@ module Mail
|
|
156
151
|
parse unless @parsed
|
157
152
|
"#<#{self.class}:#{self.object_id} Address: |#{to_s}| >"
|
158
153
|
end
|
159
|
-
|
154
|
+
|
160
155
|
def encoded
|
161
156
|
@output_type = :encode
|
162
157
|
format
|
163
158
|
end
|
164
|
-
|
159
|
+
|
165
160
|
def decoded
|
166
161
|
@output_type = :decode
|
167
162
|
format
|
168
163
|
end
|
169
164
|
|
165
|
+
def group
|
166
|
+
@data && @data.group
|
167
|
+
end
|
168
|
+
|
170
169
|
private
|
171
|
-
|
170
|
+
|
172
171
|
def parse(value = nil)
|
173
172
|
@parsed = true
|
173
|
+
@data = nil
|
174
174
|
|
175
175
|
case value
|
176
|
-
when NilClass
|
177
|
-
@data = nil
|
178
|
-
nil
|
179
176
|
when Mail::Parsers::AddressStruct
|
180
177
|
@data = value
|
181
178
|
when String
|
182
|
-
|
183
|
-
if value.blank?
|
184
|
-
@data = nil
|
185
|
-
else
|
179
|
+
unless value.blank?
|
186
180
|
address_list = Mail::Parsers::AddressListsParser.new.parse(value)
|
187
181
|
@data = address_list.addresses.first
|
188
182
|
end
|
189
183
|
end
|
190
184
|
end
|
191
|
-
|
185
|
+
|
192
186
|
def strip_all_comments(string)
|
193
187
|
unless comments.blank?
|
194
188
|
comments.each do |comment|
|
195
|
-
string = string.gsub("(#{comment})",
|
189
|
+
string = string.gsub("(#{comment})", EMPTY)
|
196
190
|
end
|
197
191
|
end
|
198
192
|
string.strip
|
@@ -202,53 +196,36 @@ module Mail
|
|
202
196
|
unless comments.blank?
|
203
197
|
comments.each do |comment|
|
204
198
|
if @data.domain && @data.domain.include?("(#{comment})")
|
205
|
-
value = value.gsub("(#{comment})",
|
199
|
+
value = value.gsub("(#{comment})", EMPTY)
|
206
200
|
end
|
207
201
|
end
|
208
202
|
end
|
209
203
|
value.to_s.strip
|
210
204
|
end
|
211
|
-
|
205
|
+
|
212
206
|
def get_display_name
|
213
207
|
if @data.display_name
|
214
208
|
str = strip_all_comments(@data.display_name.to_s)
|
215
|
-
elsif @data.comments
|
216
|
-
|
217
|
-
str = strip_domain_comments(format_comments)
|
218
|
-
else
|
219
|
-
str = nil
|
220
|
-
end
|
221
|
-
else
|
222
|
-
nil
|
223
|
-
end
|
224
|
-
|
225
|
-
if str.blank?
|
226
|
-
nil
|
227
|
-
else
|
228
|
-
str
|
209
|
+
elsif @data.comments && @data.domain
|
210
|
+
str = strip_domain_comments(format_comments)
|
229
211
|
end
|
212
|
+
|
213
|
+
str unless str.blank?
|
230
214
|
end
|
231
|
-
|
215
|
+
|
232
216
|
def get_name
|
233
217
|
if display_name
|
234
218
|
str = display_name
|
235
|
-
|
236
|
-
|
237
|
-
comment_text = comments.join(' ').squeeze(" ")
|
238
|
-
str = "(#{comment_text})"
|
239
|
-
end
|
219
|
+
elsif comments
|
220
|
+
str = "(#{comments.join(SPACE).squeeze(SPACE)})"
|
240
221
|
end
|
241
222
|
|
242
|
-
|
243
|
-
nil
|
244
|
-
else
|
245
|
-
unparen(str)
|
246
|
-
end
|
223
|
+
unparen(str) unless str.blank?
|
247
224
|
end
|
248
|
-
|
225
|
+
|
249
226
|
def format_comments
|
250
227
|
if comments
|
251
|
-
comment_text = comments.map {|c| escape_paren(c) }.join(
|
228
|
+
comment_text = comments.map {|c| escape_paren(c) }.join(SPACE).squeeze(SPACE)
|
252
229
|
@format_comments ||= "(#{comment_text})"
|
253
230
|
else
|
254
231
|
nil
|
@@ -262,7 +239,7 @@ module Mail
|
|
262
239
|
def get_domain
|
263
240
|
@data && @data.domain
|
264
241
|
end
|
265
|
-
|
242
|
+
|
266
243
|
def get_comments
|
267
244
|
@data && @data.comments
|
268
245
|
end
|