net-imap 0.4.16 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,13 +14,6 @@ module Net
14
14
  # receive a SequenceSet as an argument, for example IMAP#search, IMAP#fetch,
15
15
  # and IMAP#store.
16
16
  #
17
- # == EXPERIMENTAL API
18
- #
19
- # SequenceSet is currently experimental. Only two methods, ::[] and
20
- # #valid_string, are considered stable. Although the API isn't expected to
21
- # change much, any other methods may be removed or changed without
22
- # deprecation.
23
- #
24
17
  # == Creating sequence sets
25
18
  #
26
19
  # SequenceSet.new with no arguments creates an empty sequence set. Note
@@ -37,7 +30,8 @@ module Net
37
30
  #
38
31
  # SequenceSet.new may receive a single optional argument: a non-zero 32 bit
39
32
  # unsigned integer, a range, a <tt>sequence-set</tt> formatted string,
40
- # another sequence set, or an enumerable containing any of these.
33
+ # another sequence set, a Set (containing only numbers or <tt>*</tt>), or an
34
+ # Array containing any of these (array inputs may be nested).
41
35
  #
42
36
  # set = Net::IMAP::SequenceSet.new(1)
43
37
  # set.valid_string #=> "1"
@@ -289,8 +283,7 @@ module Net
289
283
  private_constant :STAR_INT, :STARS
290
284
 
291
285
  COERCIBLE = ->{ _1.respond_to? :to_sequence_set }
292
- ENUMABLE = ->{ _1.respond_to?(:each) && _1.respond_to?(:empty?) }
293
- private_constant :COERCIBLE, :ENUMABLE
286
+ private_constant :COERCIBLE
294
287
 
295
288
  class << self
296
289
 
@@ -304,7 +297,7 @@ module Net
304
297
  # Use ::new to create a mutable or empty SequenceSet.
305
298
  def [](first, *rest)
306
299
  if rest.empty?
307
- if first.is_a?(SequenceSet) && set.frozen? && set.valid?
300
+ if first.is_a?(SequenceSet) && first.frozen? && first.valid?
308
301
  first
309
302
  else
310
303
  new(first).validate.freeze
@@ -682,6 +675,7 @@ module Net
682
675
  # Unlike #add, #merge, or #union, the new value is appended to #string.
683
676
  # This may result in a #string which has duplicates or is out-of-order.
684
677
  def append(object)
678
+ modifying!
685
679
  tuple = input_to_tuple object
686
680
  entry = tuple_to_str tuple
687
681
  tuple_add tuple
@@ -1271,7 +1265,8 @@ module Net
1271
1265
  when *STARS, Integer, Range then [input_to_tuple(obj)]
1272
1266
  when String then str_to_tuples obj
1273
1267
  when SequenceSet then obj.tuples
1274
- when ENUMABLE then obj.flat_map { input_to_tuples _1 }
1268
+ when Set then obj.map { [to_tuple_int(_1)] * 2 }
1269
+ when Array then obj.flat_map { input_to_tuples _1 }
1275
1270
  when nil then []
1276
1271
  else
1277
1272
  raise DataFormatError,
@@ -1284,8 +1279,7 @@ module Net
1284
1279
  # String, Set, Array, or... any type of object.
1285
1280
  def input_try_convert(input)
1286
1281
  SequenceSet.try_convert(input) ||
1287
- # Integer.try_convert(input) || # ruby 3.1+
1288
- input.respond_to?(:to_int) && Integer(input.to_int) ||
1282
+ Integer.try_convert(input) ||
1289
1283
  String.try_convert(input) ||
1290
1284
  input
1291
1285
  end
@@ -1317,6 +1311,12 @@ module Net
1317
1311
  range.include?(min) || range.include?(max) || (min..max).cover?(range)
1318
1312
  end
1319
1313
 
1314
+ def modifying!
1315
+ if frozen?
1316
+ raise FrozenError, "can't modify frozen #{self.class}: %p" % [self]
1317
+ end
1318
+ end
1319
+
1320
1320
  def tuples_add(tuples) tuples.each do tuple_add _1 end; self end
1321
1321
  def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end
1322
1322
 
@@ -1331,6 +1331,7 @@ module Net
1331
1331
  # ---------??===lower==|--|==|----|===upper===|-- join until upper
1332
1332
  # ---------??===lower==|--|==|--|=====upper===|-- join to upper
1333
1333
  def tuple_add(tuple)
1334
+ modifying!
1334
1335
  min, max = tuple
1335
1336
  lower, lower_idx = tuple_gte_with_index(min - 1)
1336
1337
  if lower.nil? then tuples << tuple
@@ -1367,6 +1368,7 @@ module Net
1367
1368
  # -------??=====lower====|--|====|---|====upper====|-- 7. delete until
1368
1369
  # -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim
1369
1370
  def tuple_subtract(tuple)
1371
+ modifying!
1370
1372
  min, max = tuple
1371
1373
  lower, idx = tuple_gte_with_index(min)
1372
1374
  if lower.nil? then nil # case 1.
@@ -1407,12 +1409,11 @@ module Net
1407
1409
  end
1408
1410
 
1409
1411
  def nz_number(num)
1410
- case num
1411
- when Integer, /\A[1-9]\d*\z/ then num = Integer(num)
1412
- else raise DataFormatError, "%p is not a valid nz-number" % [num]
1413
- end
1414
- NumValidator.ensure_nz_number(num)
1415
- num
1412
+ String === num && !/\A[1-9]\d*\z/.match?(num) and
1413
+ raise DataFormatError, "%p is not a valid nz-number" % [num]
1414
+ NumValidator.ensure_nz_number Integer num
1415
+ rescue TypeError # To catch errors from Integer()
1416
+ raise DataFormatError, $!.message
1416
1417
  end
1417
1418
 
1418
1419
  # intentionally defined after the class implementation
data/lib/net/imap.rb CHANGED
@@ -288,6 +288,8 @@ module Net
288
288
  # pre-authenticated connection.
289
289
  # - #responses: Yields unhandled UntaggedResponse#data and <em>non-+nil+</em>
290
290
  # ResponseCode#data.
291
+ # - #extract_responses: Removes and returns the responses for which the block
292
+ # returns a true value.
291
293
  # - #clear_responses: Deletes unhandled data from #responses and returns it.
292
294
  # - #add_response_handler: Add a block to be called inside the receiver thread
293
295
  # with every server response.
@@ -717,7 +719,7 @@ module Net
717
719
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
718
720
  #
719
721
  class IMAP < Protocol
720
- VERSION = "0.4.16"
722
+ VERSION = "0.5.0"
721
723
 
722
724
  # Aliases for supported capabilities, to be used with the #enable command.
723
725
  ENABLE_ALIASES = {
@@ -944,9 +946,6 @@ module Net
944
946
  @sock = tcp_socket(@host, @port)
945
947
  start_tls_session if ssl_ctx
946
948
  start_imap_connection
947
-
948
- # DEPRECATED: to remove in next version
949
- @client_thread = Thread.current
950
949
  end
951
950
 
952
951
  # Returns true after the TLS negotiation has completed and the remote
@@ -954,11 +953,6 @@ module Net
954
953
  # but peer verification was disabled.
955
954
  def tls_verified?; @tls_verified end
956
955
 
957
- def client_thread # :nodoc:
958
- warn "Net::IMAP#client_thread is deprecated and will be removed soon."
959
- @client_thread
960
- end
961
-
962
956
  # Disconnects from the server.
963
957
  #
964
958
  # Related: #logout, #logout!
@@ -1242,6 +1236,9 @@ module Net
1242
1236
  # +SASL-IR+ capability, below). Defaults to the #config value for
1243
1237
  # {sasl_ir}[rdoc-ref:Config#sasl_ir], which defaults to +true+.
1244
1238
  #
1239
+ # The +registry+ kwarg can be used to select the mechanism implementation
1240
+ # from a custom registry. See SASL.authenticator and SASL::Authenticators.
1241
+ #
1245
1242
  # All other arguments are forwarded to the registered SASL authenticator for
1246
1243
  # the requested mechanism. <em>The documentation for each individual
1247
1244
  # mechanism must be consulted for its specific parameters.</em>
@@ -1336,29 +1333,9 @@ module Net
1336
1333
  # Previously cached #capabilities will be cleared when this method
1337
1334
  # completes. If the TaggedResponse to #authenticate includes updated
1338
1335
  # capabilities, they will be cached.
1339
- def authenticate(mechanism, *creds,
1340
- sasl_ir: config.sasl_ir,
1341
- **props, &callback)
1342
- mechanism = mechanism.to_s.tr("_", "-").upcase
1343
- authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
1344
- cmdargs = ["AUTHENTICATE", mechanism]
1345
- if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
1346
- authenticator.respond_to?(:initial_response?) &&
1347
- authenticator.initial_response?
1348
- response = authenticator.process(nil)
1349
- cmdargs << (response.empty? ? "=" : [response].pack("m0"))
1350
- end
1351
- result = send_command_with_continuations(*cmdargs) {|data|
1352
- challenge = data.unpack1("m")
1353
- response = authenticator.process challenge
1354
- [response].pack("m0")
1355
- }
1356
- if authenticator.respond_to?(:done?) && !authenticator.done?
1357
- logout!
1358
- raise SASL::AuthenticationIncomplete, result
1359
- end
1360
- @capabilities = capabilities_from_resp_code result
1361
- result
1336
+ def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback)
1337
+ sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback)
1338
+ .tap { @capabilities = capabilities_from_resp_code _1 }
1362
1339
  end
1363
1340
 
1364
1341
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1378,13 +1355,9 @@ module Net
1378
1355
  # ===== Capabilities
1379
1356
  #
1380
1357
  # An IMAP client MUST NOT call #login when the server advertises the
1381
- # +LOGINDISABLED+ capability.
1382
- #
1383
- # if imap.capability? "LOGINDISABLED"
1384
- # raise "Remote server has disabled the login command"
1385
- # else
1386
- # imap.login username, password
1387
- # end
1358
+ # +LOGINDISABLED+ capability. By default, Net::IMAP will raise a
1359
+ # LoginDisabledError when that capability is present. See
1360
+ # Config#enforce_logindisabled.
1388
1361
  #
1389
1362
  # Server capabilities may change after #starttls, #login, and #authenticate.
1390
1363
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1392,6 +1365,9 @@ module Net
1392
1365
  # ResponseCode.
1393
1366
  #
1394
1367
  def login(user, password)
1368
+ if enforce_logindisabled? && capability?("LOGINDISABLED")
1369
+ raise LoginDisabledError
1370
+ end
1395
1371
  send_command("LOGIN", user, password)
1396
1372
  .tap { @capabilities = capabilities_from_resp_code _1 }
1397
1373
  end
@@ -1948,7 +1924,7 @@ module Net
1948
1924
  # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]].
1949
1925
  def uid_expunge(uid_set)
1950
1926
  synchronize do
1951
- send_command("UID EXPUNGE", MessageSet.new(uid_set))
1927
+ send_command("UID EXPUNGE", SequenceSet.new(uid_set))
1952
1928
  clear_responses("EXPUNGE")
1953
1929
  end
1954
1930
  end
@@ -2494,41 +2470,98 @@ module Net
2494
2470
  end
2495
2471
  end
2496
2472
 
2473
+ RESPONSES_DEPRECATION_MSG =
2474
+ "Pass a type or block to #responses, " \
2475
+ "set config.responses_without_block to :frozen_dup " \
2476
+ "or :silence_deprecation_warning, " \
2477
+ "or use #extract_responses or #clear_responses."
2478
+ private_constant :RESPONSES_DEPRECATION_MSG
2479
+
2497
2480
  # :call-seq:
2481
+ # responses -> hash of {String => Array} (see config.responses_without_block)
2482
+ # responses(type) -> frozen array
2498
2483
  # responses {|hash| ...} -> block result
2499
2484
  # responses(type) {|array| ...} -> block result
2500
2485
  #
2501
- # Yields unhandled responses and returns the result of the block.
2486
+ # Yields or returns unhandled server responses. Unhandled responses are
2487
+ # stored in a hash, with arrays of UntaggedResponse#data keyed by
2488
+ # UntaggedResponse#name and <em>non-+nil+</em> untagged ResponseCode#data
2489
+ # keyed by ResponseCode#name.
2490
+ #
2491
+ # When a block is given, yields unhandled responses and returns the block's
2492
+ # result. Without a block, returns the unhandled responses.
2493
+ #
2494
+ # [With +type+]
2495
+ # Yield or return only the array of responses for that +type+.
2496
+ # When no block is given, the returned array is a frozen copy.
2497
+ # [Without +type+]
2498
+ # Yield or return the entire responses hash.
2499
+ #
2500
+ # When no block is given, the behavior is determined by
2501
+ # Config#responses_without_block:
2502
+ # >>>
2503
+ # [+:silence_deprecation_warning+ <em>(original behavior)</em>]
2504
+ # Returns the mutable responses hash (without any warnings).
2505
+ # <em>This is not thread-safe.</em>
2506
+ #
2507
+ # [+:warn+ <em>(default since +v0.5+)</em>]
2508
+ # Prints a warning and returns the mutable responses hash.
2509
+ # <em>This is not thread-safe.</em>
2502
2510
  #
2503
- # Unhandled responses are stored in a hash, with arrays of
2504
- # <em>non-+nil+</em> UntaggedResponse#data keyed by UntaggedResponse#name
2505
- # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
2506
- # yield the entire responses hash. Call with +type+ to yield only the array
2507
- # of responses for that type.
2511
+ # [+:frozen_dup+ <em>(planned default for +v0.6+)</em>]
2512
+ # Returns a frozen copy of the unhandled responses hash, with frozen
2513
+ # array values.
2514
+ #
2515
+ # [+:raise+]
2516
+ # Raise an +ArgumentError+ with the deprecation warning.
2508
2517
  #
2509
2518
  # For example:
2510
2519
  #
2511
2520
  # imap.select("inbox")
2512
- # p imap.responses("EXISTS", &:last)
2521
+ # p imap.responses("EXISTS").last
2513
2522
  # #=> 2
2523
+ # p imap.responses("UIDNEXT", &:last)
2524
+ # #=> 123456
2514
2525
  # p imap.responses("UIDVALIDITY", &:last)
2515
2526
  # #=> 968263756
2527
+ # p imap.responses {|responses|
2528
+ # {
2529
+ # exists: responses.delete("EXISTS").last,
2530
+ # uidnext: responses.delete("UIDNEXT").last,
2531
+ # uidvalidity: responses.delete("UIDVALIDITY").last,
2532
+ # }
2533
+ # }
2534
+ # #=> {:exists=>2, :uidnext=>123456, :uidvalidity=>968263756}
2535
+ # # "EXISTS", "UIDNEXT", and "UIDVALIDITY" have been removed:
2536
+ # p imap.responses(&:keys)
2537
+ # #=> ["FLAGS", "OK", "PERMANENTFLAGS", "RECENT", "HIGHESTMODSEQ"]
2538
+ #
2539
+ # Related: #extract_responses, #clear_responses, #response_handlers, #greeting
2516
2540
  #
2541
+ # ===== Thread safety
2517
2542
  # >>>
2518
2543
  # *Note:* Access to the responses hash is synchronized for thread-safety.
2519
2544
  # The receiver thread and response_handlers cannot process new responses
2520
2545
  # until the block completes. Accessing either the response hash or its
2521
- # response type arrays outside of the block is unsafe.
2546
+ # response type arrays outside of the block is unsafe. They can be safely
2547
+ # updated inside the block. Consider using #clear_responses or
2548
+ # #extract_responses instead.
2522
2549
  #
2523
- # Calling without a block is unsafe and deprecated. Future releases will
2524
- # raise ArgumentError unless a block is given.
2525
- # See Config#responses_without_block.
2550
+ # Net::IMAP will add and remove responses from the responses hash and its
2551
+ # array values, in the calling threads for commands and in the receiver
2552
+ # thread, but will not modify any responses after adding them to the
2553
+ # responses hash.
2554
+ #
2555
+ # ===== Clearing responses
2526
2556
  #
2527
2557
  # Previously unhandled responses are automatically cleared before entering a
2528
2558
  # mailbox with #select or #examine. Long-lived connections can receive many
2529
2559
  # unhandled server responses, which must be pruned or they will continually
2530
2560
  # consume more memory. Update or clear the responses hash or arrays inside
2531
- # the block, or use #clear_responses.
2561
+ # the block, or remove responses with #extract_responses, #clear_responses,
2562
+ # or #add_response_handler.
2563
+ #
2564
+ # ===== Missing responses
2532
2565
  #
2533
2566
  # Only non-+nil+ data is stored. Many important response codes have no data
2534
2567
  # of their own, but are used as "tags" on the ResponseText object they are
@@ -2539,19 +2572,24 @@ module Net
2539
2572
  # ResponseCode#data on tagged responses. Although some command methods do
2540
2573
  # return the TaggedResponse directly, #add_response_handler must be used to
2541
2574
  # handle all response codes.
2542
- #
2543
- # Related: #clear_responses, #response_handlers, #greeting
2544
2575
  def responses(type = nil)
2545
2576
  if block_given?
2546
2577
  synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
2547
2578
  elsif type
2548
- raise ArgumentError, "Pass a block or use #clear_responses"
2579
+ synchronize { @responses[type.to_s.upcase].dup.freeze }
2549
2580
  else
2550
2581
  case config.responses_without_block
2551
2582
  when :raise
2552
- raise ArgumentError, "Pass a block or use #clear_responses"
2583
+ raise ArgumentError, RESPONSES_DEPRECATION_MSG
2553
2584
  when :warn
2554
- warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1)
2585
+ warn(RESPONSES_DEPRECATION_MSG, uplevel: 1, category: :deprecated)
2586
+ when :frozen_dup
2587
+ synchronize {
2588
+ responses = @responses.transform_values(&:freeze)
2589
+ responses.default_proc = nil
2590
+ responses.default = [].freeze
2591
+ return responses.freeze
2592
+ }
2555
2593
  end
2556
2594
  @responses
2557
2595
  end
@@ -2567,7 +2605,7 @@ module Net
2567
2605
  # Clearing responses is synchronized with other threads. The lock is
2568
2606
  # released before returning.
2569
2607
  #
2570
- # Related: #responses, #response_handlers
2608
+ # Related: #extract_responses, #responses, #response_handlers
2571
2609
  def clear_responses(type = nil)
2572
2610
  synchronize {
2573
2611
  if type
@@ -2581,6 +2619,30 @@ module Net
2581
2619
  .freeze
2582
2620
  end
2583
2621
 
2622
+ # :call-seq:
2623
+ # extract_responses(type) {|response| ... } -> array
2624
+ #
2625
+ # Yields all of the unhandled #responses for a single response +type+.
2626
+ # Removes and returns the responses for which the block returns a true
2627
+ # value.
2628
+ #
2629
+ # Extracting responses is synchronized with other threads. The lock is
2630
+ # released before returning.
2631
+ #
2632
+ # Related: #responses, #clear_responses
2633
+ def extract_responses(type)
2634
+ type = String.try_convert(type) or
2635
+ raise ArgumentError, "type must be a string"
2636
+ raise ArgumentError, "must provide a block" unless block_given?
2637
+ extracted = []
2638
+ responses(type) do |all|
2639
+ all.reject! do |response|
2640
+ extracted << response if yield response
2641
+ end
2642
+ end
2643
+ extracted
2644
+ end
2645
+
2584
2646
  # Returns all response handlers, including those that are added internally
2585
2647
  # by commands. Each response handler will be called with every new
2586
2648
  # UntaggedResponse, TaggedResponse, and ContinuationRequest.
@@ -2869,6 +2931,14 @@ module Net
2869
2931
  end
2870
2932
  end
2871
2933
 
2934
+ def enforce_logindisabled?
2935
+ if config.enforce_logindisabled == :when_capabilities_cached
2936
+ capabilities_cached?
2937
+ else
2938
+ config.enforce_logindisabled
2939
+ end
2940
+ end
2941
+
2872
2942
  def search_internal(cmd, keys, charset)
2873
2943
  if keys.instance_of?(String)
2874
2944
  keys = [RawData.new(keys)]
@@ -2902,9 +2972,9 @@ module Net
2902
2972
  synchronize do
2903
2973
  clear_responses("FETCH")
2904
2974
  if mod
2905
- send_command(cmd, MessageSet.new(set), attr, mod)
2975
+ send_command(cmd, SequenceSet.new(set), attr, mod)
2906
2976
  else
2907
- send_command(cmd, MessageSet.new(set), attr)
2977
+ send_command(cmd, SequenceSet.new(set), attr)
2908
2978
  end
2909
2979
  clear_responses("FETCH")
2910
2980
  end
@@ -2912,7 +2982,7 @@ module Net
2912
2982
 
2913
2983
  def store_internal(cmd, set, attr, flags, unchangedsince: nil)
2914
2984
  attr = RawData.new(attr) if attr.instance_of?(String)
2915
- args = [MessageSet.new(set)]
2985
+ args = [SequenceSet.new(set)]
2916
2986
  args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
2917
2987
  args << attr << flags
2918
2988
  synchronize do
@@ -2923,7 +2993,7 @@ module Net
2923
2993
  end
2924
2994
 
2925
2995
  def copy_internal(cmd, set, mailbox)
2926
- send_command(cmd, MessageSet.new(set), mailbox)
2996
+ send_command(cmd, SequenceSet.new(set), mailbox)
2927
2997
  end
2928
2998
 
2929
2999
  def sort_internal(cmd, sort_keys, search_keys, charset)
@@ -2954,7 +3024,7 @@ module Net
2954
3024
  keys.collect! do |i|
2955
3025
  case i
2956
3026
  when -1, Range, Array
2957
- MessageSet.new(i)
3027
+ SequenceSet.new(i)
2958
3028
  else
2959
3029
  i
2960
3030
  end
data/net-imap.gemspec CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.summary = %q{Ruby client api for Internet Message Access Protocol}
17
17
  spec.description = %q{Ruby client api for Internet Message Access Protocol}
18
18
  spec.homepage = "https://github.com/ruby/net-imap"
19
- spec.required_ruby_version = Gem::Requirement.new(">= 2.7.3")
19
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0")
20
20
  spec.licenses = ["Ruby", "BSD-2-Clause"]
21
21
 
22
22
  spec.metadata["homepage_uri"] = spec.homepage
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-imap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.16
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2024-09-04 00:00:00.000000000 Z
12
+ date: 2024-10-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-protocol
@@ -118,14 +118,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
118
  requirements:
119
119
  - - ">="
120
120
  - !ruby/object:Gem::Version
121
- version: 2.7.3
121
+ version: 3.1.0
122
122
  required_rubygems_version: !ruby/object:Gem::Requirement
123
123
  requirements:
124
124
  - - ">="
125
125
  - !ruby/object:Gem::Version
126
126
  version: '0'
127
127
  requirements: []
128
- rubygems_version: 3.5.9
128
+ rubygems_version: 3.5.16
129
129
  signing_key:
130
130
  specification_version: 4
131
131
  summary: Ruby client api for Internet Message Access Protocol