net-imap 0.5.5 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
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