kbaum-mail 2.1.2.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 (114) hide show
  1. data/CHANGELOG.rdoc +289 -0
  2. data/README.rdoc +575 -0
  3. data/Rakefile +72 -0
  4. data/TODO.rdoc +19 -0
  5. data/lib/mail.rb +113 -0
  6. data/lib/mail/attachments_list.rb +76 -0
  7. data/lib/mail/body.rb +243 -0
  8. data/lib/mail/configuration.rb +69 -0
  9. data/lib/mail/core_extensions/nil.rb +11 -0
  10. data/lib/mail/core_extensions/string.rb +19 -0
  11. data/lib/mail/elements/address.rb +306 -0
  12. data/lib/mail/elements/address_list.rb +74 -0
  13. data/lib/mail/elements/content_disposition_element.rb +30 -0
  14. data/lib/mail/elements/content_location_element.rb +25 -0
  15. data/lib/mail/elements/content_transfer_encoding_element.rb +21 -0
  16. data/lib/mail/elements/content_type_element.rb +35 -0
  17. data/lib/mail/elements/date_time_element.rb +26 -0
  18. data/lib/mail/elements/envelope_from_element.rb +34 -0
  19. data/lib/mail/elements/message_ids_element.rb +29 -0
  20. data/lib/mail/elements/mime_version_element.rb +26 -0
  21. data/lib/mail/elements/phrase_list.rb +21 -0
  22. data/lib/mail/elements/received_element.rb +30 -0
  23. data/lib/mail/encodings/base64.rb +18 -0
  24. data/lib/mail/encodings/encodings.rb +201 -0
  25. data/lib/mail/encodings/quoted_printable.rb +26 -0
  26. data/lib/mail/envelope.rb +35 -0
  27. data/lib/mail/field.rb +219 -0
  28. data/lib/mail/field_list.rb +33 -0
  29. data/lib/mail/fields/bcc_field.rb +53 -0
  30. data/lib/mail/fields/cc_field.rb +52 -0
  31. data/lib/mail/fields/comments_field.rb +41 -0
  32. data/lib/mail/fields/common/address_container.rb +16 -0
  33. data/lib/mail/fields/common/common_address.rb +128 -0
  34. data/lib/mail/fields/common/common_date.rb +51 -0
  35. data/lib/mail/fields/common/common_field.rb +64 -0
  36. data/lib/mail/fields/common/common_message_id.rb +57 -0
  37. data/lib/mail/fields/common/parameter_hash.rb +39 -0
  38. data/lib/mail/fields/content_description_field.rb +19 -0
  39. data/lib/mail/fields/content_disposition_field.rb +60 -0
  40. data/lib/mail/fields/content_id_field.rb +63 -0
  41. data/lib/mail/fields/content_location_field.rb +42 -0
  42. data/lib/mail/fields/content_transfer_encoding_field.rb +45 -0
  43. data/lib/mail/fields/content_type_field.rb +175 -0
  44. data/lib/mail/fields/date_field.rb +53 -0
  45. data/lib/mail/fields/from_field.rb +53 -0
  46. data/lib/mail/fields/in_reply_to_field.rb +52 -0
  47. data/lib/mail/fields/keywords_field.rb +43 -0
  48. data/lib/mail/fields/message_id_field.rb +80 -0
  49. data/lib/mail/fields/mime_version_field.rb +54 -0
  50. data/lib/mail/fields/optional_field.rb +11 -0
  51. data/lib/mail/fields/received_field.rb +62 -0
  52. data/lib/mail/fields/references_field.rb +53 -0
  53. data/lib/mail/fields/reply_to_field.rb +53 -0
  54. data/lib/mail/fields/resent_bcc_field.rb +53 -0
  55. data/lib/mail/fields/resent_cc_field.rb +53 -0
  56. data/lib/mail/fields/resent_date_field.rb +33 -0
  57. data/lib/mail/fields/resent_from_field.rb +53 -0
  58. data/lib/mail/fields/resent_message_id_field.rb +32 -0
  59. data/lib/mail/fields/resent_sender_field.rb +60 -0
  60. data/lib/mail/fields/resent_to_field.rb +53 -0
  61. data/lib/mail/fields/return_path_field.rb +62 -0
  62. data/lib/mail/fields/sender_field.rb +65 -0
  63. data/lib/mail/fields/structured_field.rb +36 -0
  64. data/lib/mail/fields/subject_field.rb +15 -0
  65. data/lib/mail/fields/to_field.rb +53 -0
  66. data/lib/mail/fields/unstructured_field.rb +117 -0
  67. data/lib/mail/header.rb +235 -0
  68. data/lib/mail/mail.rb +194 -0
  69. data/lib/mail/message.rb +1780 -0
  70. data/lib/mail/network/delivery_methods/file_delivery.rb +40 -0
  71. data/lib/mail/network/delivery_methods/sendmail.rb +62 -0
  72. data/lib/mail/network/delivery_methods/smtp.rb +110 -0
  73. data/lib/mail/network/delivery_methods/test_mailer.rb +40 -0
  74. data/lib/mail/network/retriever_methods/imap.rb +31 -0
  75. data/lib/mail/network/retriever_methods/pop3.rb +149 -0
  76. data/lib/mail/parsers/address_lists.rb +61 -0
  77. data/lib/mail/parsers/address_lists.treetop +19 -0
  78. data/lib/mail/parsers/content_disposition.rb +369 -0
  79. data/lib/mail/parsers/content_disposition.treetop +46 -0
  80. data/lib/mail/parsers/content_location.rb +133 -0
  81. data/lib/mail/parsers/content_location.treetop +20 -0
  82. data/lib/mail/parsers/content_transfer_encoding.rb +179 -0
  83. data/lib/mail/parsers/content_transfer_encoding.treetop +25 -0
  84. data/lib/mail/parsers/content_type.rb +512 -0
  85. data/lib/mail/parsers/content_type.treetop +58 -0
  86. data/lib/mail/parsers/date_time.rb +111 -0
  87. data/lib/mail/parsers/date_time.treetop +11 -0
  88. data/lib/mail/parsers/envelope_from.rb +188 -0
  89. data/lib/mail/parsers/envelope_from.treetop +32 -0
  90. data/lib/mail/parsers/message_ids.rb +42 -0
  91. data/lib/mail/parsers/message_ids.treetop +15 -0
  92. data/lib/mail/parsers/mime_version.rb +141 -0
  93. data/lib/mail/parsers/mime_version.treetop +19 -0
  94. data/lib/mail/parsers/phrase_lists.rb +42 -0
  95. data/lib/mail/parsers/phrase_lists.treetop +15 -0
  96. data/lib/mail/parsers/received.rb +68 -0
  97. data/lib/mail/parsers/received.treetop +11 -0
  98. data/lib/mail/parsers/rfc2045.rb +406 -0
  99. data/lib/mail/parsers/rfc2045.treetop +35 -0
  100. data/lib/mail/parsers/rfc2822.rb +5081 -0
  101. data/lib/mail/parsers/rfc2822.treetop +410 -0
  102. data/lib/mail/parsers/rfc2822_obsolete.rb +3607 -0
  103. data/lib/mail/parsers/rfc2822_obsolete.treetop +241 -0
  104. data/lib/mail/part.rb +82 -0
  105. data/lib/mail/parts_list.rb +34 -0
  106. data/lib/mail/patterns.rb +43 -0
  107. data/lib/mail/utilities.rb +163 -0
  108. data/lib/mail/vendor/treetop.rb +4 -0
  109. data/lib/mail/version.rb +10 -0
  110. data/lib/mail/version_specific/ruby_1_8.rb +84 -0
  111. data/lib/mail/version_specific/ruby_1_9.rb +77 -0
  112. data/lib/tasks/corpus.rake +125 -0
  113. data/lib/tasks/treetop.rake +10 -0
  114. metadata +188 -0
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class ContentDispositionElement # :nodoc:
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize( string )
8
+ parser = Mail::ContentDispositionParser.new
9
+ if tree = parser.parse(cleaned(string))
10
+ @disposition_type = tree.disposition_type.text_value
11
+ @parameters = tree.parameters
12
+ else
13
+ raise Mail::Field::ParseError, "ContentDispositionElement can not parse |#{string}|\nReason was: #{parser.failure_reason}\n"
14
+ end
15
+ end
16
+
17
+ def disposition_type
18
+ @disposition_type
19
+ end
20
+
21
+ def parameters
22
+ @parameters
23
+ end
24
+
25
+ def cleaned(string)
26
+ string =~ /(.+);\s*$/ ? $1 : string
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class ContentLocationElement # :nodoc:
4
+
5
+ include Mail::Utilities
6
+
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, "ContentLocationElement can not parse |#{string}|\nReason was: #{parser.failure_reason}\n"
13
+ end
14
+ end
15
+
16
+ def location
17
+ @location
18
+ end
19
+
20
+ def to_s(*args)
21
+ location.to_s
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class ContentTransferEncodingElement
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize( string )
8
+ parser = Mail::ContentTransferEncodingParser.new
9
+ if tree = parser.parse(string.downcase)
10
+ @encoding = tree.encoding.text_value
11
+ else
12
+ raise Mail::Field::ParseError, "ContentTransferEncodingElement can not parse |#{string}|\nReason was: #{parser.failure_reason}\n"
13
+ end
14
+ end
15
+
16
+ def encoding
17
+ @encoding
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class ContentTypeElement # :nodoc:
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize( string )
8
+ parser = Mail::ContentTypeParser.new
9
+ if tree = parser.parse(cleaned(string))
10
+ @main_type = tree.main_type.text_value
11
+ @sub_type = tree.sub_type.text_value
12
+ @parameters = tree.parameters
13
+ else
14
+ raise Mail::Field::ParseError, "ContentTypeElement can not parse |#{string}|\nReason was: #{parser.failure_reason}\n"
15
+ end
16
+ end
17
+
18
+ def main_type
19
+ @main_type
20
+ end
21
+
22
+ def sub_type
23
+ @sub_type
24
+ end
25
+
26
+ def parameters
27
+ @parameters
28
+ end
29
+
30
+ def cleaned(string)
31
+ string =~ /(.+);\s*$/ ? $1 : string
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class DateTimeElement # :nodoc:
4
+
5
+ include Mail::Utilities
6
+
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, "DateTimeElement can not parse |#{string}|\nReason was: #{parser.failure_reason}\n"
14
+ end
15
+ end
16
+
17
+ def date_string
18
+ @date_string
19
+ end
20
+
21
+ def time_string
22
+ @time_string
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class EnvelopeFromElement
4
+
5
+ include Mail::Utilities
6
+
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, "EnvelopeFromElement can not parse |#{string}|\nReason was: #{parser.failure_reason}\n"
14
+ end
15
+ end
16
+
17
+ def tree
18
+ @tree
19
+ end
20
+
21
+ def date_time
22
+ @date_time
23
+ end
24
+
25
+ def address
26
+ @address
27
+ end
28
+
29
+ def to_s(*args)
30
+ "#{@info}; #{@date_time.to_s(*args)}"
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class MessageIdsElement
4
+
5
+ include Mail::Utilities
6
+
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, "MessageIdsElement can not parse |#{string}|\nReason was: #{parser.failure_reason}\n"
13
+ end
14
+ end
15
+
16
+ def message_ids
17
+ @message_ids
18
+ end
19
+
20
+ def message_id
21
+ @message_ids.first
22
+ end
23
+
24
+ def clean_msg_id( val )
25
+ val =~ /.*<(.*)>.*/ ; $1
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class MimeVersionElement
4
+
5
+ include Mail::Utilities
6
+
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, "MimeVersionElement can not parse |#{string}|\nReason was: #{parser.failure_reason}\n"
14
+ end
15
+ end
16
+
17
+ def major
18
+ @major
19
+ end
20
+
21
+ def minor
22
+ @minor
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class PhraseList
4
+
5
+ include Mail::Utilities
6
+
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, "PhraseList can not parse |#{string}|\nReason was: #{parser.failure_reason}\n"
13
+ end
14
+ end
15
+
16
+ def phrases
17
+ @phrases.map { |p| unquote(p.text_value) }
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class ReceivedElement
4
+
5
+ include Mail::Utilities
6
+
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, "ReceivedElement can not parse |#{string}|\nReason was: #{parser.failure_reason}\n"
14
+ end
15
+ end
16
+
17
+ def date_time
18
+ @date_time
19
+ end
20
+
21
+ def info
22
+ @info
23
+ end
24
+
25
+ def to_s(*args)
26
+ "#{@info}; #{@date_time.to_s(*args)}"
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ module Encodings
4
+ class Base64
5
+
6
+ # Decode the string from Base64
7
+ def self.decode(str)
8
+ RubyVer.decode_base64( str )
9
+ end
10
+
11
+ # Encode the string to Base64
12
+ def self.encode(str)
13
+ RubyVer.encode_base64( str )
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,201 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ # Raised when attempting to decode an unknown encoding type
4
+ class UnknownEncodingType < StandardError #:nodoc:
5
+ end
6
+
7
+ module Encodings
8
+
9
+ include Mail::Patterns
10
+
11
+ # Is the encoding we want defined?
12
+ #
13
+ # Example:
14
+ #
15
+ # Encodings.defined?(:base64) #=> true
16
+ def Encodings.defined?( str )
17
+ string = str.to_s.split(/[_-]/).map { |v| v.capitalize }.join('')
18
+ RubyVer.has_constant?(Mail::Encodings, string)
19
+ end
20
+
21
+ # Gets a defined encoding type, QuotedPrintable or Base64 for now.
22
+ #
23
+ # Each encoding needs to be defined as a Mail::Encodings::ClassName for
24
+ # this to work, allows us to add other encodings in the future.
25
+ #
26
+ # Example:
27
+ #
28
+ # Encodings.get_encoding(:base64) #=> Mail::Encodings::Base64
29
+ def Encodings.get_encoding( str )
30
+ string = str.to_s.split(/[_-]/).map { |v| v.capitalize }.join('')
31
+ RubyVer.get_constant(Mail::Encodings, string)
32
+ end
33
+
34
+ # Encodes a parameter value using URI Escaping, note the language field 'en' can
35
+ # be set using Mail::Configuration, like so:
36
+ #
37
+ # Mail.defaults.do
38
+ # param_encode_language 'jp'
39
+ # end
40
+ #
41
+ # The character set used for encoding will either be the value of $KCODE for
42
+ # Ruby < 1.9 or the encoding on the string passed in.
43
+ #
44
+ # Example:
45
+ #
46
+ # Mail::Encodings.param_encode("This is fun") #=> "us-ascii'en'This%20is%20fun"
47
+ def Encodings.param_encode(str)
48
+ case
49
+ when str.ascii_only? && str =~ TOKEN_UNSAFE
50
+ %Q{"#{str}"}
51
+ when str.ascii_only?
52
+ str
53
+ else
54
+ RubyVer.param_encode(str)
55
+ end
56
+ end
57
+
58
+ # Decodes a parameter value using URI Escaping.
59
+ #
60
+ # Example:
61
+ #
62
+ # Mail::Encodings.param_decode("This%20is%20fun", 'us-ascii') #=> "This is fun"
63
+ #
64
+ # str = Mail::Encodings.param_decode("This%20is%20fun", 'iso-8559-1')
65
+ # str.encoding #=> 'ISO-8859-1' ## Only on Ruby 1.9
66
+ # str #=> "This is fun"
67
+ def Encodings.param_decode(str, encoding)
68
+ RubyVer.param_decode(str, encoding)
69
+ end
70
+
71
+ # Decodes or encodes a string as needed for either Base64 or QP encoding types in
72
+ # the =?<encoding>?[QB]?<string>?=" format.
73
+ #
74
+ # The output type needs to be :decode to decode the input string or :encode to
75
+ # encode the input string. The character set used for encoding will either be
76
+ # the value of $KCODE for Ruby < 1.9 or the encoding on the string passed in.
77
+ #
78
+ # On encoding, will only send out Base64 encoded strings.
79
+ def Encodings.decode_encode(str, output_type)
80
+ case
81
+ when output_type == :decode
82
+ Encodings.value_decode(str)
83
+ else
84
+ if str.ascii_only?
85
+ str
86
+ else
87
+ Encodings.b_value_encode(str, find_encoding(str))
88
+ end
89
+ end
90
+ end
91
+
92
+ # Decodes a given string as Base64 or Quoted Printable, depending on what
93
+ # type it is.
94
+ #
95
+ # String has to be of the format =?<encoding>?[QB]?<string>?=
96
+ def Encodings.value_decode(str)
97
+ str.gsub!(/\?=(\s*)=\?/, '?==?') # Remove whitespaces between 'encoded-word's
98
+ str.gsub(/(.*?)(=\?.*?\?.\?.*?\?=)|$/m) do
99
+ before = $1.to_s
100
+ text = $2.to_s
101
+
102
+ case
103
+ when text =~ /=\?.+\?[Bb]\?/m
104
+ before + b_value_decode(text)
105
+ when text =~ /=\?.+\?[Qq]\?/m
106
+ before + q_value_decode(text)
107
+ else
108
+ before + text
109
+ end
110
+ end
111
+ end
112
+
113
+ # Takes an encoded string of the format =?<encoding>?[QB]?<string>?=
114
+ def Encodings.unquote_and_convert_to(str, to_encoding)
115
+ original_encoding, string = split_encoding_from_string( str )
116
+
117
+ output = value_decode( str ).to_s
118
+
119
+ if original_encoding.to_s.downcase.gsub("-", "") == to_encoding.to_s.downcase.gsub("-", "")
120
+ output
121
+ elsif original_encoding && to_encoding
122
+ begin
123
+ require 'iconv'
124
+ Iconv.iconv(to_encoding, original_encoding, output).first
125
+ rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
126
+ # the 'from' parameter specifies a charset other than what the text
127
+ # actually is...not much we can do in this case but just return the
128
+ # unconverted text.
129
+ #
130
+ # Ditto if either parameter represents an unknown charset, like
131
+ # X-UNKNOWN.
132
+ output
133
+ end
134
+ else
135
+ output
136
+ end
137
+ end
138
+
139
+ # Encode a string with Base64 Encoding and returns it ready to be inserted
140
+ # as a value for a field, that is, in the =?<charset>?B?<string>?= format
141
+ #
142
+ # Example:
143
+ #
144
+ # Encodings.b_value_encode('This is あ string', 'UTF-8')
145
+ # #=> "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?="
146
+ def Encodings.b_value_encode(str, encoding = nil)
147
+ string, encoding = RubyVer.b_value_encode(str, encoding)
148
+ string.split("\n").map do |str|
149
+ "=?#{encoding}?B?#{str.chomp}?="
150
+ end.join(" ")
151
+ end
152
+
153
+ # Encode a string with Quoted-Printable Encoding and returns it ready to be inserted
154
+ # as a value for a field, that is, in the =?<charset>?Q?<string>?= format
155
+ #
156
+ # Example:
157
+ #
158
+ # Encodings.q_value_encode('This is あ string', 'UTF-8')
159
+ # #=> "=?UTF-8?Q?This_is_=E3=81=82_string?="
160
+ def Encodings.q_value_encode(str, encoding = nil)
161
+ string, encoding = RubyVer.q_value_encode(str, encoding)
162
+ "=?#{encoding}?Q?#{string.chomp.gsub(/ /, '_')}?="
163
+ end
164
+
165
+ private
166
+
167
+ # Decodes a Base64 string from the "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=" format
168
+ #
169
+ # Example:
170
+ #
171
+ # Encodings.b_value_encode("=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=")
172
+ # #=> 'This is あ string'
173
+ def Encodings.b_value_decode(str)
174
+ RubyVer.b_value_decode(str)
175
+ end
176
+
177
+ # Decodes a Quoted-Printable string from the "=?UTF-8?Q?This_is_=E3=81=82_string?=" format
178
+ #
179
+ # Example:
180
+ #
181
+ # Encodings.b_value_encode("=?UTF-8?Q?This_is_=E3=81=82_string?=")
182
+ # #=> 'This is あ string'
183
+ def Encodings.q_value_decode(str)
184
+ RubyVer.q_value_decode(str).gsub(/_/, ' ')
185
+ end
186
+
187
+ def Encodings.split_encoding_from_string( str )
188
+ match = str.match(/\=\?(.+)?\?[QB]\?(.+)?\?\=/mi)
189
+ if match
190
+ [match[1], match[2]]
191
+ else
192
+ nil
193
+ end
194
+ end
195
+
196
+ def Encodings.find_encoding(str)
197
+ RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
198
+ end
199
+
200
+ end
201
+ end