net-imap 0.4.6 → 0.4.8

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.

Potentially problematic release.


This version of net-imap might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9a3148dcf057ea22dbb67a976576ca59060038a8352fc1a71f3ec4cd928289b
4
- data.tar.gz: 38e9bbc72d9d90bad28b24f47305d6376c6b17e41233af747bfd3cae27cdfc2e
3
+ metadata.gz: b90ea893e6f831943d2d0412ff068c7a6603ce3bf723900ea7401a9e045f724b
4
+ data.tar.gz: '087c861278ce56a7369780aa27d77e56ed6d94907a4116a3a4fd9707f327d57a'
5
5
  SHA512:
6
- metadata.gz: f5f9f4ade6c25741a402ea78c81928ab455c9b8c6bff5998ae884d567257cb823a16195aa148abc7d9215aa9d0bdce3632904e0f921f061174b04ccdfd4abf9c
7
- data.tar.gz: 683fe61b95035387e6f651565174f6499a0f68e712287e904318f05e23fc24f195035a4c29eaeedbeb42a24401e7f2884f8e75af359d623daf098da402f8aaea
6
+ metadata.gz: 5fa157a8887d5ca2b97b75756b5414d5973667299ae3dbdb340b432eae05cfdb2994969ea2dab68b595f2eae66bd50d19fb814196fb991af1bfc285dfa5b3d6f
7
+ data.tar.gz: 7b173943ff66f97b5b22795752a34f7338228e6ef5a4dca43b293f5e3153f90d34b7039fd24c76399b1f460e77ffe422dd3691466b0952e3631f2406bab70854
@@ -27,7 +27,7 @@ jobs:
27
27
  bundler-cache: true
28
28
  - name: Setup Pages
29
29
  id: pages
30
- uses: actions/configure-pages@v3
30
+ uses: actions/configure-pages@v4
31
31
  - name: Build with RDoc
32
32
  run: bundle exec rake rdoc
33
33
  - name: Upload artifact
@@ -43,4 +43,4 @@ jobs:
43
43
  steps:
44
44
  - name: Deploy to GitHub Pages
45
45
  id: deployment
46
- uses: actions/deploy-pages@v2
46
+ uses: actions/deploy-pages@v3
@@ -3,6 +3,7 @@
3
3
  module Net
4
4
  class IMAP < Protocol
5
5
  autoload :FetchData, "#{__dir__}/fetch_data"
6
+ autoload :SearchResult, "#{__dir__}/search_result"
6
7
  autoload :SequenceSet, "#{__dir__}/sequence_set"
7
8
 
8
9
  # Net::IMAP::ContinuationRequest represents command continuation requests.
@@ -205,6 +206,7 @@ module Net
205
206
  # defines them. When unknown response code data is encountered, #data
206
207
  # will return an unparsed string.
207
208
  #
209
+ # ==== +IMAP4rev1+ Response Codes
208
210
  # See [IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501]] {§7.1, "Server
209
211
  # Responses - Status
210
212
  # Responses"}[https://www.rfc-editor.org/rfc/rfc3501#section-7.1] for full
@@ -228,13 +230,32 @@ module Net
228
230
  # {§2.3.1.1, "Unique Identifier (UID) Message
229
231
  # Attribute}[https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1].
230
232
  # * +UIDVALIDITY+, #data is an Integer, the UID validity value of the
231
- # mailbox See [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501]],
233
+ # mailbox. See [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501]],
232
234
  # {§2.3.1.1, "Unique Identifier (UID) Message
233
235
  # Attribute}[https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1].
234
236
  # * +UNSEEN+, #data is an Integer, the number of messages which do not have
235
237
  # the <tt>\Seen</tt> flag set.
236
- #
237
- # See RFC5530[https://www.rfc-editor.org/rfc/rfc5530], "IMAP Response
238
+ # <em>DEPRECATED by IMAP4rev2.</em>
239
+ #
240
+ # ==== +BINARY+ extension
241
+ # See {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516].
242
+ # * +UNKNOWN-CTE+, with a tagged +NO+ response, when the server does not
243
+ # known how to decode a CTE (content-transfer-encoding). #data is +nil+.
244
+ # See IMAP#fetch.
245
+ #
246
+ # ==== +UIDPLUS+ extension
247
+ # See {[RFC4315 §3]}[https://www.rfc-editor.org/rfc/rfc4315#section-3].
248
+ # * +APPENDUID+, #data is UIDPlusData. See IMAP#append.
249
+ # * +COPYUID+, #data is UIDPlusData. See IMAP#copy.
250
+ # * +UIDNOTSTICKY+, #data is +nil+. See IMAP#select.
251
+ #
252
+ # ==== +SEARCHRES+ extension
253
+ # See {[RFC5182]}[https://www.rfc-editor.org/rfc/rfc5182].
254
+ # * +NOTSAVED+, with a tagged +NO+ response, when the search result variable
255
+ # is not saved. #data is +nil+.
256
+ #
257
+ # ==== +RFC5530+ Response Codes
258
+ # See {[RFC5530]}[https://www.rfc-editor.org/rfc/rfc5530], "IMAP Response
238
259
  # Codes" for the definition of the following response codes, which are all
239
260
  # machine-readable annotations for the human-readable ResponseText#text, and
240
261
  # have +nil+ #data of their own:
@@ -256,9 +277,34 @@ module Net
256
277
  # * +ALREADYEXISTS+
257
278
  # * +NONEXISTENT+
258
279
  #
259
- # Other supported \IMAP extension response codes:
260
- # * +OBJECTID+ {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html#section-7]
261
- # * +MAILBOXID+, #data will be a string
280
+ # ==== +QRESYNC+ extension
281
+ # See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
282
+ # * +CLOSED+, returned when the currently selected mailbox is closed
283
+ # implicity by selecting or examining another mailbox. #data is +nil+.
284
+ #
285
+ # ==== +IMAP4rev2+ Response Codes
286
+ # See {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051] {§7.1, "Server
287
+ # Responses - Status
288
+ # Responses"}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1] for full
289
+ # descriptions of IMAP4rev2 response codes. IMAP4rev2 includes all of the
290
+ # response codes listed above (except "UNSEEN") and adds the following:
291
+ # * +HASCHILDREN+, with a tagged +NO+ response, when a mailbox delete failed
292
+ # because the server doesn't allow deletion of mailboxes with children.
293
+ # #data is +nil+.
294
+ #
295
+ # ==== +CONDSTORE+ extension
296
+ # See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
297
+ # * +NOMODSEQ+, when selecting a mailbox that does not support
298
+ # mod-sequences. #data is +nil+. See IMAP#select.
299
+ # * +HIGHESTMODSEQ+, #data is an Integer, the highest mod-sequence value of
300
+ # all messages in the mailbox. See IMAP#select.
301
+ # * +MODIFIED+, #data is a SequenceSet, the messages that have been modified
302
+ # since the +UNCHANGEDSINCE+ mod-sequence given to +STORE+ or <tt>UID
303
+ # STORE</tt>.
304
+ #
305
+ # ==== +OBJECTID+ extension
306
+ # See {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
307
+ # * +MAILBOXID+, #data is a string
262
308
  #
263
309
  class ResponseCode < Struct.new(:name, :data)
264
310
  ##
@@ -728,6 +774,19 @@ module Net
728
774
  #
729
775
  # An array of Net::IMAP::ThreadMember objects for mail items that are
730
776
  # children of this in the thread.
777
+
778
+ # Returns a SequenceSet containing #seqno and all #children's seqno,
779
+ # recursively.
780
+ def to_sequence_set
781
+ SequenceSet.new all_seqnos
782
+ end
783
+
784
+ protected
785
+
786
+ def all_seqnos(node = self)
787
+ [node.seqno].concat node.children.flat_map { _1.all_seqnos }
788
+ end
789
+
731
790
  end
732
791
 
733
792
  # Net::IMAP::BodyStructure is included by all of the structs that can be
@@ -231,6 +231,10 @@ module Net
231
231
  FLAG_PERM_LIST = /\G\((#{FLAG_PERM}(?:#{SP}#{FLAG_PERM})*|)\)/ni
232
232
  MBX_LIST_FLAGS = /\G (#{MBX_FLAG }(?:#{SP}#{MBX_FLAG })*) /nix
233
233
 
234
+ # Gmail allows SP and "]" in flags.......
235
+ QUIRKY_FLAG = Regexp.union(/\\?#{ASTRING_CHARS}/n, "\\*")
236
+ QUIRKY_FLAGS_LIST = /\G\(( [^)]* )\)/nx
237
+
234
238
  # RFC3501:
235
239
  # QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
236
240
  # "\" quoted-specials
@@ -464,7 +468,7 @@ module Net
464
468
  def sequence_set
465
469
  str = combine_adjacent(*SEQUENCE_SET_TOKENS)
466
470
  if Patterns::SEQUENCE_SET_STR.match?(str)
467
- SequenceSet.new(str)
471
+ SequenceSet[str]
468
472
  else
469
473
  parse_error("unexpected atom %p, expected sequence-set", str)
470
474
  end
@@ -978,7 +982,7 @@ module Net
978
982
  # env-bcc = "(" 1*address ")" / nil
979
983
  def nlist__address
980
984
  return if NIL?
981
- lpar; list = [address]; list << address until rpar?
985
+ lpar; list = [address]; list << address until (quirky_SP?; rpar?)
982
986
  list
983
987
  end
984
988
 
@@ -989,6 +993,12 @@ module Net
989
993
  alias env_cc nlist__address
990
994
  alias env_bcc nlist__address
991
995
 
996
+ # Used when servers erroneously send an extra SP.
997
+ #
998
+ # As of 2023-11-28, Outlook.com (still) sends SP
999
+ # between +address+ in <tt>env-*</tt> lists.
1000
+ alias quirky_SP? SP?
1001
+
992
1002
  # date-time = DQUOTE date-day-fixed "-" date-month "-" date-year
993
1003
  # SP time SP zone DQUOTE
994
1004
  alias date_time quoted
@@ -1360,7 +1370,7 @@ module Net
1360
1370
  MailboxList.new(attr, delim, name)
1361
1371
  end
1362
1372
 
1363
- def getquota_response
1373
+ def quota_response
1364
1374
  # If quota never established, get back
1365
1375
  # `NO Quota root does not exist'.
1366
1376
  # If quota removed, get `()' after the
@@ -1393,7 +1403,7 @@ module Net
1393
1403
  end
1394
1404
  end
1395
1405
 
1396
- def getquotaroot_response
1406
+ def quotaroot_response
1397
1407
  # Similar to getquota, but only admin can use getquota.
1398
1408
  token = match(T_ATOM)
1399
1409
  name = token.value.upcase
@@ -1452,34 +1462,16 @@ module Net
1452
1462
  # mailbox-data = obsolete-search-response / ...
1453
1463
  # obsolete-search-response = "SEARCH" *(SP nz-number)
1454
1464
  def mailbox_data__search
1455
- token = match(T_ATOM)
1456
- name = token.value.upcase
1457
- token = lookahead
1458
- if token.symbol == T_SPACE
1459
- shift_token
1460
- data = []
1461
- while true
1462
- token = lookahead
1463
- case token.symbol
1464
- when T_CRLF
1465
- break
1466
- when T_SPACE
1467
- shift_token
1468
- when T_NUMBER
1469
- data.push(number)
1470
- when T_LPAR
1471
- # TODO: include the MODSEQ value in a response
1472
- shift_token
1473
- match(T_ATOM)
1474
- match(T_SPACE)
1475
- match(T_NUMBER)
1476
- match(T_RPAR)
1477
- end
1478
- end
1479
- else
1480
- data = []
1465
+ name = label_in("SEARCH", "SORT")
1466
+ data = []
1467
+ while _ = SP? && nz_number? do data << _ end
1468
+ if lpar?
1469
+ label("MODSEQ"); SP!
1470
+ modseq = mod_sequence_value
1471
+ rpar
1481
1472
  end
1482
- return UntaggedResponse.new(name, data, @str)
1473
+ data = SearchResult.new(data, modseq: modseq)
1474
+ UntaggedResponse.new(name, data, @str)
1483
1475
  end
1484
1476
  alias sort_data mailbox_data__search
1485
1477
 
@@ -1603,6 +1595,7 @@ module Net
1603
1595
  when "UIDVALIDITY" then nz_number # RFC3501, RFC9051
1604
1596
  when "RECENT" then number # RFC3501 (obsolete)
1605
1597
  when "SIZE" then number64 # RFC8483, RFC9051
1598
+ when "HIGHESTMODSEQ" then mod_sequence_valzer # RFC7162
1606
1599
  when "MAILBOXID" then parens__objectid # RFC8474
1607
1600
  else
1608
1601
  number? || ExtensionData.new(tagged_ext_val)
@@ -1809,6 +1802,8 @@ module Net
1809
1802
  # resp-text-code =/ "HIGHESTMODSEQ" SP mod-sequence-value /
1810
1803
  # "NOMODSEQ" /
1811
1804
  # "MODIFIED" SP sequence-set
1805
+ # RFC7162 (QRESYNC):
1806
+ # resp-text-code =/ "CLOSED"
1812
1807
  #
1813
1808
  # RFC8474: OBJECTID
1814
1809
  # resp-text-code =/ "MAILBOXID" SP "(" objectid ")"
@@ -1830,7 +1825,9 @@ module Net
1830
1825
  "EXPUNGEISSUED", "CORRUPTION", "SERVERBUG", "CLIENTBUG", "CANNOT",
1831
1826
  "LIMIT", "OVERQUOTA", "ALREADYEXISTS", "NONEXISTENT", "CLOSED",
1832
1827
  "NOTSAVED", "UIDNOTSTICKY", "UNKNOWN-CTE", "HASCHILDREN"
1833
- when "NOMODSEQ" # CONDSTORE
1828
+ when "NOMODSEQ" then nil # CONDSTORE
1829
+ when "HIGHESTMODSEQ" then SP!; mod_sequence_value # CONDSTORE
1830
+ when "MODIFIED" then SP!; sequence_set # CONDSTORE
1834
1831
  when "MAILBOXID" then SP!; parens__objectid # RFC8474: OBJECTID
1835
1832
  else
1836
1833
  SP? and text_chars_except_rbra
@@ -1914,22 +1911,35 @@ module Net
1914
1911
 
1915
1912
  # flag-list = "(" [flag *(SP flag)] ")"
1916
1913
  def flag_list
1917
- match_re(Patterns::FLAG_LIST, "flag-list")[1]
1918
- .split(nil)
1919
- .map! { _1.start_with?("\\") ? _1[1..].capitalize.to_sym : _1 }
1914
+ if (match = accept_re(Patterns::FLAG_LIST))
1915
+ match[1].split(nil)
1916
+ .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 }
1917
+ else
1918
+ quirky__flag_list "flags-list"
1919
+ end
1920
1920
  end
1921
1921
 
1922
1922
  # "(" [flag-perm *(SP flag-perm)] ")"
1923
1923
  def flag_perm__list
1924
- match_re(Patterns::FLAG_PERM_LIST, "PERMANENTFLAGS flag-perm list")[1]
1925
- .split(nil)
1926
- .map! { _1.start_with?("\\") ? _1[1..].capitalize.to_sym : _1 }
1924
+ if (match = accept_re(Patterns::FLAG_PERM_LIST))
1925
+ match[1].split(nil)
1926
+ .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 }
1927
+ else
1928
+ quirky__flag_list "PERMANENTFLAGS flag-perm list"
1929
+ end
1930
+ end
1931
+
1932
+ def quirky__flag_list(name)
1933
+ match_re(Patterns::QUIRKY_FLAGS_LIST, "quirks mode #{name}")[1]
1934
+ .scan(Patterns::QUIRKY_FLAG)
1935
+ .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 }
1927
1936
  end
1928
1937
 
1929
1938
  # See Patterns::MBX_LIST_FLAGS
1930
1939
  def mbx_list_flags
1931
1940
  match_re(Patterns::MBX_LIST_FLAGS, "mbx-list-flags")[1]
1932
- .split(nil).map! { _1[1..].capitalize.to_sym }
1941
+ .split(nil)
1942
+ .map! { _1.delete_prefix!("\\"); _1.capitalize.to_sym }
1933
1943
  end
1934
1944
 
1935
1945
  # See https://developers.google.com/gmail/imap/imap-extensions
@@ -1962,6 +1972,10 @@ module Net
1962
1972
  # ;; Per-message mod-sequence.
1963
1973
  alias permsg_modsequence mod_sequence_value
1964
1974
 
1975
+ # RFC7162:
1976
+ # mod-sequence-valzer = "0" / mod-sequence-value
1977
+ alias mod_sequence_valzer number64
1978
+
1965
1979
  def parens__modseq; lpar; _ = permsg_modsequence; rpar; _ end
1966
1980
 
1967
1981
  # RFC8474:
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+
6
+ # An array of sequence numbers returned by Net::IMAP#search, or unique
7
+ # identifiers returned by Net::IMAP#uid_search.
8
+ #
9
+ # For backward compatibility, SearchResult inherits from Array.
10
+ class SearchResult < Array
11
+
12
+ # Returns a frozen SearchResult populated with the given +seq_nums+.
13
+ #
14
+ # Net::IMAP::SearchResult[1, 3, 5, modseq: 9]
15
+ # # => Net::IMAP::SearchResult[1, 3, 5, modseq: 9]
16
+ def self.[](*seq_nums, modseq: nil)
17
+ new(seq_nums, modseq: modseq)
18
+ end
19
+
20
+ # A modification sequence number, as described by the +CONDSTORE+
21
+ # extension in {[RFC7162
22
+ # §3.1.6]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.6].
23
+ attr_reader :modseq
24
+
25
+ # Returns a frozen SearchResult populated with the given +seq_nums+.
26
+ #
27
+ # Net::IMAP::SearchResult.new([1, 3, 5], modseq: 9)
28
+ # # => Net::IMAP::SearchResult[1, 3, 5, modseq: 9]
29
+ def initialize(seq_nums, modseq: nil)
30
+ super(seq_nums.to_ary.map { Integer _1 })
31
+ @modseq = Integer modseq if modseq
32
+ freeze
33
+ end
34
+
35
+ # Returns a frozen copy of +other+.
36
+ def initialize_copy(other); super; freeze end
37
+
38
+ # Returns whether +other+ is a SearchResult with the same values and the
39
+ # same #modseq. The order of numbers is irrelevant.
40
+ #
41
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
42
+ # Net::IMAP::SearchResult[123, 456, modseq: 789]
43
+ # # => true
44
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
45
+ # Net::IMAP::SearchResult[456, 123, modseq: 789]
46
+ # # => true
47
+ #
48
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
49
+ # Net::IMAP::SearchResult[987, 654, modseq: 789]
50
+ # # => false
51
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
52
+ # Net::IMAP::SearchResult[1, 2, 3, modseq: 9999]
53
+ # # => false
54
+ #
55
+ # SearchResult can be compared directly with Array, if #modseq is nil and
56
+ # the array is sorted.
57
+ #
58
+ # Net::IMAP::SearchResult[9, 8, 6, 4, 1] == [1, 4, 6, 8, 9] # => true
59
+ # Net::IMAP::SearchResult[3, 5, 7, modseq: 99] == [3, 5, 7] # => false
60
+ #
61
+ # Note that Array#== does require matching order and ignores #modseq.
62
+ #
63
+ # [9, 8, 6, 4, 1] == Net::IMAP::SearchResult[1, 4, 6, 8, 9] # => false
64
+ # [3, 5, 7] == Net::IMAP::SearchResult[3, 5, 7, modseq: 99] # => true
65
+ #
66
+ def ==(other)
67
+ (modseq ?
68
+ other.is_a?(self.class) && modseq == other.modseq :
69
+ other.is_a?(Array)) &&
70
+ size == other.size &&
71
+ sort == other.sort
72
+ end
73
+
74
+ # Hash equality. Unlike #==, order will be taken into account.
75
+ def hash
76
+ return super if modseq.nil?
77
+ [super, self.class, modseq].hash
78
+ end
79
+
80
+ # Hash equality. Unlike #==, order will be taken into account.
81
+ def eql?(other)
82
+ return super if modseq.nil?
83
+ self.class == other.class && hash == other.hash
84
+ end
85
+
86
+ # Returns a string that represents the SearchResult.
87
+ #
88
+ # Net::IMAP::SearchResult[123, 456, 789].inspect
89
+ # # => "[123, 456, 789]"
90
+ #
91
+ # Net::IMAP::SearchResult[543, 210, 678, modseq: 2048].inspect
92
+ # # => "Net::IMAP::SearchResult[543, 210, 678, modseq: 2048]"
93
+ #
94
+ def inspect
95
+ return super if modseq.nil?
96
+ "%s[%s, modseq: %p]" % [self.class, join(", "), modseq]
97
+ end
98
+
99
+ # Returns a string that follows the formal \IMAP syntax.
100
+ #
101
+ # data = Net::IMAP::SearchResult[2, 8, 32, 128, 256, 512]
102
+ # data.to_s # => "* SEARCH 2 8 32 128 256 512"
103
+ # data.to_s("SEARCH") # => "* SEARCH 2 8 32 128 256 512"
104
+ # data.to_s("SORT") # => "* SORT 2 8 32 128 256 512"
105
+ # data.to_s(nil) # => "2 8 32 128 256 512"
106
+ #
107
+ # data = Net::IMAP::SearchResult[1, 3, 16, 1024, modseq: 2048].to_s
108
+ # data.to_s # => "* SEARCH 1 3 16 1024 (MODSEQ 2048)"
109
+ # data.to_s("SORT") # => "* SORT 1 3 16 1024 (MODSEQ 2048)"
110
+ # data.to_s # => "1 3 16 1024 (MODSEQ 2048)"
111
+ #
112
+ def to_s(type = "SEARCH")
113
+ str = +""
114
+ str << "* %s " % [type.to_str] unless type.nil?
115
+ str << join(" ")
116
+ str << " (MODSEQ %d)" % [modseq] if modseq
117
+ -str
118
+ end
119
+
120
+ # Converts the SearchResult into a SequenceSet.
121
+ #
122
+ # Net::IMAP::SearchResult[9, 1, 2, 4, 10, 12, 3, modseq: 123_456]
123
+ # .to_sequence_set
124
+ # # => Net::IMAP::SequenceSet["1:4,9:10,12"]
125
+ def to_sequence_set; SequenceSet[*self] end
126
+
127
+ def pretty_print(pp)
128
+ return super if modseq.nil?
129
+ pp.text self.class.name + "["
130
+ pp.group_sub do
131
+ pp.nest(2) do
132
+ pp.breakable ""
133
+ each do |num|
134
+ pp.pp num
135
+ pp.text ","
136
+ pp.fill_breakable
137
+ end
138
+ pp.breakable ""
139
+ pp.text "modseq: "
140
+ pp.pp modseq
141
+ end
142
+ pp.breakable ""
143
+ pp.text "]"
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ end
150
+ end