net-imap 0.5.5 → 0.5.6

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: c9b92943c4c4d17210f7374b4f5577f95471038a987540a8cdad2088e6bec25d
4
- data.tar.gz: 6a28ce5ca7778cbfa3de48c3451be4d17fe6298a01e2a946725caf022a34dd8c
3
+ metadata.gz: cdbdda0ed73da899ec338f66022a16104562d3701c568b0a6d4897270a608ac5
4
+ data.tar.gz: b6a7ec70776b32f8eb57d01a0869503eb5d76f719ac091eb92e0206608e936e9
5
5
  SHA512:
6
- metadata.gz: 5a575e282b7cd6828d56360003b1de29e8ca0e3d55ce733c8de250f781413dc8e9e6a90549c14b9ed21bb46e0cdc88f3e75d92aeaed769d594f93056359ee40d
7
- data.tar.gz: 87d57464c32eab235e241c74efea7953b9798b9c40a14f66170e751560943f728aabe852d38c746283cdcb4c03914ee375021c49fec67f653113d3330a6732d2
6
+ metadata.gz: 381bf2428719ed8decb5d241fda0e19f28031dd4a77980b3717bb29c37bed1c927f00e5b57862e209ecf24b2e9b38c01088d6e1a90fc4b4cc026cdd9e6611100
7
+ data.tar.gz: 513c6a77d46b6d2cf67aea4511023acc76c69940e3b1a0d0eae7223b53ff63bc8e6e009f51fef826b09f76f6ad1d92e84243e8457f59d3912db7e74bf69d3d1b
data/Gemfile CHANGED
@@ -8,6 +8,7 @@ gem "digest"
8
8
  gem "strscan"
9
9
  gem "base64"
10
10
 
11
+ gem "irb"
11
12
  gem "rake"
12
13
  gem "rdoc"
13
14
  gem "test-unit"
@@ -287,6 +287,67 @@ module Net
287
287
  #
288
288
  # Alias for responses_without_block
289
289
 
290
+ # Whether ResponseParser should use the deprecated UIDPlusData or
291
+ # CopyUIDData for +COPYUID+ response codes, and UIDPlusData or
292
+ # AppendUIDData for +APPENDUID+ response codes.
293
+ #
294
+ # UIDPlusData stores its data in arrays of numbers, which is vulnerable to
295
+ # a memory exhaustion denial of service attack from an untrusted or
296
+ # compromised server. Set this option to +false+ to completely block this
297
+ # vulnerability. Otherwise, parser_max_deprecated_uidplus_data_size
298
+ # mitigates this vulnerability.
299
+ #
300
+ # AppendUIDData and CopyUIDData are _mostly_ backward-compatible with
301
+ # UIDPlusData. Most applications should be able to upgrade with little
302
+ # or no changes.
303
+ #
304
+ # <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
305
+ #
306
+ # <em>(Config option added in +v0.4.19+ and +v0.5.6+.)</em>
307
+ #
308
+ # <em>UIDPlusData will be removed in +v0.6+ and this config setting will
309
+ # be ignored.</em>
310
+ #
311
+ # ==== Valid options
312
+ #
313
+ # [+true+ <em>(original default)</em>]
314
+ # ResponseParser only uses UIDPlusData.
315
+ #
316
+ # [+:up_to_max_size+ <em>(default since +v0.5.6+)</em>]
317
+ # ResponseParser uses UIDPlusData when the +uid-set+ size is below
318
+ # parser_max_deprecated_uidplus_data_size. Above that size,
319
+ # ResponseParser uses AppendUIDData or CopyUIDData.
320
+ #
321
+ # [+false+ <em>(planned default for +v0.6+)</em>]
322
+ # ResponseParser _only_ uses AppendUIDData and CopyUIDData.
323
+ attr_accessor :parser_use_deprecated_uidplus_data, type: [
324
+ true, :up_to_max_size, false
325
+ ]
326
+
327
+ # The maximum +uid-set+ size that ResponseParser will parse into
328
+ # deprecated UIDPlusData. This limit only applies when
329
+ # parser_use_deprecated_uidplus_data is not +false+.
330
+ #
331
+ # <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
332
+ #
333
+ # <em>Support for limiting UIDPlusData to a maximum size was added in
334
+ # +v0.3.8+, +v0.4.19+, and +v0.5.6+.</em>
335
+ #
336
+ # <em>UIDPlusData will be removed in +v0.6+.</em>
337
+ #
338
+ # ==== Versioned Defaults
339
+ #
340
+ # Because this limit guards against a remote server causing catastrophic
341
+ # memory exhaustion, the versioned default (used by #load_defaults) also
342
+ # applies to versions without the feature.
343
+ #
344
+ # * +0.3+ and prior: <tt>10,000</tt>
345
+ # * +0.4+: <tt>1,000</tt>
346
+ # * +0.5+: <tt>100</tt>
347
+ # * +0.6+: <tt>0</tt>
348
+ #
349
+ attr_accessor :parser_max_deprecated_uidplus_data_size, type: Integer
350
+
290
351
  # Creates a new config object and initialize its attribute with +attrs+.
291
352
  #
292
353
  # If +parent+ is not given, the global config is used by default.
@@ -367,6 +428,8 @@ module Net
367
428
  sasl_ir: true,
368
429
  enforce_logindisabled: true,
369
430
  responses_without_block: :warn,
431
+ parser_use_deprecated_uidplus_data: :up_to_max_size,
432
+ parser_max_deprecated_uidplus_data_size: 100,
370
433
  ).freeze
371
434
 
372
435
  @global = default.new
@@ -378,6 +441,8 @@ module Net
378
441
  sasl_ir: false,
379
442
  responses_without_block: :silence_deprecation_warning,
380
443
  enforce_logindisabled: false,
444
+ parser_use_deprecated_uidplus_data: true,
445
+ parser_max_deprecated_uidplus_data_size: 10_000,
381
446
  ).freeze
382
447
  version_defaults[0.0] = Config[0]
383
448
  version_defaults[0.1] = Config[0]
@@ -386,12 +451,15 @@ module Net
386
451
 
387
452
  version_defaults[0.4] = Config[0.3].dup.update(
388
453
  sasl_ir: true,
454
+ parser_max_deprecated_uidplus_data_size: 1000,
389
455
  ).freeze
390
456
 
391
457
  version_defaults[0.5] = Config[:current]
392
458
 
393
459
  version_defaults[0.6] = Config[0.5].dup.update(
394
460
  responses_without_block: :frozen_dup,
461
+ parser_use_deprecated_uidplus_data: false,
462
+ parser_max_deprecated_uidplus_data_size: 0,
395
463
  ).freeze
396
464
  version_defaults[:next] = Config[0.6]
397
465
  version_defaults[:future] = Config[:next]
@@ -7,6 +7,9 @@ module Net
7
7
  autoload :UIDFetchData, "#{__dir__}/fetch_data"
8
8
  autoload :SearchResult, "#{__dir__}/search_result"
9
9
  autoload :SequenceSet, "#{__dir__}/sequence_set"
10
+ autoload :UIDPlusData, "#{__dir__}/uidplus_data"
11
+ autoload :AppendUIDData, "#{__dir__}/uidplus_data"
12
+ autoload :CopyUIDData, "#{__dir__}/uidplus_data"
10
13
  autoload :VanishedData, "#{__dir__}/vanished_data"
11
14
 
12
15
  # Net::IMAP::ContinuationRequest represents command continuation requests.
@@ -344,55 +347,6 @@ module Net
344
347
  # code data can take.
345
348
  end
346
349
 
347
- # UIDPlusData represents the ResponseCode#data that accompanies the
348
- # +APPENDUID+ and +COPYUID+ {response codes}[rdoc-ref:ResponseCode].
349
- #
350
- # A server that supports +UIDPLUS+ should send a UIDPlusData object inside
351
- # every TaggedResponse returned by the append[rdoc-ref:Net::IMAP#append],
352
- # copy[rdoc-ref:Net::IMAP#copy], move[rdoc-ref:Net::IMAP#move], {uid
353
- # copy}[rdoc-ref:Net::IMAP#uid_copy], and {uid
354
- # move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the destination
355
- # mailbox reports +UIDNOTSTICKY+.
356
- #
357
- # == Required capability
358
- # Requires either +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315]]
359
- # or +IMAP4rev2+ capability.
360
- #
361
- class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
362
- ##
363
- # method: uidvalidity
364
- # :call-seq: uidvalidity -> nonzero uint32
365
- #
366
- # The UIDVALIDITY of the destination mailbox.
367
-
368
- ##
369
- # method: source_uids
370
- # :call-seq: source_uids -> nil or an array of nonzero uint32
371
- #
372
- # The UIDs of the copied or moved messages.
373
- #
374
- # Note:: Returns +nil+ for Net::IMAP#append.
375
-
376
- ##
377
- # method: assigned_uids
378
- # :call-seq: assigned_uids -> an array of nonzero uint32
379
- #
380
- # The newly assigned UIDs of the copied, moved, or appended messages.
381
- #
382
- # Note:: This always returns an array, even when it contains only one UID.
383
-
384
- ##
385
- # :call-seq: uid_mapping -> nil or a hash
386
- #
387
- # Returns a hash mapping each source UID to the newly assigned destination
388
- # UID.
389
- #
390
- # Note:: Returns +nil+ for Net::IMAP#append.
391
- def uid_mapping
392
- source_uids&.zip(assigned_uids)&.to_h
393
- end
394
- end
395
-
396
350
  # MailboxList represents the data of an untagged +LIST+ response, for a
397
351
  # _single_ mailbox path. IMAP#list returns an array of MailboxList objects.
398
352
  #
@@ -13,13 +13,17 @@ module Net
13
13
 
14
14
  attr_reader :config
15
15
 
16
- # :call-seq: Net::IMAP::ResponseParser.new -> Net::IMAP::ResponseParser
16
+ # Creates a new ResponseParser.
17
+ #
18
+ # When +config+ is frozen or global, the parser #config inherits from it.
19
+ # Otherwise, +config+ will be used directly.
17
20
  def initialize(config: Config.global)
18
21
  @str = nil
19
22
  @pos = nil
20
23
  @lex_state = nil
21
24
  @token = nil
22
25
  @config = Config[config]
26
+ @config = @config.new if @config == Config.global || @config.frozen?
23
27
  end
24
28
 
25
29
  # :call-seq:
@@ -1997,11 +2001,10 @@ module Net
1997
2001
  #
1998
2002
  # n.b, uniqueid ⊂ uid-set. To avoid inconsistent return types, we always
1999
2003
  # match uid_set even if that returns a single-member array.
2000
- #
2001
2004
  def resp_code_apnd__data
2002
2005
  validity = number; SP!
2003
2006
  dst_uids = uid_set # uniqueid ⊂ uid-set
2004
- UIDPlusData.new(validity, nil, dst_uids)
2007
+ AppendUID(validity, dst_uids)
2005
2008
  end
2006
2009
 
2007
2010
  # already matched: "COPYUID"
@@ -2011,7 +2014,25 @@ module Net
2011
2014
  validity = number; SP!
2012
2015
  src_uids = uid_set; SP!
2013
2016
  dst_uids = uid_set
2014
- UIDPlusData.new(validity, src_uids, dst_uids)
2017
+ CopyUID(validity, src_uids, dst_uids)
2018
+ end
2019
+
2020
+ def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
2021
+ def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end
2022
+
2023
+ # TODO: remove this code in the v0.6.0 release
2024
+ def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids)
2025
+ return unless config.parser_use_deprecated_uidplus_data
2026
+ compact_uid_sets = [src_uids, dst_uids].compact
2027
+ count = compact_uid_sets.map { _1.count_with_duplicates }.max
2028
+ max = config.parser_max_deprecated_uidplus_data_size
2029
+ if count <= max
2030
+ src_uids &&= src_uids.each_ordered_number.to_a
2031
+ dst_uids = dst_uids.each_ordered_number.to_a
2032
+ UIDPlusData.new(validity, src_uids, dst_uids)
2033
+ elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size
2034
+ parse_error("uid-set is too large: %d > %d", count, max)
2035
+ end
2015
2036
  end
2016
2037
 
2017
2038
  ADDRESS_REGEXP = /\G
@@ -2137,15 +2158,9 @@ module Net
2137
2158
  # uniqueid = nz-number
2138
2159
  # ; Strictly ascending
2139
2160
  def uid_set
2140
- token = match(T_NUMBER, T_ATOM)
2141
- case token.symbol
2142
- when T_NUMBER then [Integer(token.value)]
2143
- when T_ATOM
2144
- token.value.split(",").flat_map {|range|
2145
- range = range.split(":").map {|uniqueid| Integer(uniqueid) }
2146
- range.size == 1 ? range : Range.new(range.min, range.max).to_a
2147
- }
2148
- end
2161
+ set = sequence_set
2162
+ parse_error("uid-set cannot contain '*'") if set.include_star?
2163
+ set
2149
2164
  end
2150
2165
 
2151
2166
  def nil_atom
@@ -56,18 +56,20 @@ module Net
56
56
  # set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024]
57
57
  # set.valid_string #=> "1:10,55,1024:2048"
58
58
  #
59
- # == Normalized form
59
+ # == Ordered and Normalized sets
60
60
  #
61
- # When a sequence set is created with a single String value, that #string
62
- # representation is preserved. SequenceSet's internal representation
63
- # implicitly sorts all entries, de-duplicates numbers, and coalesces
64
- # adjacent or overlapping ranges. Most enumeration methods and offset-based
65
- # methods use this normalized representation. Most modification methods
66
- # will convert #string to its normalized form.
61
+ # Sometimes the order of the set's members is significant, such as with the
62
+ # +ESORT+, <tt>CONTEXT=SORT</tt>, and +UIDPLUS+ extensions. So, when a
63
+ # sequence set is created by the parser or with a single string value, that
64
+ # #string representation is preserved.
67
65
  #
68
- # In some cases the order of the string representation is significant, such
69
- # as the +ESORT+, <tt>CONTEXT=SORT</tt>, and +UIDPLUS+ extensions. Use
70
- # #entries or #each_entry to enumerate the set in its original order. To
66
+ # Internally, SequenceSet stores a normalized representation which sorts all
67
+ # entries, de-duplicates numbers, and coalesces adjacent or overlapping
68
+ # ranges. Most methods use this normalized representation to achieve
69
+ # <tt>O(lg n)</tt> porformance. Use #entries or #each_entry to enumerate
70
+ # the set in its original order.
71
+ #
72
+ # Most modification methods convert #string to its normalized form. To
71
73
  # preserve #string order while modifying a set, use #append, #string=, or
72
74
  # #replace.
73
75
  #
@@ -160,7 +162,7 @@ module Net
160
162
  # - #===:
161
163
  # Returns whether a given object is fully contained within +self+, or
162
164
  # +nil+ if the object cannot be converted to a compatible type.
163
- # - #cover? (aliased as #===):
165
+ # - #cover?:
164
166
  # Returns whether a given object is fully contained within +self+.
165
167
  # - #intersect? (aliased as #overlap?):
166
168
  # Returns whether +self+ and a given object have any common elements.
@@ -181,30 +183,41 @@ module Net
181
183
  # - #max: Returns the maximum number in the set.
182
184
  # - #minmax: Returns the minimum and maximum numbers in the set.
183
185
  #
184
- # <i>Accessing value by offset:</i>
186
+ # <i>Accessing value by offset in sorted set:</i>
185
187
  # - #[] (aliased as #slice): Returns the number or consecutive subset at a
186
- # given offset or range of offsets.
187
- # - #at: Returns the number at a given offset.
188
- # - #find_index: Returns the given number's offset in the set
188
+ # given offset or range of offsets in the sorted set.
189
+ # - #at: Returns the number at a given offset in the sorted set.
190
+ # - #find_index: Returns the given number's offset in the sorted set.
191
+ #
192
+ # <i>Accessing value by offset in ordered entries</i>
193
+ # - #ordered_at: Returns the number at a given offset in the ordered entries.
194
+ # - #find_ordered_index: Returns the index of the given number's first
195
+ # occurrence in entries.
189
196
  #
190
197
  # <i>Set cardinality:</i>
191
198
  # - #count (aliased as #size): Returns the count of numbers in the set.
199
+ # Duplicated numbers are not counted.
192
200
  # - #empty?: Returns whether the set has no members. \IMAP syntax does not
193
201
  # allow empty sequence sets.
194
202
  # - #valid?: Returns whether the set has any members.
195
203
  # - #full?: Returns whether the set contains every possible value, including
196
204
  # <tt>*</tt>.
197
205
  #
206
+ # <i>Denormalized properties:</i>
207
+ # - #has_duplicates?: Returns whether the ordered entries repeat any
208
+ # numbers.
209
+ # - #count_duplicates: Returns the count of repeated numbers in the ordered
210
+ # entries.
211
+ # - #count_with_duplicates: Returns the count of numbers in the ordered
212
+ # entries, including any repeated numbers.
213
+ #
198
214
  # === Methods for Iterating
199
215
  #
216
+ # <i>Normalized (sorted and coalesced):</i>
200
217
  # - #each_element: Yields each number and range in the set, sorted and
201
218
  # coalesced, and returns +self+.
202
219
  # - #elements (aliased as #to_a): Returns an Array of every number and range
203
220
  # in the set, sorted and coalesced.
204
- # - #each_entry: Yields each number and range in the set, unsorted and
205
- # without deduplicating numbers or coalescing ranges, and returns +self+.
206
- # - #entries: Returns an Array of every number and range in the set,
207
- # unsorted and without deduplicating numbers or coalescing ranges.
208
221
  # - #each_range:
209
222
  # Yields each element in the set as a Range and returns +self+.
210
223
  # - #ranges: Returns an Array of every element in the set, converting
@@ -214,6 +227,14 @@ module Net
214
227
  # ranges into all of their contained numbers.
215
228
  # - #to_set: Returns a Set containing all of the #numbers in the set.
216
229
  #
230
+ # <i>Order preserving:</i>
231
+ # - #each_entry: Yields each number and range in the set, unsorted and
232
+ # without deduplicating numbers or coalescing ranges, and returns +self+.
233
+ # - #entries: Returns an Array of every number and range in the set,
234
+ # unsorted and without deduplicating numbers or coalescing ranges.
235
+ # - #each_ordered_number: Yields each number in the ordered entries and
236
+ # returns +self+.
237
+ #
217
238
  # === Methods for \Set Operations
218
239
  # These methods do not modify +self+.
219
240
  #
@@ -233,19 +254,29 @@ module Net
233
254
  # === Methods for Assigning
234
255
  # These methods add or replace elements in +self+.
235
256
  #
257
+ # <i>Normalized (sorted and coalesced):</i>
258
+ #
259
+ # These methods always update #string to be fully sorted and coalesced.
260
+ #
236
261
  # - #add (aliased as #<<): Adds a given object to the set; returns +self+.
237
262
  # - #add?: If the given object is not an element in the set, adds it and
238
263
  # returns +self+; otherwise, returns +nil+.
239
264
  # - #merge: Merges multiple elements into the set; returns +self+.
265
+ # - #complement!: Replaces the contents of the set with its own #complement.
266
+ #
267
+ # <i>Order preserving:</i>
268
+ #
269
+ # These methods _may_ cause #string to not be sorted or coalesced.
270
+ #
240
271
  # - #append: Adds a given object to the set, appending it to the existing
241
272
  # string, and returns +self+.
242
273
  # - #string=: Assigns a new #string value and replaces #elements to match.
243
274
  # - #replace: Replaces the contents of the set with the contents
244
275
  # of a given object.
245
- # - #complement!: Replaces the contents of the set with its own #complement.
246
276
  #
247
277
  # === Methods for Deleting
248
- # These methods remove elements from +self+.
278
+ # These methods remove elements from +self+, and update #string to be fully
279
+ # sorted and coalesced.
249
280
  #
250
281
  # - #clear: Removes all elements in the set; returns +self+.
251
282
  # - #delete: Removes a given object from the set; returns +self+.
@@ -681,8 +712,9 @@ module Net
681
712
  modifying!
682
713
  tuple = input_to_tuple object
683
714
  entry = tuple_to_str tuple
715
+ string unless empty? # write @string before tuple_add
684
716
  tuple_add tuple
685
- @string = -(string ? "#{@string},#{entry}" : entry)
717
+ @string = -(@string ? "#{@string},#{entry}" : entry)
686
718
  self
687
719
  end
688
720
 
@@ -838,8 +870,8 @@ module Net
838
870
  # <tt>*</tt> translates to an endless range. Use #limit to translate both
839
871
  # cases to a maximum value.
840
872
  #
841
- # If the original input was unordered or contains overlapping ranges, the
842
- # returned ranges will be ordered and coalesced.
873
+ # The returned elements will be sorted and coalesced, even when the input
874
+ # #string is not. <tt>*</tt> will sort last. See #normalize.
843
875
  #
844
876
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements
845
877
  # #=> [2, 5..9, 11..12, :*]
@@ -857,7 +889,7 @@ module Net
857
889
  # translates to <tt>:*..</tt>. Use #limit to set <tt>*</tt> to a maximum
858
890
  # value.
859
891
  #
860
- # The returned ranges will be ordered and coalesced, even when the input
892
+ # The returned ranges will be sorted and coalesced, even when the input
861
893
  # #string is not. <tt>*</tt> will sort last. See #normalize.
862
894
  #
863
895
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges
@@ -906,9 +938,7 @@ module Net
906
938
  # Related: #entries, #each_element
907
939
  def each_entry(&block) # :yields: integer or range or :*
908
940
  return to_enum(__method__) unless block_given?
909
- return each_element(&block) unless @string
910
- @string.split(",").each do yield tuple_to_entry str_to_tuple _1 end
911
- self
941
+ each_entry_tuple do yield tuple_to_entry _1 end
912
942
  end
913
943
 
914
944
  # Yields each number or range (or <tt>:*</tt>) in #elements to the block
@@ -926,6 +956,16 @@ module Net
926
956
 
927
957
  private
928
958
 
959
+ def each_entry_tuple(&block)
960
+ return to_enum(__method__) unless block_given?
961
+ if @string
962
+ @string.split(",") do block.call str_to_tuple _1 end
963
+ else
964
+ @tuples.each(&block)
965
+ end
966
+ self
967
+ end
968
+
929
969
  def tuple_to_entry((min, max))
930
970
  if min == STAR_INT then :*
931
971
  elsif max == STAR_INT then min..
@@ -957,19 +997,36 @@ module Net
957
997
  # Returns an enumerator when called without a block (even if the set
958
998
  # contains <tt>*</tt>).
959
999
  #
960
- # Related: #numbers
1000
+ # Related: #numbers, #each_ordered_number
961
1001
  def each_number(&block) # :yields: integer
962
1002
  return to_enum(__method__) unless block_given?
963
1003
  raise RangeError, '%s contains "*"' % [self.class] if include_star?
964
- each_element do |elem|
965
- case elem
966
- when Range then elem.each(&block)
967
- when Integer then block.(elem)
968
- end
969
- end
1004
+ @tuples.each do each_number_in_tuple _1, _2, &block end
970
1005
  self
971
1006
  end
972
1007
 
1008
+ # Yields each number in #entries to the block and returns self.
1009
+ # If the set contains a <tt>*</tt>, RangeError will be raised.
1010
+ #
1011
+ # Returns an enumerator when called without a block (even if the set
1012
+ # contains <tt>*</tt>).
1013
+ #
1014
+ # Related: #entries, #each_number
1015
+ def each_ordered_number(&block)
1016
+ return to_enum(__method__) unless block_given?
1017
+ raise RangeError, '%s contains "*"' % [self.class] if include_star?
1018
+ each_entry_tuple do each_number_in_tuple _1, _2, &block end
1019
+ end
1020
+
1021
+ private def each_number_in_tuple(min, max, &block)
1022
+ if min == STAR_INT then yield :*
1023
+ elsif min == max then yield min
1024
+ elsif max != STAR_INT then (min..max).each(&block)
1025
+ else
1026
+ raise RangeError, "#{SequenceSet} cannot enumerate range with '*'"
1027
+ end
1028
+ end
1029
+
973
1030
  # Returns a Set with all of the #numbers in the sequence set.
974
1031
  #
975
1032
  # If the set contains a <tt>*</tt>, RangeError will be raised.
@@ -981,8 +1038,10 @@ module Net
981
1038
 
982
1039
  # Returns the count of #numbers in the set.
983
1040
  #
984
- # If <tt>*</tt> and <tt>2**32 - 1</tt> (the maximum 32-bit unsigned
985
- # integer value) are both in the set, they will only be counted once.
1041
+ # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1042
+ # unsigned integer value).
1043
+ #
1044
+ # Related: #count_with_duplicates
986
1045
  def count
987
1046
  @tuples.sum(@tuples.count) { _2 - _1 } +
988
1047
  (include_star? && include?(UINT32_MAX) ? -1 : 0)
@@ -990,33 +1049,87 @@ module Net
990
1049
 
991
1050
  alias size count
992
1051
 
993
- # Returns the index of +number+ in the set, or +nil+ if +number+ isn't in
994
- # the set.
1052
+ # Returns the count of numbers in the ordered #entries, including any
1053
+ # repeated numbers.
1054
+ #
1055
+ # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1056
+ # unsigned integer value).
1057
+ #
1058
+ # When #string is normalized, this behaves the same as #count.
995
1059
  #
996
- # Related: #[]
1060
+ # Related: #entries, #count_duplicates, #has_duplicates?
1061
+ def count_with_duplicates
1062
+ return count unless @string
1063
+ each_entry_tuple.sum {|min, max|
1064
+ max - min + ((max == STAR_INT && min != STAR_INT) ? 0 : 1)
1065
+ }
1066
+ end
1067
+
1068
+ # Returns the count of repeated numbers in the ordered #entries, the
1069
+ # difference between #count_with_duplicates and #count.
1070
+ #
1071
+ # When #string is normalized, this is zero.
1072
+ #
1073
+ # Related: #entries, #count_with_duplicates, #has_duplicates?
1074
+ def count_duplicates
1075
+ return 0 unless @string
1076
+ count_with_duplicates - count
1077
+ end
1078
+
1079
+ # :call-seq: has_duplicates? -> true | false
1080
+ #
1081
+ # Returns whether or not the ordered #entries repeat any numbers.
1082
+ #
1083
+ # Always returns +false+ when #string is normalized.
1084
+ #
1085
+ # Related: #entries, #count_with_duplicates, #count_duplicates?
1086
+ def has_duplicates?
1087
+ return false unless @string
1088
+ count_with_duplicates != count
1089
+ end
1090
+
1091
+ # Returns the (sorted and deduplicated) index of +number+ in the set, or
1092
+ # +nil+ if +number+ isn't in the set.
1093
+ #
1094
+ # Related: #[], #at, #find_ordered_index
997
1095
  def find_index(number)
998
1096
  number = to_tuple_int number
999
- each_tuple_with_index do |min, max, idx_min|
1097
+ each_tuple_with_index(@tuples) do |min, max, idx_min|
1000
1098
  number < min and return nil
1001
1099
  number <= max and return from_tuple_int(idx_min + (number - min))
1002
1100
  end
1003
1101
  nil
1004
1102
  end
1005
1103
 
1104
+ # Returns the first index of +number+ in the ordered #entries, or
1105
+ # +nil+ if +number+ isn't in the set.
1106
+ #
1107
+ # Related: #find_index
1108
+ def find_ordered_index(number)
1109
+ number = to_tuple_int number
1110
+ each_tuple_with_index(each_entry_tuple) do |min, max, idx_min|
1111
+ if min <= number && number <= max
1112
+ return from_tuple_int(idx_min + (number - min))
1113
+ end
1114
+ end
1115
+ nil
1116
+ end
1117
+
1006
1118
  private
1007
1119
 
1008
- def each_tuple_with_index
1120
+ def each_tuple_with_index(tuples)
1009
1121
  idx_min = 0
1010
- @tuples.each do |min, max|
1011
- yield min, max, idx_min, (idx_max = idx_min + (max - min))
1122
+ tuples.each do |min, max|
1123
+ idx_max = idx_min + (max - min)
1124
+ yield min, max, idx_min, idx_max
1012
1125
  idx_min = idx_max + 1
1013
1126
  end
1014
1127
  idx_min
1015
1128
  end
1016
1129
 
1017
- def reverse_each_tuple_with_index
1130
+ def reverse_each_tuple_with_index(tuples)
1018
1131
  idx_max = -1
1019
- @tuples.reverse_each do |min, max|
1132
+ tuples.reverse_each do |min, max|
1020
1133
  yield min, max, (idx_min = idx_max - (max - min)), idx_max
1021
1134
  idx_max = idx_min - 1
1022
1135
  end
@@ -1027,18 +1140,38 @@ module Net
1027
1140
 
1028
1141
  # :call-seq: at(index) -> integer or nil
1029
1142
  #
1030
- # Returns a number from +self+, without modifying the set. Behaves the
1031
- # same as #[], except that #at only allows a single integer argument.
1143
+ # Returns the number at the given +index+ in the sorted set, without
1144
+ # modifying the set.
1032
1145
  #
1033
- # Related: #[], #slice
1146
+ # +index+ is interpreted the same as in #[], except that #at only allows a
1147
+ # single integer argument.
1148
+ #
1149
+ # Related: #[], #slice, #ordered_at
1034
1150
  def at(index)
1151
+ lookup_number_by_tuple_index(tuples, index)
1152
+ end
1153
+
1154
+ # :call-seq: ordered_at(index) -> integer or nil
1155
+ #
1156
+ # Returns the number at the given +index+ in the ordered #entries, without
1157
+ # modifying the set.
1158
+ #
1159
+ # +index+ is interpreted the same as in #at (and #[]), except that
1160
+ # #ordered_at applies to the ordered #entries, not the sorted set.
1161
+ #
1162
+ # Related: #[], #slice, #ordered_at
1163
+ def ordered_at(index)
1164
+ lookup_number_by_tuple_index(each_entry_tuple, index)
1165
+ end
1166
+
1167
+ private def lookup_number_by_tuple_index(tuples, index)
1035
1168
  index = Integer(index.to_int)
1036
1169
  if index.negative?
1037
- reverse_each_tuple_with_index do |min, max, idx_min, idx_max|
1170
+ reverse_each_tuple_with_index(tuples) do |min, max, idx_min, idx_max|
1038
1171
  idx_min <= index and return from_tuple_int(min + (index - idx_min))
1039
1172
  end
1040
1173
  else
1041
- each_tuple_with_index do |min, _, idx_min, idx_max|
1174
+ each_tuple_with_index(tuples) do |min, _, idx_min, idx_max|
1042
1175
  index <= idx_max and return from_tuple_int(min + (index - idx_min))
1043
1176
  end
1044
1177
  end
@@ -1053,17 +1186,18 @@ module Net
1053
1186
  # seqset[range] -> sequence set or nil
1054
1187
  # slice(range) -> sequence set or nil
1055
1188
  #
1056
- # Returns a number or a subset from +self+, without modifying the set.
1189
+ # Returns a number or a subset from the _sorted_ set, without modifying
1190
+ # the set.
1057
1191
  #
1058
1192
  # When an Integer argument +index+ is given, the number at offset +index+
1059
- # is returned:
1193
+ # in the sorted set is returned:
1060
1194
  #
1061
1195
  # set = Net::IMAP::SequenceSet["10:15,20:23,26"]
1062
1196
  # set[0] #=> 10
1063
1197
  # set[5] #=> 15
1064
1198
  # set[10] #=> 26
1065
1199
  #
1066
- # If +index+ is negative, it counts relative to the end of +self+:
1200
+ # If +index+ is negative, it counts relative to the end of the sorted set:
1067
1201
  # set = Net::IMAP::SequenceSet["10:15,20:23,26"]
1068
1202
  # set[-1] #=> 26
1069
1203
  # set[-3] #=> 22
@@ -1075,13 +1209,14 @@ module Net
1075
1209
  # set[11] #=> nil
1076
1210
  # set[-12] #=> nil
1077
1211
  #
1078
- # The result is based on the normalized set—sorted and de-duplicatednot
1079
- # on the assigned value of #string.
1212
+ # The result is based on the sorted and de-duplicated set, not on the
1213
+ # ordered #entries in #string.
1080
1214
  #
1081
1215
  # set = Net::IMAP::SequenceSet["12,20:23,11:16,21"]
1082
1216
  # set[0] #=> 11
1083
1217
  # set[-1] #=> 23
1084
1218
  #
1219
+ # Related: #at
1085
1220
  def [](index, length = nil)
1086
1221
  if length then slice_length(index, length)
1087
1222
  elsif index.is_a?(Range) then slice_range(index)
@@ -1337,8 +1472,8 @@ module Net
1337
1472
  modifying!
1338
1473
  min, max = tuple
1339
1474
  lower, lower_idx = tuple_gte_with_index(min - 1)
1340
- if lower.nil? then tuples << tuple
1341
- elsif (max + 1) < lower.first then tuples.insert(lower_idx, tuple)
1475
+ if lower.nil? then tuples << [min, max]
1476
+ elsif (max + 1) < lower.first then tuples.insert(lower_idx, [min, max])
1342
1477
  else tuple_coalesce(lower, lower_idx, min, max)
1343
1478
  end
1344
1479
  end
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP < Protocol
5
+
6
+ # *NOTE:* <em>UIDPlusData is deprecated and will be removed in the +0.6.0+
7
+ # release.</em> To use AppendUIDData and CopyUIDData before +0.6.0+, set
8
+ # Config#parser_use_deprecated_uidplus_data to +false+.
9
+ #
10
+ # UIDPlusData represents the ResponseCode#data that accompanies the
11
+ # +APPENDUID+ and +COPYUID+ {response codes}[rdoc-ref:ResponseCode].
12
+ #
13
+ # A server that supports +UIDPLUS+ should send UIDPlusData in response to
14
+ # the append[rdoc-ref:Net::IMAP#append], copy[rdoc-ref:Net::IMAP#copy],
15
+ # move[rdoc-ref:Net::IMAP#move], {uid copy}[rdoc-ref:Net::IMAP#uid_copy],
16
+ # and {uid move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the
17
+ # destination mailbox reports +UIDNOTSTICKY+.
18
+ #
19
+ # Note that append[rdoc-ref:Net::IMAP#append], copy[rdoc-ref:Net::IMAP#copy]
20
+ # and {uid_copy}[rdoc-ref:Net::IMAP#uid_copy] return UIDPlusData in their
21
+ # TaggedResponse. But move[rdoc-ref:Net::IMAP#copy] and
22
+ # {uid_move}[rdoc-ref:Net::IMAP#uid_move] _should_ send UIDPlusData in an
23
+ # UntaggedResponse response before sending their TaggedResponse. However
24
+ # some servers do send UIDPlusData in the TaggedResponse for +MOVE+
25
+ # commands---this complies with the older +UIDPLUS+ specification but is
26
+ # discouraged by the +MOVE+ extension and disallowed by +IMAP4rev2+.
27
+ #
28
+ # == Required capability
29
+ # Requires either +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315]]
30
+ # or +IMAP4rev2+ capability.
31
+ #
32
+ class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
33
+ ##
34
+ # method: uidvalidity
35
+ # :call-seq: uidvalidity -> nonzero uint32
36
+ #
37
+ # The UIDVALIDITY of the destination mailbox.
38
+
39
+ ##
40
+ # method: source_uids
41
+ # :call-seq: source_uids -> nil or an array of nonzero uint32
42
+ #
43
+ # The UIDs of the copied or moved messages.
44
+ #
45
+ # Note:: Returns +nil+ for Net::IMAP#append.
46
+
47
+ ##
48
+ # method: assigned_uids
49
+ # :call-seq: assigned_uids -> an array of nonzero uint32
50
+ #
51
+ # The newly assigned UIDs of the copied, moved, or appended messages.
52
+ #
53
+ # Note:: This always returns an array, even when it contains only one UID.
54
+
55
+ ##
56
+ # :call-seq: uid_mapping -> nil or a hash
57
+ #
58
+ # Returns a hash mapping each source UID to the newly assigned destination
59
+ # UID.
60
+ #
61
+ # Note:: Returns +nil+ for Net::IMAP#append.
62
+ def uid_mapping
63
+ source_uids&.zip(assigned_uids)&.to_h
64
+ end
65
+ end
66
+
67
+ # >>>
68
+ # *NOTE:* <em>AppendUIDData will replace UIDPlusData for +APPENDUID+ in the
69
+ # +0.6.0+ release.</em> To use AppendUIDData before +0.6.0+, set
70
+ # Config#parser_use_deprecated_uidplus_data to +false+.
71
+ #
72
+ # AppendUIDData represents the ResponseCode#data that accompanies the
73
+ # +APPENDUID+ {response code}[rdoc-ref:ResponseCode].
74
+ #
75
+ # A server that supports +UIDPLUS+ (or +IMAP4rev2+) should send
76
+ # AppendUIDData inside every TaggedResponse returned by the
77
+ # append[rdoc-ref:Net::IMAP#append] command---unless the target mailbox
78
+ # reports +UIDNOTSTICKY+.
79
+ #
80
+ # == Required capability
81
+ # Requires either +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315]]
82
+ # or +IMAP4rev2+ capability.
83
+ class AppendUIDData < Data.define(:uidvalidity, :assigned_uids)
84
+ def initialize(uidvalidity:, assigned_uids:)
85
+ uidvalidity = Integer(uidvalidity)
86
+ assigned_uids = SequenceSet[assigned_uids]
87
+ NumValidator.ensure_nz_number(uidvalidity)
88
+ if assigned_uids.include_star?
89
+ raise DataFormatError, "uid-set cannot contain '*'"
90
+ end
91
+ super
92
+ end
93
+
94
+ ##
95
+ # attr_reader: uidvalidity
96
+ # :call-seq: uidvalidity -> nonzero uint32
97
+ #
98
+ # The UIDVALIDITY of the destination mailbox.
99
+
100
+ ##
101
+ # attr_reader: assigned_uids
102
+ #
103
+ # A SequenceSet with the newly assigned UIDs of the appended messages.
104
+
105
+ # Returns the number of messages that have been appended.
106
+ def size
107
+ assigned_uids.count_with_duplicates
108
+ end
109
+ end
110
+
111
+ # >>>
112
+ # *NOTE:* <em>CopyUIDData will replace UIDPlusData for +COPYUID+ in the
113
+ # +0.6.0+ release.</em> To use CopyUIDData before +0.6.0+, set
114
+ # Config#parser_use_deprecated_uidplus_data to +false+.
115
+ #
116
+ # CopyUIDData represents the ResponseCode#data that accompanies the
117
+ # +COPYUID+ {response code}[rdoc-ref:ResponseCode].
118
+ #
119
+ # A server that supports +UIDPLUS+ (or +IMAP4rev2+) should send CopyUIDData
120
+ # in response to
121
+ # copy[rdoc-ref:Net::IMAP#copy], {uid_copy}[rdoc-ref:Net::IMAP#uid_copy],
122
+ # move[rdoc-ref:Net::IMAP#copy], and {uid_move}[rdoc-ref:Net::IMAP#uid_move]
123
+ # commands---unless the destination mailbox reports +UIDNOTSTICKY+.
124
+ #
125
+ # Note that copy[rdoc-ref:Net::IMAP#copy] and
126
+ # {uid_copy}[rdoc-ref:Net::IMAP#uid_copy] return CopyUIDData in their
127
+ # TaggedResponse. But move[rdoc-ref:Net::IMAP#copy] and
128
+ # {uid_move}[rdoc-ref:Net::IMAP#uid_move] _should_ send CopyUIDData in an
129
+ # UntaggedResponse response before sending their TaggedResponse. However
130
+ # some servers do send CopyUIDData in the TaggedResponse for +MOVE+
131
+ # commands---this complies with the older +UIDPLUS+ specification but is
132
+ # discouraged by the +MOVE+ extension and disallowed by +IMAP4rev2+.
133
+ #
134
+ # == Required capability
135
+ # Requires either +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315]]
136
+ # or +IMAP4rev2+ capability.
137
+ class CopyUIDData < Data.define(:uidvalidity, :source_uids, :assigned_uids)
138
+ def initialize(uidvalidity:, source_uids:, assigned_uids:)
139
+ uidvalidity = Integer(uidvalidity)
140
+ source_uids = SequenceSet[source_uids]
141
+ assigned_uids = SequenceSet[assigned_uids]
142
+ NumValidator.ensure_nz_number(uidvalidity)
143
+ if source_uids.include_star? || assigned_uids.include_star?
144
+ raise DataFormatError, "uid-set cannot contain '*'"
145
+ elsif source_uids.count_with_duplicates != assigned_uids.count_with_duplicates
146
+ raise DataFormatError, "mismatched uid-set sizes for %s and %s" % [
147
+ source_uids, assigned_uids
148
+ ]
149
+ end
150
+ super
151
+ end
152
+
153
+ ##
154
+ # attr_reader: uidvalidity
155
+ #
156
+ # The +UIDVALIDITY+ of the destination mailbox (a nonzero unsigned 32 bit
157
+ # integer).
158
+
159
+ ##
160
+ # attr_reader: source_uids
161
+ #
162
+ # A SequenceSet with the original UIDs of the copied or moved messages.
163
+
164
+ ##
165
+ # attr_reader: assigned_uids
166
+ #
167
+ # A SequenceSet with the newly assigned UIDs of the copied or moved
168
+ # messages.
169
+
170
+ # Returns the number of messages that have been copied or moved.
171
+ # source_uids and the assigned_uids will both the same number of UIDs.
172
+ def size
173
+ assigned_uids.count_with_duplicates
174
+ end
175
+
176
+ # :call-seq:
177
+ # assigned_uid_for(source_uid) -> uid
178
+ # self[source_uid] -> uid
179
+ #
180
+ # Returns the UID in the destination mailbox for the message that was
181
+ # copied from +source_uid+ in the source mailbox.
182
+ #
183
+ # This is the reverse of #source_uid_for.
184
+ #
185
+ # Related: source_uid_for, each_uid_pair, uid_mapping
186
+ def assigned_uid_for(source_uid)
187
+ idx = source_uids.find_ordered_index(source_uid) and
188
+ assigned_uids.ordered_at(idx)
189
+ end
190
+ alias :[] :assigned_uid_for
191
+
192
+ # :call-seq:
193
+ # source_uid_for(assigned_uid) -> uid
194
+ #
195
+ # Returns the UID in the source mailbox for the message that was copied to
196
+ # +assigned_uid+ in the source mailbox.
197
+ #
198
+ # This is the reverse of #assigned_uid_for.
199
+ #
200
+ # Related: assigned_uid_for, each_uid_pair, uid_mapping
201
+ def source_uid_for(assigned_uid)
202
+ idx = assigned_uids.find_ordered_index(assigned_uid) and
203
+ source_uids.ordered_at(idx)
204
+ end
205
+
206
+ # Yields a pair of UIDs for each copied message. The first is the
207
+ # message's UID in the source mailbox and the second is the UID in the
208
+ # destination mailbox.
209
+ #
210
+ # Returns an enumerator when no block is given.
211
+ #
212
+ # Please note the warning on uid_mapping before calling methods like
213
+ # +to_h+ or +to_a+ on the returned enumerator.
214
+ #
215
+ # Related: uid_mapping, assigned_uid_for, source_uid_for
216
+ def each_uid_pair
217
+ return enum_for(__method__) unless block_given?
218
+ source_uids.each_ordered_number.lazy
219
+ .zip(assigned_uids.each_ordered_number.lazy) do
220
+ |source_uid, assigned_uid|
221
+ yield source_uid, assigned_uid
222
+ end
223
+ end
224
+ alias each_pair each_uid_pair
225
+ alias each each_uid_pair
226
+
227
+ # :call-seq: uid_mapping -> hash
228
+ #
229
+ # Returns a hash mapping each source UID to the newly assigned destination
230
+ # UID.
231
+ #
232
+ # <em>*Warning:*</em> The hash that is created may consume _much_ more
233
+ # memory than the data used to create it. When handling responses from an
234
+ # untrusted server, check #size before calling this method.
235
+ #
236
+ # Related: each_uid_pair, assigned_uid_for, source_uid_for
237
+ def uid_mapping
238
+ each_uid_pair.to_h
239
+ end
240
+
241
+ end
242
+
243
+ end
244
+ end
data/lib/net/imap.rb CHANGED
@@ -744,7 +744,7 @@ module Net
744
744
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
745
745
  #
746
746
  class IMAP < Protocol
747
- VERSION = "0.5.5"
747
+ VERSION = "0.5.6"
748
748
 
749
749
  # Aliases for supported capabilities, to be used with the #enable command.
750
750
  ENABLE_ALIASES = {
@@ -1239,13 +1239,21 @@ module Net
1239
1239
  #
1240
1240
  def starttls(**options)
1241
1241
  @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
1242
- send_command("STARTTLS") do |resp|
1242
+ error = nil
1243
+ ok = send_command("STARTTLS") do |resp|
1243
1244
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
1244
1245
  clear_cached_capabilities
1245
1246
  clear_responses
1246
1247
  start_tls_session
1247
1248
  end
1249
+ rescue Exception => error
1250
+ raise # note that the error backtrace is in the receiver_thread
1248
1251
  end
1252
+ if error
1253
+ disconnect
1254
+ raise error
1255
+ end
1256
+ ok
1249
1257
  end
1250
1258
 
1251
1259
  # :call-seq:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-imap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
8
8
  - nicholas a. evans
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-01-04 00:00:00.000000000 Z
11
+ date: 2025-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-protocol
@@ -96,6 +96,7 @@ files:
96
96
  - lib/net/imap/stringprep/saslprep_tables.rb
97
97
  - lib/net/imap/stringprep/tables.rb
98
98
  - lib/net/imap/stringprep/trace.rb
99
+ - lib/net/imap/uidplus_data.rb
99
100
  - lib/net/imap/vanished_data.rb
100
101
  - net-imap.gemspec
101
102
  - rakelib/benchmarks.rake