net-imap 0.4.18 → 0.4.19

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: f71a5476fad0d1bbe6dd52a510fa083ef6b35cb98f27af536a5991ebe9f31244
4
- data.tar.gz: 10082ff774cfccf2b0b43c58cf62bdb99b3e2e75cf7ff7fcb9c508cde8dc33ed
3
+ metadata.gz: 0d13d2ec5aeab6f5fce9d77841e77c690a2849d2d6393d4bac0847fcec6dcf2b
4
+ data.tar.gz: 2821ba41ca70465fdfc38005908ae2fa2828ecd5c3425bf407df27f2e949ea2b
5
5
  SHA512:
6
- metadata.gz: c98013c4ad6493bb0e455cced96388e35214567eb0359085e5023beffc9d748472ff729dc70718a3e66ba86a78e5a1d5de803fc68e5012a6da852e57f951398c
7
- data.tar.gz: bd5b766443689dad66eb1caf4479763f8181d38e64b3db7fdf415b277d27721fda9d4ce634e4638ab4f0860e3a82f63a37581830e81a617fe08baaff07262694
6
+ metadata.gz: 45bb4a741d80d2097e487b3d1ca31196f97322d6104967c6cad6410d65321e372667a6ff5bbb5bf9aafbdddc7906c7fc05b88c2fff06984dc9efd8a309802ecc
7
+ data.tar.gz: '08acdba3fdc323f01bc593ac7fde03ed5e06f1265be821263213fd53714b647e81305713d6829b67adba55dfb541a231e8c0a2303ad83e3efada4088e10c8419'
@@ -262,6 +262,67 @@ module Net
262
262
  #
263
263
  # Alias for responses_without_block
264
264
 
265
+ # Whether ResponseParser should use the deprecated UIDPlusData or
266
+ # CopyUIDData for +COPYUID+ response codes, and UIDPlusData or
267
+ # AppendUIDData for +APPENDUID+ response codes.
268
+ #
269
+ # UIDPlusData stores its data in arrays of numbers, which is vulnerable to
270
+ # a memory exhaustion denial of service attack from an untrusted or
271
+ # compromised server. Set this option to +false+ to completely block this
272
+ # vulnerability. Otherwise, parser_max_deprecated_uidplus_data_size
273
+ # mitigates this vulnerability.
274
+ #
275
+ # AppendUIDData and CopyUIDData are _mostly_ backward-compatible with
276
+ # UIDPlusData. Most applications should be able to upgrade with little
277
+ # or no changes.
278
+ #
279
+ # <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
280
+ #
281
+ # <em>(Config option added in +v0.4.19+ and +v0.5.6+.)</em>
282
+ #
283
+ # <em>UIDPlusData will be removed in +v0.6+ and this config setting will
284
+ # be ignored.</em>
285
+ #
286
+ # ==== Valid options
287
+ #
288
+ # [+true+ <em>(original default)</em>]
289
+ # ResponseParser only uses UIDPlusData.
290
+ #
291
+ # [+:up_to_max_size+ <em>(default since +v0.5.6+)</em>]
292
+ # ResponseParser uses UIDPlusData when the +uid-set+ size is below
293
+ # parser_max_deprecated_uidplus_data_size. Above that size,
294
+ # ResponseParser uses AppendUIDData or CopyUIDData.
295
+ #
296
+ # [+false+ <em>(planned default for +v0.6+)</em>]
297
+ # ResponseParser _only_ uses AppendUIDData and CopyUIDData.
298
+ attr_accessor :parser_use_deprecated_uidplus_data, type: [
299
+ true, :up_to_max_size, false
300
+ ]
301
+
302
+ # The maximum +uid-set+ size that ResponseParser will parse into
303
+ # deprecated UIDPlusData. This limit only applies when
304
+ # parser_use_deprecated_uidplus_data is not +false+.
305
+ #
306
+ # <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
307
+ #
308
+ # <em>Support for limiting UIDPlusData to a maximum size was added in
309
+ # +v0.3.8+, +v0.4.19+, and +v0.5.6+.</em>
310
+ #
311
+ # <em>UIDPlusData will be removed in +v0.6+.</em>
312
+ #
313
+ # ==== Versioned Defaults
314
+ #
315
+ # Because this limit guards against a remote server causing catastrophic
316
+ # memory exhaustion, the versioned default (used by #load_defaults) also
317
+ # applies to versions without the feature.
318
+ #
319
+ # * +0.3+ and prior: <tt>10,000</tt>
320
+ # * +0.4+: <tt>1,000</tt>
321
+ # * +0.5+: <tt>100</tt>
322
+ # * +0.6+: <tt>0</tt>
323
+ #
324
+ attr_accessor :parser_max_deprecated_uidplus_data_size, type: Integer
325
+
265
326
  # Creates a new config object and initialize its attribute with +attrs+.
266
327
  #
267
328
  # If +parent+ is not given, the global config is used by default.
@@ -341,6 +402,8 @@ module Net
341
402
  idle_response_timeout: 5,
342
403
  sasl_ir: true,
343
404
  responses_without_block: :silence_deprecation_warning,
405
+ parser_use_deprecated_uidplus_data: true,
406
+ parser_max_deprecated_uidplus_data_size: 1000,
344
407
  ).freeze
345
408
 
346
409
  @global = default.new
@@ -349,6 +412,8 @@ module Net
349
412
 
350
413
  version_defaults[0] = Config[0.4].dup.update(
351
414
  sasl_ir: false,
415
+ parser_use_deprecated_uidplus_data: true,
416
+ parser_max_deprecated_uidplus_data_size: 10_000,
352
417
  ).freeze
353
418
  version_defaults[0.0] = Config[0]
354
419
  version_defaults[0.1] = Config[0]
@@ -357,6 +422,8 @@ module Net
357
422
 
358
423
  version_defaults[0.5] = Config[0.4].dup.update(
359
424
  responses_without_block: :warn,
425
+ parser_use_deprecated_uidplus_data: :up_to_max_size,
426
+ parser_max_deprecated_uidplus_data_size: 100,
360
427
  ).freeze
361
428
 
362
429
  version_defaults[:default] = Config[0.4]
@@ -365,6 +432,8 @@ module Net
365
432
 
366
433
  version_defaults[0.6] = Config[0.5].dup.update(
367
434
  responses_without_block: :frozen_dup,
435
+ parser_use_deprecated_uidplus_data: false,
436
+ parser_max_deprecated_uidplus_data_size: 0,
368
437
  ).freeze
369
438
  version_defaults[:future] = Config[0.6]
370
439
 
@@ -5,6 +5,9 @@ module Net
5
5
  autoload :FetchData, "#{__dir__}/fetch_data"
6
6
  autoload :SearchResult, "#{__dir__}/search_result"
7
7
  autoload :SequenceSet, "#{__dir__}/sequence_set"
8
+ autoload :UIDPlusData, "#{__dir__}/uidplus_data"
9
+ autoload :AppendUIDData, "#{__dir__}/uidplus_data"
10
+ autoload :CopyUIDData, "#{__dir__}/uidplus_data"
8
11
 
9
12
  # Net::IMAP::ContinuationRequest represents command continuation requests.
10
13
  #
@@ -324,60 +327,6 @@ module Net
324
327
  # code data can take.
325
328
  end
326
329
 
327
- # Net::IMAP::UIDPlusData represents the ResponseCode#data that accompanies
328
- # the +APPENDUID+ and +COPYUID+ response codes.
329
- #
330
- # See [[UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]].
331
- #
332
- # ==== Capability requirement
333
- #
334
- # The +UIDPLUS+ capability[rdoc-ref:Net::IMAP#capability] must be supported.
335
- # A server that supports +UIDPLUS+ should send a UIDPlusData object inside
336
- # every TaggedResponse returned by the append[rdoc-ref:Net::IMAP#append],
337
- # copy[rdoc-ref:Net::IMAP#copy], move[rdoc-ref:Net::IMAP#move], {uid
338
- # copy}[rdoc-ref:Net::IMAP#uid_copy], and {uid
339
- # move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the destination
340
- # mailbox reports +UIDNOTSTICKY+.
341
- #
342
- #--
343
- # TODO: support MULTIAPPEND
344
- #++
345
- #
346
- class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
347
- ##
348
- # method: uidvalidity
349
- # :call-seq: uidvalidity -> nonzero uint32
350
- #
351
- # The UIDVALIDITY of the destination mailbox.
352
-
353
- ##
354
- # method: source_uids
355
- # :call-seq: source_uids -> nil or an array of nonzero uint32
356
- #
357
- # The UIDs of the copied or moved messages.
358
- #
359
- # Note:: Returns +nil+ for Net::IMAP#append.
360
-
361
- ##
362
- # method: assigned_uids
363
- # :call-seq: assigned_uids -> an array of nonzero uint32
364
- #
365
- # The newly assigned UIDs of the copied, moved, or appended messages.
366
- #
367
- # Note:: This always returns an array, even when it contains only one UID.
368
-
369
- ##
370
- # :call-seq: uid_mapping -> nil or a hash
371
- #
372
- # Returns a hash mapping each source UID to the newly assigned destination
373
- # UID.
374
- #
375
- # Note:: Returns +nil+ for Net::IMAP#append.
376
- def uid_mapping
377
- source_uids&.zip(assigned_uids)&.to_h
378
- end
379
- end
380
-
381
330
  # Net::IMAP::MailboxList represents contents of the LIST response,
382
331
  # representing a single mailbox path.
383
332
  #
@@ -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:
@@ -1863,11 +1867,10 @@ module Net
1863
1867
  #
1864
1868
  # n.b, uniqueid ⊂ uid-set. To avoid inconsistent return types, we always
1865
1869
  # match uid_set even if that returns a single-member array.
1866
- #
1867
1870
  def resp_code_apnd__data
1868
1871
  validity = number; SP!
1869
1872
  dst_uids = uid_set # uniqueid ⊂ uid-set
1870
- UIDPlusData.new(validity, nil, dst_uids)
1873
+ AppendUID(validity, dst_uids)
1871
1874
  end
1872
1875
 
1873
1876
  # already matched: "COPYUID"
@@ -1877,7 +1880,25 @@ module Net
1877
1880
  validity = number; SP!
1878
1881
  src_uids = uid_set; SP!
1879
1882
  dst_uids = uid_set
1880
- UIDPlusData.new(validity, src_uids, dst_uids)
1883
+ CopyUID(validity, src_uids, dst_uids)
1884
+ end
1885
+
1886
+ def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
1887
+ def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end
1888
+
1889
+ # TODO: remove this code in the v0.6.0 release
1890
+ def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids)
1891
+ return unless config.parser_use_deprecated_uidplus_data
1892
+ compact_uid_sets = [src_uids, dst_uids].compact
1893
+ count = compact_uid_sets.map { _1.count_with_duplicates }.max
1894
+ max = config.parser_max_deprecated_uidplus_data_size
1895
+ if count <= max
1896
+ src_uids &&= src_uids.each_ordered_number.to_a
1897
+ dst_uids = dst_uids.each_ordered_number.to_a
1898
+ UIDPlusData.new(validity, src_uids, dst_uids)
1899
+ elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size
1900
+ parse_error("uid-set is too large: %d > %d", count, max)
1901
+ end
1881
1902
  end
1882
1903
 
1883
1904
  ADDRESS_REGEXP = /\G
@@ -2003,15 +2024,9 @@ module Net
2003
2024
  # uniqueid = nz-number
2004
2025
  # ; Strictly ascending
2005
2026
  def uid_set
2006
- token = match(T_NUMBER, T_ATOM)
2007
- case token.symbol
2008
- when T_NUMBER then [Integer(token.value)]
2009
- when T_ATOM
2010
- token.value.split(",").flat_map {|range|
2011
- range = range.split(":").map {|uniqueid| Integer(uniqueid) }
2012
- range.size == 1 ? range : Range.new(range.min, range.max).to_a
2013
- }
2014
- end
2027
+ set = sequence_set
2028
+ parse_error("uid-set cannot contain '*'") if set.include_star?
2029
+ set
2015
2030
  end
2016
2031
 
2017
2032
  def nil_atom
@@ -60,18 +60,20 @@ module Net
60
60
  # set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024]
61
61
  # set.valid_string #=> "1:10,55,1024:2048"
62
62
  #
63
- # == Normalized form
63
+ # == Ordered and Normalized sets
64
64
  #
65
- # When a sequence set is created with a single String value, that #string
66
- # representation is preserved. SequenceSet's internal representation
67
- # implicitly sorts all entries, de-duplicates numbers, and coalesces
68
- # adjacent or overlapping ranges. Most enumeration methods and offset-based
69
- # methods use this normalized representation. Most modification methods
70
- # will convert #string to its normalized form.
65
+ # Sometimes the order of the set's members is significant, such as with the
66
+ # +ESORT+, <tt>CONTEXT=SORT</tt>, and +UIDPLUS+ extensions. So, when a
67
+ # sequence set is created by the parser or with a single string value, that
68
+ # #string representation is preserved.
71
69
  #
72
- # In some cases the order of the string representation is significant, such
73
- # as the +ESORT+, <tt>CONTEXT=SORT</tt>, and +UIDPLUS+ extensions. Use
74
- # #entries or #each_entry to enumerate the set in its original order. To
70
+ # Internally, SequenceSet stores a normalized representation which sorts all
71
+ # entries, de-duplicates numbers, and coalesces adjacent or overlapping
72
+ # ranges. Most methods use this normalized representation to achieve
73
+ # <tt>O(lg n)</tt> porformance. Use #entries or #each_entry to enumerate
74
+ # the set in its original order.
75
+ #
76
+ # Most modification methods convert #string to its normalized form. To
75
77
  # preserve #string order while modifying a set, use #append, #string=, or
76
78
  # #replace.
77
79
  #
@@ -164,7 +166,7 @@ module Net
164
166
  # - #===:
165
167
  # Returns whether a given object is fully contained within +self+, or
166
168
  # +nil+ if the object cannot be converted to a compatible type.
167
- # - #cover? (aliased as #===):
169
+ # - #cover?:
168
170
  # Returns whether a given object is fully contained within +self+.
169
171
  # - #intersect? (aliased as #overlap?):
170
172
  # Returns whether +self+ and a given object have any common elements.
@@ -185,30 +187,41 @@ module Net
185
187
  # - #max: Returns the maximum number in the set.
186
188
  # - #minmax: Returns the minimum and maximum numbers in the set.
187
189
  #
188
- # <i>Accessing value by offset:</i>
190
+ # <i>Accessing value by offset in sorted set:</i>
189
191
  # - #[] (aliased as #slice): Returns the number or consecutive subset at a
190
- # given offset or range of offsets.
191
- # - #at: Returns the number at a given offset.
192
- # - #find_index: Returns the given number's offset in the set
192
+ # given offset or range of offsets in the sorted set.
193
+ # - #at: Returns the number at a given offset in the sorted set.
194
+ # - #find_index: Returns the given number's offset in the sorted set.
195
+ #
196
+ # <i>Accessing value by offset in ordered entries</i>
197
+ # - #ordered_at: Returns the number at a given offset in the ordered entries.
198
+ # - #find_ordered_index: Returns the index of the given number's first
199
+ # occurrence in entries.
193
200
  #
194
201
  # <i>Set cardinality:</i>
195
202
  # - #count (aliased as #size): Returns the count of numbers in the set.
203
+ # Duplicated numbers are not counted.
196
204
  # - #empty?: Returns whether the set has no members. \IMAP syntax does not
197
205
  # allow empty sequence sets.
198
206
  # - #valid?: Returns whether the set has any members.
199
207
  # - #full?: Returns whether the set contains every possible value, including
200
208
  # <tt>*</tt>.
201
209
  #
210
+ # <i>Denormalized properties:</i>
211
+ # - #has_duplicates?: Returns whether the ordered entries repeat any
212
+ # numbers.
213
+ # - #count_duplicates: Returns the count of repeated numbers in the ordered
214
+ # entries.
215
+ # - #count_with_duplicates: Returns the count of numbers in the ordered
216
+ # entries, including any repeated numbers.
217
+ #
202
218
  # === Methods for Iterating
203
219
  #
220
+ # <i>Normalized (sorted and coalesced):</i>
204
221
  # - #each_element: Yields each number and range in the set, sorted and
205
222
  # coalesced, and returns +self+.
206
223
  # - #elements (aliased as #to_a): Returns an Array of every number and range
207
224
  # in the set, sorted and coalesced.
208
- # - #each_entry: Yields each number and range in the set, unsorted and
209
- # without deduplicating numbers or coalescing ranges, and returns +self+.
210
- # - #entries: Returns an Array of every number and range in the set,
211
- # unsorted and without deduplicating numbers or coalescing ranges.
212
225
  # - #each_range:
213
226
  # Yields each element in the set as a Range and returns +self+.
214
227
  # - #ranges: Returns an Array of every element in the set, converting
@@ -218,6 +231,14 @@ module Net
218
231
  # ranges into all of their contained numbers.
219
232
  # - #to_set: Returns a Set containing all of the #numbers in the set.
220
233
  #
234
+ # <i>Order preserving:</i>
235
+ # - #each_entry: Yields each number and range in the set, unsorted and
236
+ # without deduplicating numbers or coalescing ranges, and returns +self+.
237
+ # - #entries: Returns an Array of every number and range in the set,
238
+ # unsorted and without deduplicating numbers or coalescing ranges.
239
+ # - #each_ordered_number: Yields each number in the ordered entries and
240
+ # returns +self+.
241
+ #
221
242
  # === Methods for \Set Operations
222
243
  # These methods do not modify +self+.
223
244
  #
@@ -237,19 +258,29 @@ module Net
237
258
  # === Methods for Assigning
238
259
  # These methods add or replace elements in +self+.
239
260
  #
261
+ # <i>Normalized (sorted and coalesced):</i>
262
+ #
263
+ # These methods always update #string to be fully sorted and coalesced.
264
+ #
240
265
  # - #add (aliased as #<<): Adds a given object to the set; returns +self+.
241
266
  # - #add?: If the given object is not an element in the set, adds it and
242
267
  # returns +self+; otherwise, returns +nil+.
243
268
  # - #merge: Merges multiple elements into the set; returns +self+.
269
+ # - #complement!: Replaces the contents of the set with its own #complement.
270
+ #
271
+ # <i>Order preserving:</i>
272
+ #
273
+ # These methods _may_ cause #string to not be sorted or coalesced.
274
+ #
244
275
  # - #append: Adds a given object to the set, appending it to the existing
245
276
  # string, and returns +self+.
246
277
  # - #string=: Assigns a new #string value and replaces #elements to match.
247
278
  # - #replace: Replaces the contents of the set with the contents
248
279
  # of a given object.
249
- # - #complement!: Replaces the contents of the set with its own #complement.
250
280
  #
251
281
  # === Methods for Deleting
252
- # These methods remove elements from +self+.
282
+ # These methods remove elements from +self+, and update #string to be fully
283
+ # sorted and coalesced.
253
284
  #
254
285
  # - #clear: Removes all elements in the set; returns +self+.
255
286
  # - #delete: Removes a given object from the set; returns +self+.
@@ -685,8 +716,9 @@ module Net
685
716
  modifying!
686
717
  tuple = input_to_tuple object
687
718
  entry = tuple_to_str tuple
719
+ string unless empty? # write @string before tuple_add
688
720
  tuple_add tuple
689
- @string = -(string ? "#{@string},#{entry}" : entry)
721
+ @string = -(@string ? "#{@string},#{entry}" : entry)
690
722
  self
691
723
  end
692
724
 
@@ -842,8 +874,8 @@ module Net
842
874
  # <tt>*</tt> translates to an endless range. Use #limit to translate both
843
875
  # cases to a maximum value.
844
876
  #
845
- # If the original input was unordered or contains overlapping ranges, the
846
- # returned ranges will be ordered and coalesced.
877
+ # The returned elements will be sorted and coalesced, even when the input
878
+ # #string is not. <tt>*</tt> will sort last. See #normalize.
847
879
  #
848
880
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements
849
881
  # #=> [2, 5..9, 11..12, :*]
@@ -861,7 +893,7 @@ module Net
861
893
  # translates to <tt>:*..</tt>. Use #limit to set <tt>*</tt> to a maximum
862
894
  # value.
863
895
  #
864
- # The returned ranges will be ordered and coalesced, even when the input
896
+ # The returned ranges will be sorted and coalesced, even when the input
865
897
  # #string is not. <tt>*</tt> will sort last. See #normalize.
866
898
  #
867
899
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges
@@ -910,9 +942,7 @@ module Net
910
942
  # Related: #entries, #each_element
911
943
  def each_entry(&block) # :yields: integer or range or :*
912
944
  return to_enum(__method__) unless block_given?
913
- return each_element(&block) unless @string
914
- @string.split(",").each do yield tuple_to_entry str_to_tuple _1 end
915
- self
945
+ each_entry_tuple do yield tuple_to_entry _1 end
916
946
  end
917
947
 
918
948
  # Yields each number or range (or <tt>:*</tt>) in #elements to the block
@@ -930,6 +960,16 @@ module Net
930
960
 
931
961
  private
932
962
 
963
+ def each_entry_tuple(&block)
964
+ return to_enum(__method__) unless block_given?
965
+ if @string
966
+ @string.split(",") do block.call str_to_tuple _1 end
967
+ else
968
+ @tuples.each(&block)
969
+ end
970
+ self
971
+ end
972
+
933
973
  def tuple_to_entry((min, max))
934
974
  if min == STAR_INT then :*
935
975
  elsif max == STAR_INT then min..
@@ -961,19 +1001,36 @@ module Net
961
1001
  # Returns an enumerator when called without a block (even if the set
962
1002
  # contains <tt>*</tt>).
963
1003
  #
964
- # Related: #numbers
1004
+ # Related: #numbers, #each_ordered_number
965
1005
  def each_number(&block) # :yields: integer
966
1006
  return to_enum(__method__) unless block_given?
967
1007
  raise RangeError, '%s contains "*"' % [self.class] if include_star?
968
- each_element do |elem|
969
- case elem
970
- when Range then elem.each(&block)
971
- when Integer then block.(elem)
972
- end
973
- end
1008
+ @tuples.each do each_number_in_tuple _1, _2, &block end
974
1009
  self
975
1010
  end
976
1011
 
1012
+ # Yields each number in #entries to the block and returns self.
1013
+ # If the set contains a <tt>*</tt>, RangeError will be raised.
1014
+ #
1015
+ # Returns an enumerator when called without a block (even if the set
1016
+ # contains <tt>*</tt>).
1017
+ #
1018
+ # Related: #entries, #each_number
1019
+ def each_ordered_number(&block)
1020
+ return to_enum(__method__) unless block_given?
1021
+ raise RangeError, '%s contains "*"' % [self.class] if include_star?
1022
+ each_entry_tuple do each_number_in_tuple _1, _2, &block end
1023
+ end
1024
+
1025
+ private def each_number_in_tuple(min, max, &block)
1026
+ if min == STAR_INT then yield :*
1027
+ elsif min == max then yield min
1028
+ elsif max != STAR_INT then (min..max).each(&block)
1029
+ else
1030
+ raise RangeError, "#{SequenceSet} cannot enumerate range with '*'"
1031
+ end
1032
+ end
1033
+
977
1034
  # Returns a Set with all of the #numbers in the sequence set.
978
1035
  #
979
1036
  # If the set contains a <tt>*</tt>, RangeError will be raised.
@@ -985,8 +1042,10 @@ module Net
985
1042
 
986
1043
  # Returns the count of #numbers in the set.
987
1044
  #
988
- # If <tt>*</tt> and <tt>2**32 - 1</tt> (the maximum 32-bit unsigned
989
- # integer value) are both in the set, they will only be counted once.
1045
+ # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1046
+ # unsigned integer value).
1047
+ #
1048
+ # Related: #count_with_duplicates
990
1049
  def count
991
1050
  @tuples.sum(@tuples.count) { _2 - _1 } +
992
1051
  (include_star? && include?(UINT32_MAX) ? -1 : 0)
@@ -994,33 +1053,87 @@ module Net
994
1053
 
995
1054
  alias size count
996
1055
 
997
- # Returns the index of +number+ in the set, or +nil+ if +number+ isn't in
998
- # the set.
1056
+ # Returns the count of numbers in the ordered #entries, including any
1057
+ # repeated numbers.
1058
+ #
1059
+ # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1060
+ # unsigned integer value).
1061
+ #
1062
+ # When #string is normalized, this behaves the same as #count.
999
1063
  #
1000
- # Related: #[]
1064
+ # Related: #entries, #count_duplicates, #has_duplicates?
1065
+ def count_with_duplicates
1066
+ return count unless @string
1067
+ each_entry_tuple.sum {|min, max|
1068
+ max - min + ((max == STAR_INT && min != STAR_INT) ? 0 : 1)
1069
+ }
1070
+ end
1071
+
1072
+ # Returns the count of repeated numbers in the ordered #entries, the
1073
+ # difference between #count_with_duplicates and #count.
1074
+ #
1075
+ # When #string is normalized, this is zero.
1076
+ #
1077
+ # Related: #entries, #count_with_duplicates, #has_duplicates?
1078
+ def count_duplicates
1079
+ return 0 unless @string
1080
+ count_with_duplicates - count
1081
+ end
1082
+
1083
+ # :call-seq: has_duplicates? -> true | false
1084
+ #
1085
+ # Returns whether or not the ordered #entries repeat any numbers.
1086
+ #
1087
+ # Always returns +false+ when #string is normalized.
1088
+ #
1089
+ # Related: #entries, #count_with_duplicates, #count_duplicates?
1090
+ def has_duplicates?
1091
+ return false unless @string
1092
+ count_with_duplicates != count
1093
+ end
1094
+
1095
+ # Returns the (sorted and deduplicated) index of +number+ in the set, or
1096
+ # +nil+ if +number+ isn't in the set.
1097
+ #
1098
+ # Related: #[], #at, #find_ordered_index
1001
1099
  def find_index(number)
1002
1100
  number = to_tuple_int number
1003
- each_tuple_with_index do |min, max, idx_min|
1101
+ each_tuple_with_index(@tuples) do |min, max, idx_min|
1004
1102
  number < min and return nil
1005
1103
  number <= max and return from_tuple_int(idx_min + (number - min))
1006
1104
  end
1007
1105
  nil
1008
1106
  end
1009
1107
 
1108
+ # Returns the first index of +number+ in the ordered #entries, or
1109
+ # +nil+ if +number+ isn't in the set.
1110
+ #
1111
+ # Related: #find_index
1112
+ def find_ordered_index(number)
1113
+ number = to_tuple_int number
1114
+ each_tuple_with_index(each_entry_tuple) do |min, max, idx_min|
1115
+ if min <= number && number <= max
1116
+ return from_tuple_int(idx_min + (number - min))
1117
+ end
1118
+ end
1119
+ nil
1120
+ end
1121
+
1010
1122
  private
1011
1123
 
1012
- def each_tuple_with_index
1124
+ def each_tuple_with_index(tuples)
1013
1125
  idx_min = 0
1014
- @tuples.each do |min, max|
1015
- yield min, max, idx_min, (idx_max = idx_min + (max - min))
1126
+ tuples.each do |min, max|
1127
+ idx_max = idx_min + (max - min)
1128
+ yield min, max, idx_min, idx_max
1016
1129
  idx_min = idx_max + 1
1017
1130
  end
1018
1131
  idx_min
1019
1132
  end
1020
1133
 
1021
- def reverse_each_tuple_with_index
1134
+ def reverse_each_tuple_with_index(tuples)
1022
1135
  idx_max = -1
1023
- @tuples.reverse_each do |min, max|
1136
+ tuples.reverse_each do |min, max|
1024
1137
  yield min, max, (idx_min = idx_max - (max - min)), idx_max
1025
1138
  idx_max = idx_min - 1
1026
1139
  end
@@ -1031,18 +1144,38 @@ module Net
1031
1144
 
1032
1145
  # :call-seq: at(index) -> integer or nil
1033
1146
  #
1034
- # Returns a number from +self+, without modifying the set. Behaves the
1035
- # same as #[], except that #at only allows a single integer argument.
1147
+ # Returns the number at the given +index+ in the sorted set, without
1148
+ # modifying the set.
1036
1149
  #
1037
- # Related: #[], #slice
1150
+ # +index+ is interpreted the same as in #[], except that #at only allows a
1151
+ # single integer argument.
1152
+ #
1153
+ # Related: #[], #slice, #ordered_at
1038
1154
  def at(index)
1155
+ lookup_number_by_tuple_index(tuples, index)
1156
+ end
1157
+
1158
+ # :call-seq: ordered_at(index) -> integer or nil
1159
+ #
1160
+ # Returns the number at the given +index+ in the ordered #entries, without
1161
+ # modifying the set.
1162
+ #
1163
+ # +index+ is interpreted the same as in #at (and #[]), except that
1164
+ # #ordered_at applies to the ordered #entries, not the sorted set.
1165
+ #
1166
+ # Related: #[], #slice, #ordered_at
1167
+ def ordered_at(index)
1168
+ lookup_number_by_tuple_index(each_entry_tuple, index)
1169
+ end
1170
+
1171
+ private def lookup_number_by_tuple_index(tuples, index)
1039
1172
  index = Integer(index.to_int)
1040
1173
  if index.negative?
1041
- reverse_each_tuple_with_index do |min, max, idx_min, idx_max|
1174
+ reverse_each_tuple_with_index(tuples) do |min, max, idx_min, idx_max|
1042
1175
  idx_min <= index and return from_tuple_int(min + (index - idx_min))
1043
1176
  end
1044
1177
  else
1045
- each_tuple_with_index do |min, _, idx_min, idx_max|
1178
+ each_tuple_with_index(tuples) do |min, _, idx_min, idx_max|
1046
1179
  index <= idx_max and return from_tuple_int(min + (index - idx_min))
1047
1180
  end
1048
1181
  end
@@ -1057,17 +1190,18 @@ module Net
1057
1190
  # seqset[range] -> sequence set or nil
1058
1191
  # slice(range) -> sequence set or nil
1059
1192
  #
1060
- # Returns a number or a subset from +self+, without modifying the set.
1193
+ # Returns a number or a subset from the _sorted_ set, without modifying
1194
+ # the set.
1061
1195
  #
1062
1196
  # When an Integer argument +index+ is given, the number at offset +index+
1063
- # is returned:
1197
+ # in the sorted set is returned:
1064
1198
  #
1065
1199
  # set = Net::IMAP::SequenceSet["10:15,20:23,26"]
1066
1200
  # set[0] #=> 10
1067
1201
  # set[5] #=> 15
1068
1202
  # set[10] #=> 26
1069
1203
  #
1070
- # If +index+ is negative, it counts relative to the end of +self+:
1204
+ # If +index+ is negative, it counts relative to the end of the sorted set:
1071
1205
  # set = Net::IMAP::SequenceSet["10:15,20:23,26"]
1072
1206
  # set[-1] #=> 26
1073
1207
  # set[-3] #=> 22
@@ -1079,13 +1213,14 @@ module Net
1079
1213
  # set[11] #=> nil
1080
1214
  # set[-12] #=> nil
1081
1215
  #
1082
- # The result is based on the normalized set—sorted and de-duplicatednot
1083
- # on the assigned value of #string.
1216
+ # The result is based on the sorted and de-duplicated set, not on the
1217
+ # ordered #entries in #string.
1084
1218
  #
1085
1219
  # set = Net::IMAP::SequenceSet["12,20:23,11:16,21"]
1086
1220
  # set[0] #=> 11
1087
1221
  # set[-1] #=> 23
1088
1222
  #
1223
+ # Related: #at
1089
1224
  def [](index, length = nil)
1090
1225
  if length then slice_length(index, length)
1091
1226
  elsif index.is_a?(Range) then slice_range(index)
@@ -1341,8 +1476,8 @@ module Net
1341
1476
  modifying!
1342
1477
  min, max = tuple
1343
1478
  lower, lower_idx = tuple_gte_with_index(min - 1)
1344
- if lower.nil? then tuples << tuple
1345
- elsif (max + 1) < lower.first then tuples.insert(lower_idx, tuple)
1479
+ if lower.nil? then tuples << [min, max]
1480
+ elsif (max + 1) < lower.first then tuples.insert(lower_idx, [min, max])
1346
1481
  else tuple_coalesce(lower, lower_idx, min, max)
1347
1482
  end
1348
1483
  end
@@ -0,0 +1,326 @@
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
+ # This replaces the `Data.define` polyfill that's used by net-imap 0.5.
68
+ class Data_define__uidvalidity___assigned_uids_ # :no-doc:
69
+ attr_reader :uidvalidity, :assigned_uids
70
+
71
+ def self.[](...) new(...) end
72
+ def self.new(uidvalidity = (args = false; nil),
73
+ assigned_uids = nil,
74
+ **kwargs)
75
+ if kwargs.empty?
76
+ super(uidvalidity: uidvalidity, assigned_uids: assigned_uids)
77
+ elsif !args
78
+ super
79
+ else
80
+ raise ArgumentError, "sent both positional and keyword args"
81
+ end
82
+ end
83
+
84
+ def ==(other)
85
+ self.class == other.class &&
86
+ self.uidvalidity == other.uidvalidity &&
87
+ self.assigned_uids == other.assigned_uids
88
+ end
89
+
90
+ def eql?(other)
91
+ self.class.eql?(other.class) &&
92
+ self.uidvalidity.eql?(other.uidvalidity) &&
93
+ self.assigned_uids.eql?(other.assigned_uids)
94
+ end
95
+
96
+ def hash; [self.class, uidvalidity, assigned_uids].hash end
97
+
98
+ def initialize(uidvalidity:, assigned_uids:)
99
+ @uidvalidity = uidvalidity
100
+ @assigned_uids = assigned_uids
101
+ freeze
102
+ end
103
+ end
104
+
105
+ # >>>
106
+ # *NOTE:* <em>AppendUIDData will replace UIDPlusData for +APPENDUID+ in the
107
+ # +0.6.0+ release.</em> To use AppendUIDData before +0.6.0+, set
108
+ # Config#parser_use_deprecated_uidplus_data to +false+.
109
+ #
110
+ # AppendUIDData represents the ResponseCode#data that accompanies the
111
+ # +APPENDUID+ {response code}[rdoc-ref:ResponseCode].
112
+ #
113
+ # A server that supports +UIDPLUS+ (or +IMAP4rev2+) should send
114
+ # AppendUIDData inside every TaggedResponse returned by the
115
+ # append[rdoc-ref:Net::IMAP#append] command---unless the target mailbox
116
+ # reports +UIDNOTSTICKY+.
117
+ #
118
+ # == Required capability
119
+ # Requires either +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315]]
120
+ # or +IMAP4rev2+ capability.
121
+ class AppendUIDData < Data_define__uidvalidity___assigned_uids_
122
+ def initialize(uidvalidity:, assigned_uids:)
123
+ uidvalidity = Integer(uidvalidity)
124
+ assigned_uids = SequenceSet[assigned_uids]
125
+ NumValidator.ensure_nz_number(uidvalidity)
126
+ if assigned_uids.include_star?
127
+ raise DataFormatError, "uid-set cannot contain '*'"
128
+ end
129
+ super
130
+ end
131
+
132
+ ##
133
+ # attr_reader: uidvalidity
134
+ # :call-seq: uidvalidity -> nonzero uint32
135
+ #
136
+ # The UIDVALIDITY of the destination mailbox.
137
+
138
+ ##
139
+ # attr_reader: assigned_uids
140
+ #
141
+ # A SequenceSet with the newly assigned UIDs of the appended messages.
142
+
143
+ # Returns the number of messages that have been appended.
144
+ def size
145
+ assigned_uids.count_with_duplicates
146
+ end
147
+ end
148
+
149
+ # This replaces the `Data.define` polyfill that's used by net-imap 0.5.
150
+ class Data_define__uidvalidity___source_uids___assigned_uids_ # :no-doc:
151
+ attr_reader :uidvalidity, :source_uids, :assigned_uids
152
+
153
+ def self.[](...) new(...) end
154
+ def self.new(uidvalidity = (args = false; nil),
155
+ source_uids = nil,
156
+ assigned_uids = nil,
157
+ **kwargs)
158
+ if kwargs.empty?
159
+ super(uidvalidity: uidvalidity,
160
+ source_uids: source_uids,
161
+ assigned_uids: assigned_uids)
162
+ elsif !args
163
+ super(**kwargs)
164
+ else
165
+ raise ArgumentError, "sent both positional and keyword args"
166
+ end
167
+ end
168
+
169
+ def initialize(uidvalidity:, source_uids:, assigned_uids:)
170
+ @uidvalidity = uidvalidity
171
+ @source_uids = source_uids
172
+ @assigned_uids = assigned_uids
173
+ freeze
174
+ end
175
+
176
+ def ==(other)
177
+ self.class == other.class &&
178
+ self.uidvalidity == other.uidvalidity &&
179
+ self.source_uids == other.source_uids
180
+ self.assigned_uids == other.assigned_uids
181
+ end
182
+
183
+ def eql?(other)
184
+ self.class.eql?(other.class) &&
185
+ self.uidvalidity.eql?(other.uidvalidity) &&
186
+ self.source_uids.eql?(other.source_uids)
187
+ self.assigned_uids.eql?(other.assigned_uids)
188
+ end
189
+
190
+ def hash; [self.class, uidvalidity, source_uids, assigned_uids].hash end
191
+ end
192
+
193
+ # >>>
194
+ # *NOTE:* <em>CopyUIDData will replace UIDPlusData for +COPYUID+ in the
195
+ # +0.6.0+ release.</em> To use CopyUIDData before +0.6.0+, set
196
+ # Config#parser_use_deprecated_uidplus_data to +false+.
197
+ #
198
+ # CopyUIDData represents the ResponseCode#data that accompanies the
199
+ # +COPYUID+ {response code}[rdoc-ref:ResponseCode].
200
+ #
201
+ # A server that supports +UIDPLUS+ (or +IMAP4rev2+) should send CopyUIDData
202
+ # in response to
203
+ # copy[rdoc-ref:Net::IMAP#copy], {uid_copy}[rdoc-ref:Net::IMAP#uid_copy],
204
+ # move[rdoc-ref:Net::IMAP#copy], and {uid_move}[rdoc-ref:Net::IMAP#uid_move]
205
+ # commands---unless the destination mailbox reports +UIDNOTSTICKY+.
206
+ #
207
+ # Note that copy[rdoc-ref:Net::IMAP#copy] and
208
+ # {uid_copy}[rdoc-ref:Net::IMAP#uid_copy] return CopyUIDData in their
209
+ # TaggedResponse. But move[rdoc-ref:Net::IMAP#copy] and
210
+ # {uid_move}[rdoc-ref:Net::IMAP#uid_move] _should_ send CopyUIDData in an
211
+ # UntaggedResponse response before sending their TaggedResponse. However
212
+ # some servers do send CopyUIDData in the TaggedResponse for +MOVE+
213
+ # commands---this complies with the older +UIDPLUS+ specification but is
214
+ # discouraged by the +MOVE+ extension and disallowed by +IMAP4rev2+.
215
+ #
216
+ # == Required capability
217
+ # Requires either +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315]]
218
+ # or +IMAP4rev2+ capability.
219
+ class CopyUIDData < Data_define__uidvalidity___source_uids___assigned_uids_
220
+ def initialize(uidvalidity:, source_uids:, assigned_uids:)
221
+ uidvalidity = Integer(uidvalidity)
222
+ source_uids = SequenceSet[source_uids]
223
+ assigned_uids = SequenceSet[assigned_uids]
224
+ NumValidator.ensure_nz_number(uidvalidity)
225
+ if source_uids.include_star? || assigned_uids.include_star?
226
+ raise DataFormatError, "uid-set cannot contain '*'"
227
+ elsif source_uids.count_with_duplicates != assigned_uids.count_with_duplicates
228
+ raise DataFormatError, "mismatched uid-set sizes for %s and %s" % [
229
+ source_uids, assigned_uids
230
+ ]
231
+ end
232
+ super
233
+ end
234
+
235
+ ##
236
+ # attr_reader: uidvalidity
237
+ #
238
+ # The +UIDVALIDITY+ of the destination mailbox (a nonzero unsigned 32 bit
239
+ # integer).
240
+
241
+ ##
242
+ # attr_reader: source_uids
243
+ #
244
+ # A SequenceSet with the original UIDs of the copied or moved messages.
245
+
246
+ ##
247
+ # attr_reader: assigned_uids
248
+ #
249
+ # A SequenceSet with the newly assigned UIDs of the copied or moved
250
+ # messages.
251
+
252
+ # Returns the number of messages that have been copied or moved.
253
+ # source_uids and the assigned_uids will both the same number of UIDs.
254
+ def size
255
+ assigned_uids.count_with_duplicates
256
+ end
257
+
258
+ # :call-seq:
259
+ # assigned_uid_for(source_uid) -> uid
260
+ # self[source_uid] -> uid
261
+ #
262
+ # Returns the UID in the destination mailbox for the message that was
263
+ # copied from +source_uid+ in the source mailbox.
264
+ #
265
+ # This is the reverse of #source_uid_for.
266
+ #
267
+ # Related: source_uid_for, each_uid_pair, uid_mapping
268
+ def assigned_uid_for(source_uid)
269
+ idx = source_uids.find_ordered_index(source_uid) and
270
+ assigned_uids.ordered_at(idx)
271
+ end
272
+ alias :[] :assigned_uid_for
273
+
274
+ # :call-seq:
275
+ # source_uid_for(assigned_uid) -> uid
276
+ #
277
+ # Returns the UID in the source mailbox for the message that was copied to
278
+ # +assigned_uid+ in the source mailbox.
279
+ #
280
+ # This is the reverse of #assigned_uid_for.
281
+ #
282
+ # Related: assigned_uid_for, each_uid_pair, uid_mapping
283
+ def source_uid_for(assigned_uid)
284
+ idx = assigned_uids.find_ordered_index(assigned_uid) and
285
+ source_uids.ordered_at(idx)
286
+ end
287
+
288
+ # Yields a pair of UIDs for each copied message. The first is the
289
+ # message's UID in the source mailbox and the second is the UID in the
290
+ # destination mailbox.
291
+ #
292
+ # Returns an enumerator when no block is given.
293
+ #
294
+ # Please note the warning on uid_mapping before calling methods like
295
+ # +to_h+ or +to_a+ on the returned enumerator.
296
+ #
297
+ # Related: uid_mapping, assigned_uid_for, source_uid_for
298
+ def each_uid_pair
299
+ return enum_for(__method__) unless block_given?
300
+ source_uids.each_ordered_number.lazy
301
+ .zip(assigned_uids.each_ordered_number.lazy) do
302
+ |source_uid, assigned_uid|
303
+ yield source_uid, assigned_uid
304
+ end
305
+ end
306
+ alias each_pair each_uid_pair
307
+ alias each each_uid_pair
308
+
309
+ # :call-seq: uid_mapping -> hash
310
+ #
311
+ # Returns a hash mapping each source UID to the newly assigned destination
312
+ # UID.
313
+ #
314
+ # <em>*Warning:*</em> The hash that is created may consume _much_ more
315
+ # memory than the data used to create it. When handling responses from an
316
+ # untrusted server, check #size before calling this method.
317
+ #
318
+ # Related: each_uid_pair, assigned_uid_for, source_uid_for
319
+ def uid_mapping
320
+ each_uid_pair.to_h
321
+ end
322
+
323
+ end
324
+
325
+ end
326
+ end
data/lib/net/imap.rb CHANGED
@@ -719,7 +719,7 @@ module Net
719
719
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
720
720
  #
721
721
  class IMAP < Protocol
722
- VERSION = "0.4.18"
722
+ VERSION = "0.4.19"
723
723
 
724
724
  # Aliases for supported capabilities, to be used with the #enable command.
725
725
  ENABLE_ALIASES = {
@@ -1222,13 +1222,21 @@ module Net
1222
1222
  #
1223
1223
  def starttls(**options)
1224
1224
  @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
1225
- send_command("STARTTLS") do |resp|
1225
+ error = nil
1226
+ ok = send_command("STARTTLS") do |resp|
1226
1227
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
1227
1228
  clear_cached_capabilities
1228
1229
  clear_responses
1229
1230
  start_tls_session
1230
1231
  end
1232
+ rescue Exception => error
1233
+ raise # note that the error backtrace is in the receiver_thread
1231
1234
  end
1235
+ if error
1236
+ disconnect
1237
+ raise error
1238
+ end
1239
+ ok
1232
1240
  end
1233
1241
 
1234
1242
  # :call-seq:
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-imap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.18
4
+ version: 0.4.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
8
8
  - nicholas a. evans
9
- autorequire:
10
9
  bindir: exe
11
10
  cert_chain: []
12
- date: 2024-11-08 00:00:00.000000000 Z
11
+ date: 2025-02-07 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: net-protocol
@@ -95,6 +94,7 @@ files:
95
94
  - lib/net/imap/stringprep/saslprep_tables.rb
96
95
  - lib/net/imap/stringprep/tables.rb
97
96
  - lib/net/imap/stringprep/trace.rb
97
+ - lib/net/imap/uidplus_data.rb
98
98
  - net-imap.gemspec
99
99
  - rakelib/benchmarks.rake
100
100
  - rakelib/rdoc.rake
@@ -110,7 +110,6 @@ metadata:
110
110
  homepage_uri: https://github.com/ruby/net-imap
111
111
  source_code_uri: https://github.com/ruby/net-imap
112
112
  changelog_uri: https://github.com/ruby/net-imap/releases
113
- post_install_message:
114
113
  rdoc_options: []
115
114
  require_paths:
116
115
  - lib
@@ -125,8 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
124
  - !ruby/object:Gem::Version
126
125
  version: '0'
127
126
  requirements: []
128
- rubygems_version: 3.5.22
129
- signing_key:
127
+ rubygems_version: 3.6.2
130
128
  specification_version: 4
131
129
  summary: Ruby client api for Internet Message Access Protocol
132
130
  test_files: []