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 +4 -4
- data/lib/net/imap/config.rb +69 -0
- data/lib/net/imap/response_data.rb +3 -54
- data/lib/net/imap/response_parser.rb +28 -13
- data/lib/net/imap/sequence_set.rb +193 -58
- data/lib/net/imap/uidplus_data.rb +326 -0
- data/lib/net/imap.rb +10 -2
- metadata +4 -6
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 0d13d2ec5aeab6f5fce9d77841e77c690a2849d2d6393d4bac0847fcec6dcf2b
         | 
| 4 | 
            +
              data.tar.gz: 2821ba41ca70465fdfc38005908ae2fa2828ecd5c3425bf407df27f2e949ea2b
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 45bb4a741d80d2097e487b3d1ca31196f97322d6104967c6cad6410d65321e372667a6ff5bbb5bf9aafbdddc7906c7fc05b88c2fff06984dc9efd8a309802ecc
         | 
| 7 | 
            +
              data.tar.gz: '08acdba3fdc323f01bc593ac7fde03ed5e06f1265be821263213fd53714b647e81305713d6829b67adba55dfb541a231e8c0a2303ad83e3efada4088e10c8419'
         | 
    
        data/lib/net/imap/config.rb
    CHANGED
    
    | @@ -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 | 
            -
                  #  | 
| 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 | 
            -
                     | 
| 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 | 
            -
                     | 
| 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 | 
            -
                     | 
| 2007 | 
            -
                     | 
| 2008 | 
            -
                     | 
| 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  | 
| 63 | 
            +
                # == Ordered and Normalized sets
         | 
| 64 64 | 
             
                #
         | 
| 65 | 
            -
                #  | 
| 66 | 
            -
                #  | 
| 67 | 
            -
                #  | 
| 68 | 
            -
                #  | 
| 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 | 
            -
                #  | 
| 73 | 
            -
                #  | 
| 74 | 
            -
                #  | 
| 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 | 
| 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 | 
            -
                  #  | 
| 846 | 
            -
                  #  | 
| 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  | 
| 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 | 
            -
                     | 
| 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 | 
            -
                     | 
| 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 | 
            -
                  #  | 
| 989 | 
            -
                  # integer value) | 
| 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  | 
| 998 | 
            -
                  #  | 
| 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 | 
            -
                     | 
| 1015 | 
            -
                       | 
| 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 | 
            -
                     | 
| 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  | 
| 1035 | 
            -
                  #  | 
| 1147 | 
            +
                  # Returns the number at the given +index+ in the sorted set, without
         | 
| 1148 | 
            +
                  # modifying the set.
         | 
| 1036 1149 | 
             
                  #
         | 
| 1037 | 
            -
                  #  | 
| 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  | 
| 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  | 
| 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  | 
| 1083 | 
            -
                  #  | 
| 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 <<  | 
| 1345 | 
            -
                    elsif (max + 1) < lower.first then tuples.insert(lower_idx,  | 
| 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. | 
| 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 | 
            -
                   | 
| 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. | 
| 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:  | 
| 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. | 
| 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: []
         |