diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb
index 452ef0aa..a8d546c6 100644
--- a/lib/net/imap/config.rb
+++ b/lib/net/imap/config.rb
@@ -262,6 +262,32 @@ def self.[](config)
#
# Alias for responses_without_block
+ # Whether ResponseParser should use the deprecated UIDPlusData or
+ # CopyUIDData for +COPYUID+ response codes, and UIDPlusData or
+ # AppendUIDData for +APPENDUID+ response codes.
+ #
+ # AppendUIDData and CopyUIDData are _mostly_ backward-compatible with
+ # UIDPlusData. Most applications should be able to upgrade with little
+ # or no changes.
+ #
+ # (Parser support for +UIDPLUS+ added in +v0.3.2+.)
+ #
+ # (Config option added in +v0.4.19+ and +v0.5.6+.)
+ #
+ # UIDPlusData will be removed in +v0.6+ and this config setting will
+ # be ignored.
+ #
+ # ==== Valid options
+ #
+ # [+true+ (original default)]
+ # ResponseParser only uses UIDPlusData.
+ #
+ # [+false+ (planned default for +v0.6+)]
+ # ResponseParser _only_ uses AppendUIDData and CopyUIDData.
+ attr_accessor :parser_use_deprecated_uidplus_data, type: [
+ true, false
+ ]
+
# Creates a new config object and initialize its attribute with +attrs+.
#
# If +parent+ is not given, the global config is used by default.
@@ -341,6 +367,7 @@ def defaults_hash
idle_response_timeout: 5,
sasl_ir: true,
responses_without_block: :silence_deprecation_warning,
+ parser_use_deprecated_uidplus_data: true,
).freeze
@global = default.new
@@ -349,6 +376,7 @@ def defaults_hash
version_defaults[0] = Config[0.4].dup.update(
sasl_ir: false,
+ parser_use_deprecated_uidplus_data: true,
).freeze
version_defaults[0.0] = Config[0]
version_defaults[0.1] = Config[0]
@@ -365,6 +393,7 @@ def defaults_hash
version_defaults[0.6] = Config[0.5].dup.update(
responses_without_block: :frozen_dup,
+ parser_use_deprecated_uidplus_data: false,
).freeze
version_defaults[:future] = Config[0.6]
diff --git a/lib/net/imap/response_data.rb b/lib/net/imap/response_data.rb
index e2335186..8013b4f2 100644
--- a/lib/net/imap/response_data.rb
+++ b/lib/net/imap/response_data.rb
@@ -5,6 +5,9 @@ class IMAP < Protocol
autoload :FetchData, "#{__dir__}/fetch_data"
autoload :SearchResult, "#{__dir__}/search_result"
autoload :SequenceSet, "#{__dir__}/sequence_set"
+ autoload :UIDPlusData, "#{__dir__}/uidplus_data"
+ autoload :AppendUIDData, "#{__dir__}/uidplus_data"
+ autoload :CopyUIDData, "#{__dir__}/uidplus_data"
# Net::IMAP::ContinuationRequest represents command continuation requests.
#
@@ -324,60 +327,6 @@ class ResponseCode < Struct.new(:name, :data)
# code data can take.
end
- # Net::IMAP::UIDPlusData represents the ResponseCode#data that accompanies
- # the +APPENDUID+ and +COPYUID+ response codes.
- #
- # See [[UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]].
- #
- # ==== Capability requirement
- #
- # The +UIDPLUS+ capability[rdoc-ref:Net::IMAP#capability] must be supported.
- # A server that supports +UIDPLUS+ should send a UIDPlusData object inside
- # every TaggedResponse returned by the append[rdoc-ref:Net::IMAP#append],
- # copy[rdoc-ref:Net::IMAP#copy], move[rdoc-ref:Net::IMAP#move], {uid
- # copy}[rdoc-ref:Net::IMAP#uid_copy], and {uid
- # move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the destination
- # mailbox reports +UIDNOTSTICKY+.
- #
- #--
- # TODO: support MULTIAPPEND
- #++
- #
- class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
- ##
- # method: uidvalidity
- # :call-seq: uidvalidity -> nonzero uint32
- #
- # The UIDVALIDITY of the destination mailbox.
-
- ##
- # method: source_uids
- # :call-seq: source_uids -> nil or an array of nonzero uint32
- #
- # The UIDs of the copied or moved messages.
- #
- # Note:: Returns +nil+ for Net::IMAP#append.
-
- ##
- # method: assigned_uids
- # :call-seq: assigned_uids -> an array of nonzero uint32
- #
- # The newly assigned UIDs of the copied, moved, or appended messages.
- #
- # Note:: This always returns an array, even when it contains only one UID.
-
- ##
- # :call-seq: uid_mapping -> nil or a hash
- #
- # Returns a hash mapping each source UID to the newly assigned destination
- # UID.
- #
- # Note:: Returns +nil+ for Net::IMAP#append.
- def uid_mapping
- source_uids&.zip(assigned_uids)&.to_h
- end
- end
-
# Net::IMAP::MailboxList represents contents of the LIST response,
# representing a single mailbox path.
#
diff --git a/lib/net/imap/response_parser.rb b/lib/net/imap/response_parser.rb
index f783810b..e9775c00 100644
--- a/lib/net/imap/response_parser.rb
+++ b/lib/net/imap/response_parser.rb
@@ -1867,11 +1867,10 @@ def charset__list
#
# n.b, uniqueid ⊂ uid-set. To avoid inconsistent return types, we always
# match uid_set even if that returns a single-member array.
- #
def resp_code_apnd__data
validity = number; SP!
dst_uids = uid_set # uniqueid ⊂ uid-set
- UIDPlusData.new(validity, nil, dst_uids)
+ AppendUID(validity, dst_uids)
end
# already matched: "COPYUID"
@@ -1881,6 +1880,17 @@ def resp_code_copy__data
validity = number; SP!
src_uids = uid_set; SP!
dst_uids = uid_set
+ CopyUID(validity, src_uids, dst_uids)
+ end
+
+ def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
+ def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end
+
+ # TODO: remove this code in the v0.6.0 release
+ def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids)
+ return unless config.parser_use_deprecated_uidplus_data
+ src_uids &&= src_uids.each_ordered_number.to_a
+ dst_uids = dst_uids.each_ordered_number.to_a
UIDPlusData.new(validity, src_uids, dst_uids)
end
@@ -2007,15 +2017,9 @@ def nparens__objectid; NIL? ? nil : parens__objectid end
# uniqueid = nz-number
# ; Strictly ascending
def uid_set
- token = match(T_NUMBER, T_ATOM)
- case token.symbol
- when T_NUMBER then [Integer(token.value)]
- when T_ATOM
- token.value.split(",").flat_map {|range|
- range = range.split(":").map {|uniqueid| Integer(uniqueid) }
- range.size == 1 ? range : Range.new(range.min, range.max).to_a
- }
- end
+ set = sequence_set
+ parse_error("uid-set cannot contain '*'") if set.include_star?
+ set
end
def nil_atom
diff --git a/lib/net/imap/uidplus_data.rb b/lib/net/imap/uidplus_data.rb
new file mode 100644
index 00000000..679b0b2b
--- /dev/null
+++ b/lib/net/imap/uidplus_data.rb
@@ -0,0 +1,326 @@
+# frozen_string_literal: true
+
+module Net
+ class IMAP < Protocol
+
+ # *NOTE:* UIDPlusData is deprecated and will be removed in the +0.6.0+
+ # release. To use AppendUIDData and CopyUIDData before +0.6.0+, set
+ # Config#parser_use_deprecated_uidplus_data to +false+.
+ #
+ # UIDPlusData represents the ResponseCode#data that accompanies the
+ # +APPENDUID+ and +COPYUID+ {response codes}[rdoc-ref:ResponseCode].
+ #
+ # A server that supports +UIDPLUS+ should send UIDPlusData in response to
+ # the append[rdoc-ref:Net::IMAP#append], copy[rdoc-ref:Net::IMAP#copy],
+ # move[rdoc-ref:Net::IMAP#move], {uid copy}[rdoc-ref:Net::IMAP#uid_copy],
+ # and {uid move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the
+ # destination mailbox reports +UIDNOTSTICKY+.
+ #
+ # Note that append[rdoc-ref:Net::IMAP#append], copy[rdoc-ref:Net::IMAP#copy]
+ # and {uid_copy}[rdoc-ref:Net::IMAP#uid_copy] return UIDPlusData in their
+ # TaggedResponse. But move[rdoc-ref:Net::IMAP#copy] and
+ # {uid_move}[rdoc-ref:Net::IMAP#uid_move] _should_ send UIDPlusData in an
+ # UntaggedResponse response before sending their TaggedResponse. However
+ # some servers do send UIDPlusData in the TaggedResponse for +MOVE+
+ # commands---this complies with the older +UIDPLUS+ specification but is
+ # discouraged by the +MOVE+ extension and disallowed by +IMAP4rev2+.
+ #
+ # == Required capability
+ # Requires either +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315]]
+ # or +IMAP4rev2+ capability.
+ #
+ class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
+ ##
+ # method: uidvalidity
+ # :call-seq: uidvalidity -> nonzero uint32
+ #
+ # The UIDVALIDITY of the destination mailbox.
+
+ ##
+ # method: source_uids
+ # :call-seq: source_uids -> nil or an array of nonzero uint32
+ #
+ # The UIDs of the copied or moved messages.
+ #
+ # Note:: Returns +nil+ for Net::IMAP#append.
+
+ ##
+ # method: assigned_uids
+ # :call-seq: assigned_uids -> an array of nonzero uint32
+ #
+ # The newly assigned UIDs of the copied, moved, or appended messages.
+ #
+ # Note:: This always returns an array, even when it contains only one UID.
+
+ ##
+ # :call-seq: uid_mapping -> nil or a hash
+ #
+ # Returns a hash mapping each source UID to the newly assigned destination
+ # UID.
+ #
+ # Note:: Returns +nil+ for Net::IMAP#append.
+ def uid_mapping
+ source_uids&.zip(assigned_uids)&.to_h
+ end
+ end
+
+ # This replaces the `Data.define` polyfill that's used by net-imap 0.5.
+ class Data_define__uidvalidity___assigned_uids_ # :no-doc:
+ attr_reader :uidvalidity, :assigned_uids
+
+ def self.[](...) new(...) end
+ def self.new(uidvalidity = (args = false; nil),
+ assigned_uids = nil,
+ **kwargs)
+ if kwargs.empty?
+ super(uidvalidity: uidvalidity, assigned_uids: assigned_uids)
+ elsif !args
+ super
+ else
+ raise ArgumentError, "sent both positional and keyword args"
+ end
+ end
+
+ def ==(other)
+ self.class == other.class &&
+ self.uidvalidity == other.uidvalidity &&
+ self.assigned_uids == other.assigned_uids
+ end
+
+ def eql?(other)
+ self.class.eql?(other.class) &&
+ self.uidvalidity.eql?(other.uidvalidity) &&
+ self.assigned_uids.eql?(other.assigned_uids)
+ end
+
+ def hash; [self.class, uidvalidity, assigned_uids].hash end
+
+ def initialize(uidvalidity:, assigned_uids:)
+ @uidvalidity = uidvalidity
+ @assigned_uids = assigned_uids
+ freeze
+ end
+ end
+
+ # >>>
+ # *NOTE:* AppendUIDData will replace UIDPlusData for +APPENDUID+ in the
+ # +0.6.0+ release. To use AppendUIDData before +0.6.0+, set
+ # Config#parser_use_deprecated_uidplus_data to +false+.
+ #
+ # AppendUIDData represents the ResponseCode#data that accompanies the
+ # +APPENDUID+ {response code}[rdoc-ref:ResponseCode].
+ #
+ # A server that supports +UIDPLUS+ (or +IMAP4rev2+) should send
+ # AppendUIDData inside every TaggedResponse returned by the
+ # append[rdoc-ref:Net::IMAP#append] command---unless the target mailbox
+ # reports +UIDNOTSTICKY+.
+ #
+ # == Required capability
+ # Requires either +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315]]
+ # or +IMAP4rev2+ capability.
+ class AppendUIDData < Data_define__uidvalidity___assigned_uids_
+ def initialize(uidvalidity:, assigned_uids:)
+ uidvalidity = Integer(uidvalidity)
+ assigned_uids = SequenceSet[assigned_uids]
+ NumValidator.ensure_nz_number(uidvalidity)
+ if assigned_uids.include_star?
+ raise DataFormatError, "uid-set cannot contain '*'"
+ end
+ super
+ end
+
+ ##
+ # attr_reader: uidvalidity
+ # :call-seq: uidvalidity -> nonzero uint32
+ #
+ # The UIDVALIDITY of the destination mailbox.
+
+ ##
+ # attr_reader: assigned_uids
+ #
+ # A SequenceSet with the newly assigned UIDs of the appended messages.
+
+ # Returns the number of messages that have been appended.
+ def size
+ assigned_uids.count_with_duplicates
+ end
+ end
+
+ # This replaces the `Data.define` polyfill that's used by net-imap 0.5.
+ class Data_define__uidvalidity___source_uids___assigned_uids_ # :no-doc:
+ attr_reader :uidvalidity, :source_uids, :assigned_uids
+
+ def self.[](...) new(...) end
+ def self.new(uidvalidity = (args = false; nil),
+ source_uids = nil,
+ assigned_uids = nil,
+ **kwargs)
+ if kwargs.empty?
+ super(uidvalidity: uidvalidity,
+ source_uids: source_uids,
+ assigned_uids: assigned_uids)
+ elsif !args
+ super(**kwargs)
+ else
+ raise ArgumentError, "sent both positional and keyword args"
+ end
+ end
+
+ def initialize(uidvalidity:, source_uids:, assigned_uids:)
+ @uidvalidity = uidvalidity
+ @source_uids = source_uids
+ @assigned_uids = assigned_uids
+ freeze
+ end
+
+ def ==(other)
+ self.class == other.class &&
+ self.uidvalidity == other.uidvalidity &&
+ self.source_uids == other.source_uids
+ self.assigned_uids == other.assigned_uids
+ end
+
+ def eql?(other)
+ self.class.eql?(other.class) &&
+ self.uidvalidity.eql?(other.uidvalidity) &&
+ self.source_uids.eql?(other.source_uids)
+ self.assigned_uids.eql?(other.assigned_uids)
+ end
+
+ def hash; [self.class, uidvalidity, source_uids, assigned_uids].hash end
+ end
+
+ # >>>
+ # *NOTE:* CopyUIDData will replace UIDPlusData for +COPYUID+ in the
+ # +0.6.0+ release. To use CopyUIDData before +0.6.0+, set
+ # Config#parser_use_deprecated_uidplus_data to +false+.
+ #
+ # CopyUIDData represents the ResponseCode#data that accompanies the
+ # +COPYUID+ {response code}[rdoc-ref:ResponseCode].
+ #
+ # A server that supports +UIDPLUS+ (or +IMAP4rev2+) should send CopyUIDData
+ # in response to
+ # copy[rdoc-ref:Net::IMAP#copy], {uid_copy}[rdoc-ref:Net::IMAP#uid_copy],
+ # move[rdoc-ref:Net::IMAP#copy], and {uid_move}[rdoc-ref:Net::IMAP#uid_move]
+ # commands---unless the destination mailbox reports +UIDNOTSTICKY+.
+ #
+ # Note that copy[rdoc-ref:Net::IMAP#copy] and
+ # {uid_copy}[rdoc-ref:Net::IMAP#uid_copy] return CopyUIDData in their
+ # TaggedResponse. But move[rdoc-ref:Net::IMAP#copy] and
+ # {uid_move}[rdoc-ref:Net::IMAP#uid_move] _should_ send CopyUIDData in an
+ # UntaggedResponse response before sending their TaggedResponse. However
+ # some servers do send CopyUIDData in the TaggedResponse for +MOVE+
+ # commands---this complies with the older +UIDPLUS+ specification but is
+ # discouraged by the +MOVE+ extension and disallowed by +IMAP4rev2+.
+ #
+ # == Required capability
+ # Requires either +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315]]
+ # or +IMAP4rev2+ capability.
+ class CopyUIDData < Data_define__uidvalidity___source_uids___assigned_uids_
+ def initialize(uidvalidity:, source_uids:, assigned_uids:)
+ uidvalidity = Integer(uidvalidity)
+ source_uids = SequenceSet[source_uids]
+ assigned_uids = SequenceSet[assigned_uids]
+ NumValidator.ensure_nz_number(uidvalidity)
+ if source_uids.include_star? || assigned_uids.include_star?
+ raise DataFormatError, "uid-set cannot contain '*'"
+ elsif source_uids.count_with_duplicates != assigned_uids.count_with_duplicates
+ raise DataFormatError, "mismatched uid-set sizes for %s and %s" % [
+ source_uids, assigned_uids
+ ]
+ end
+ super
+ end
+
+ ##
+ # attr_reader: uidvalidity
+ #
+ # The +UIDVALIDITY+ of the destination mailbox (a nonzero unsigned 32 bit
+ # integer).
+
+ ##
+ # attr_reader: source_uids
+ #
+ # A SequenceSet with the original UIDs of the copied or moved messages.
+
+ ##
+ # attr_reader: assigned_uids
+ #
+ # A SequenceSet with the newly assigned UIDs of the copied or moved
+ # messages.
+
+ # Returns the number of messages that have been copied or moved.
+ # source_uids and the assigned_uids will both the same number of UIDs.
+ def size
+ assigned_uids.count_with_duplicates
+ end
+
+ # :call-seq:
+ # assigned_uid_for(source_uid) -> uid
+ # self[source_uid] -> uid
+ #
+ # Returns the UID in the destination mailbox for the message that was
+ # copied from +source_uid+ in the source mailbox.
+ #
+ # This is the reverse of #source_uid_for.
+ #
+ # Related: source_uid_for, each_uid_pair, uid_mapping
+ def assigned_uid_for(source_uid)
+ idx = source_uids.find_ordered_index(source_uid) and
+ assigned_uids.ordered_at(idx)
+ end
+ alias :[] :assigned_uid_for
+
+ # :call-seq:
+ # source_uid_for(assigned_uid) -> uid
+ #
+ # Returns the UID in the source mailbox for the message that was copied to
+ # +assigned_uid+ in the source mailbox.
+ #
+ # This is the reverse of #assigned_uid_for.
+ #
+ # Related: assigned_uid_for, each_uid_pair, uid_mapping
+ def source_uid_for(assigned_uid)
+ idx = assigned_uids.find_ordered_index(assigned_uid) and
+ source_uids.ordered_at(idx)
+ end
+
+ # Yields a pair of UIDs for each copied message. The first is the
+ # message's UID in the source mailbox and the second is the UID in the
+ # destination mailbox.
+ #
+ # Returns an enumerator when no block is given.
+ #
+ # Please note the warning on uid_mapping before calling methods like
+ # +to_h+ or +to_a+ on the returned enumerator.
+ #
+ # Related: uid_mapping, assigned_uid_for, source_uid_for
+ def each_uid_pair
+ return enum_for(__method__) unless block_given?
+ source_uids.each_ordered_number.lazy
+ .zip(assigned_uids.each_ordered_number.lazy) do
+ |source_uid, assigned_uid|
+ yield source_uid, assigned_uid
+ end
+ end
+ alias each_pair each_uid_pair
+ alias each each_uid_pair
+
+ # :call-seq: uid_mapping -> hash
+ #
+ # Returns a hash mapping each source UID to the newly assigned destination
+ # UID.
+ #
+ # *Warning:* The hash that is created may consume _much_ more
+ # memory than the data used to create it. When handling responses from an
+ # untrusted server, check #size before calling this method.
+ #
+ # Related: each_uid_pair, assigned_uid_for, source_uid_for
+ def uid_mapping
+ each_uid_pair.to_h
+ end
+
+ end
+
+ end
+end
diff --git a/test/net/imap/test_imap_response_data.rb b/test/net/imap/test_imap_response_data.rb
deleted file mode 100644
index 0bee8b9a..00000000
--- a/test/net/imap/test_imap_response_data.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# frozen_string_literal: true
-
-require "net/imap"
-require "test/unit"
-
-class IMAPResponseDataTest < Test::Unit::TestCase
-
- def setup
- Net::IMAP.config.reset
- @do_not_reverse_lookup = Socket.do_not_reverse_lookup
- Socket.do_not_reverse_lookup = true
- end
-
- def teardown
- Socket.do_not_reverse_lookup = @do_not_reverse_lookup
- end
-
- def test_uidplus_copyuid__uid_mapping
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse(
- "A004 OK [copyUID 9999 20:19,500:495 92:97,101:100] Done\r\n"
- )
- code = response.data.code
- assert_equal(
- {
- 19 => 92,
- 20 => 93,
- 495 => 94,
- 496 => 95,
- 497 => 96,
- 498 => 97,
- 499 => 100,
- 500 => 101,
- },
- code.data.uid_mapping
- )
- end
-
- def test_thread_member_to_sequence_set
- # copied from the fourth example in RFC5256: (3 6 (4 23)(44 7 96))
- thmember = Net::IMAP::ThreadMember.method :new
- thread = thmember.(3, [
- thmember.(6, [
- thmember.(4, [
- thmember.(23, [])
- ]),
- thmember.(44, [
- thmember.(7, [
- thmember.(96, [])
- ])
- ])
- ])
- ])
- expected = Net::IMAP::SequenceSet.new("3:4,6:7,23,44,96")
- assert_equal(expected, thread.to_sequence_set)
- end
-
-end
diff --git a/test/net/imap/test_imap_response_parser.rb b/test/net/imap/test_imap_response_parser.rb
index aef6f1da..e85b0b4e 100644
--- a/test/net/imap/test_imap_response_parser.rb
+++ b/test/net/imap/test_imap_response_parser.rb
@@ -193,4 +193,80 @@ def test_fetch_binary_and_binary_size
Net::IMAP.debug = debug
end
+ test "APPENDUID with '*'" do
+ parser = Net::IMAP::ResponseParser.new
+ assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set cannot contain '\*'/ do
+ parser.parse(
+ "A004 OK [appendUID 1 1:*] Done\r\n"
+ )
+ end
+ end
+
+ test "APPENDUID with parser_use_deprecated_uidplus_data = true" do
+ parser = Net::IMAP::ResponseParser.new(config: {
+ parser_use_deprecated_uidplus_data: true,
+ })
+ response = parser.parse("A004 OK [APPENDUID 1 101:200] Done\r\n")
+ uidplus = response.data.code.data
+ assert_instance_of Net::IMAP::UIDPlusData, uidplus
+ assert_equal 100, uidplus.assigned_uids.size
+ end
+
+ test "APPENDUID with parser_use_deprecated_uidplus_data = false" do
+ parser = Net::IMAP::ResponseParser.new(config: {
+ parser_use_deprecated_uidplus_data: false,
+ })
+ response = parser.parse("A004 OK [APPENDUID 1 10] Done\r\n")
+ assert_instance_of Net::IMAP::AppendUIDData, response.data.code.data
+ end
+
+ test "COPYUID with backwards ranges" do
+ parser = Net::IMAP::ResponseParser.new
+ response = parser.parse(
+ "A004 OK [copyUID 9999 20:19,500:495 92:97,101:100] Done\r\n"
+ )
+ code = response.data.code
+ assert_equal(
+ {
+ 19 => 92,
+ 20 => 93,
+ 495 => 94,
+ 496 => 95,
+ 497 => 96,
+ 498 => 97,
+ 499 => 100,
+ 500 => 101,
+ },
+ code.data.uid_mapping
+ )
+ end
+
+ test "COPYUID with '*'" do
+ parser = Net::IMAP::ResponseParser.new
+ assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set cannot contain '\*'/ do
+ parser.parse(
+ "A004 OK [copyUID 1 1:* 1:*] Done\r\n"
+ )
+ end
+ end
+
+ test "COPYUID with parser_use_deprecated_uidplus_data = true" do
+ parser = Net::IMAP::ResponseParser.new(config: {
+ parser_use_deprecated_uidplus_data: true,
+ })
+ response = parser.parse("A004 OK [copyUID 1 101:200 1:100] Done\r\n")
+ uidplus = response.data.code.data
+ assert_instance_of Net::IMAP::UIDPlusData, uidplus
+ assert_equal 100, uidplus.assigned_uids.size
+ assert_equal 100, uidplus.source_uids.size
+ end
+
+ test "COPYUID with parser_use_deprecated_uidplus_data = false" do
+ parser = Net::IMAP::ResponseParser.new(config: {
+ parser_use_deprecated_uidplus_data: false,
+ })
+ response = parser.parse("A004 OK [COPYUID 1 101 1] Done\r\n")
+ assert_instance_of Net::IMAP::CopyUIDData, response.data.code.data
+ end
+
end
diff --git a/test/net/imap/test_thread_member.rb b/test/net/imap/test_thread_member.rb
new file mode 100644
index 00000000..afa933fb
--- /dev/null
+++ b/test/net/imap/test_thread_member.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require "net/imap"
+require "test/unit"
+
+class ThreadMemberTest < Test::Unit::TestCase
+
+ test "#to_sequence_set" do
+ # copied from the fourth example in RFC5256: (3 6 (4 23)(44 7 96))
+ thmember = Net::IMAP::ThreadMember.method :new
+ thread = thmember.(3, [
+ thmember.(6, [
+ thmember.(4, [
+ thmember.(23, [])
+ ]),
+ thmember.(44, [
+ thmember.(7, [
+ thmember.(96, [])
+ ])
+ ])
+ ])
+ ])
+ expected = Net::IMAP::SequenceSet.new("3:4,6:7,23,44,96")
+ assert_equal(expected, thread.to_sequence_set)
+ end
+
+end
diff --git a/test/net/imap/test_uidplus_data.rb b/test/net/imap/test_uidplus_data.rb
new file mode 100644
index 00000000..0088c346
--- /dev/null
+++ b/test/net/imap/test_uidplus_data.rb
@@ -0,0 +1,232 @@
+# frozen_string_literal: true
+
+require "net/imap"
+require "test/unit"
+
+class TestUIDPlusData < Test::Unit::TestCase
+
+ test "#uid_mapping with sorted source_uids" do
+ uidplus = Net::IMAP::UIDPlusData.new(
+ 1, [19, 20, *(495..500)], [*(92..97), 100, 101],
+ )
+ assert_equal(
+ {
+ 19 => 92,
+ 20 => 93,
+ 495 => 94,
+ 496 => 95,
+ 497 => 96,
+ 498 => 97,
+ 499 => 100,
+ 500 => 101,
+ },
+ uidplus.uid_mapping
+ )
+ end
+
+ test "#uid_mapping for with source_uids in unsorted order" do
+ uidplus = Net::IMAP::UIDPlusData.new(
+ 1, [*(495..500), 19, 20], [*(92..97), 100, 101],
+ )
+ assert_equal(
+ {
+ 495 => 92,
+ 496 => 93,
+ 497 => 94,
+ 498 => 95,
+ 499 => 96,
+ 500 => 97,
+ 19 => 100,
+ 20 => 101,
+ },
+ uidplus.uid_mapping
+ )
+ end
+
+end
+
+class TestAppendUIDData < Test::Unit::TestCase
+ # alias for convenience
+ AppendUIDData = Net::IMAP::AppendUIDData
+ SequenceSet = Net::IMAP::SequenceSet
+ DataFormatError = Net::IMAP::DataFormatError
+ UINT32_MAX = 2**32 - 1
+
+ test "#uidvalidity must be valid nz-number" do
+ assert_equal 1, AppendUIDData.new(1, 99).uidvalidity
+ assert_equal UINT32_MAX, AppendUIDData.new(UINT32_MAX, 1).uidvalidity
+ assert_raise DataFormatError do AppendUIDData.new(0, 1) end
+ assert_raise DataFormatError do AppendUIDData.new(2**32, 1) end
+ end
+
+ test "#assigned_uids must be a valid uid-set" do
+ assert_equal SequenceSet[1], AppendUIDData.new(99, "1").assigned_uids
+ assert_equal SequenceSet[1..9], AppendUIDData.new(1, "1:9").assigned_uids
+ assert_equal(SequenceSet[UINT32_MAX],
+ AppendUIDData.new(1, UINT32_MAX.to_s).assigned_uids)
+ assert_raise DataFormatError do AppendUIDData.new(1, 0) end
+ assert_raise DataFormatError do AppendUIDData.new(1, "*") end
+ assert_raise DataFormatError do AppendUIDData.new(1, "1:*") end
+ end
+
+ test "#size returns the number of UIDs" do
+ assert_equal(10, AppendUIDData.new(1, "1:10").size)
+ assert_equal(4_000_000_000, AppendUIDData.new(1, 1..4_000_000_000).size)
+ end
+
+ test "#assigned_uids is converted to SequenceSet" do
+ assert_equal SequenceSet[1], AppendUIDData.new(99, "1").assigned_uids
+ assert_equal SequenceSet[1..4], AppendUIDData.new(1, [1, 2, 3, 4]).assigned_uids
+ end
+
+end
+
+class TestCopyUIDData < Test::Unit::TestCase
+ # alias for convenience
+ CopyUIDData = Net::IMAP::CopyUIDData
+ SequenceSet = Net::IMAP::SequenceSet
+ DataFormatError = Net::IMAP::DataFormatError
+ UINT32_MAX = 2**32 - 1
+
+ test "#uidvalidity must be valid nz-number" do
+ assert_equal 1, CopyUIDData.new(1, 99, 99).uidvalidity
+ assert_equal UINT32_MAX, CopyUIDData.new(UINT32_MAX, 1, 1).uidvalidity
+ assert_raise DataFormatError do CopyUIDData.new(0, 1, 1) end
+ assert_raise DataFormatError do CopyUIDData.new(2**32, 1, 1) end
+ end
+
+ test "#source_uids must be valid uid-set" do
+ assert_equal SequenceSet[1], CopyUIDData.new(99, "1", 99).source_uids
+ assert_equal SequenceSet[5..8], CopyUIDData.new(1, 5..8, 1..4).source_uids
+ assert_equal(SequenceSet[UINT32_MAX],
+ CopyUIDData.new(1, UINT32_MAX.to_s, 1).source_uids)
+ assert_raise DataFormatError do CopyUIDData.new(99, nil, 99) end
+ assert_raise DataFormatError do CopyUIDData.new(1, 0, 1) end
+ assert_raise DataFormatError do CopyUIDData.new(1, "*", 1) end
+ end
+
+ test "#assigned_uids must be a valid uid-set" do
+ assert_equal SequenceSet[1], CopyUIDData.new(99, 1, "1").assigned_uids
+ assert_equal SequenceSet[1..9], CopyUIDData.new(1, 1..9, "1:9").assigned_uids
+ assert_equal(SequenceSet[UINT32_MAX],
+ CopyUIDData.new(1, 1, UINT32_MAX.to_s).assigned_uids)
+ assert_raise DataFormatError do CopyUIDData.new(1, 1, 0) end
+ assert_raise DataFormatError do CopyUIDData.new(1, 1, "*") end
+ assert_raise DataFormatError do CopyUIDData.new(1, 1, "1:*") end
+ end
+
+ test "#size returns the number of UIDs" do
+ assert_equal(10, CopyUIDData.new(1, "9,8,7,6,1:5,10", "1:10").size)
+ assert_equal(4_000_000_000,
+ CopyUIDData.new(
+ 1, "2000000000:4000000000,1:1999999999", 1..4_000_000_000
+ ).size)
+ end
+
+ test "#source_uids and #assigned_uids must be same size" do
+ assert_raise DataFormatError do CopyUIDData.new(1, 1..5, 1) end
+ assert_raise DataFormatError do CopyUIDData.new(1, 1, 1..5) end
+ end
+
+ test "#source_uids is converted to SequenceSet" do
+ assert_equal SequenceSet[1], CopyUIDData.new(99, "1", 99).source_uids
+ assert_equal SequenceSet[5, 6, 7, 8], CopyUIDData.new(1, 5..8, 1..4).source_uids
+ end
+
+ test "#assigned_uids is converted to SequenceSet" do
+ assert_equal SequenceSet[1], CopyUIDData.new(99, 1, "1").assigned_uids
+ assert_equal SequenceSet[1, 2, 3, 4], CopyUIDData.new(1, "1:4", 1..4).assigned_uids
+ end
+
+ test "#uid_mapping maps source_uids to assigned_uids" do
+ uidplus = CopyUIDData.new(9999, "20:19,500:495", "92:97,101:100")
+ assert_equal(
+ {
+ 19 => 92,
+ 20 => 93,
+ 495 => 94,
+ 496 => 95,
+ 497 => 96,
+ 498 => 97,
+ 499 => 100,
+ 500 => 101,
+ },
+ uidplus.uid_mapping
+ )
+ end
+
+ test "#uid_mapping for with source_uids in unsorted order" do
+ uidplus = CopyUIDData.new(1, "495:500,20:19", "92:97,101:100")
+ assert_equal(
+ {
+ 495 => 92,
+ 496 => 93,
+ 497 => 94,
+ 498 => 95,
+ 499 => 96,
+ 500 => 97,
+ 19 => 100,
+ 20 => 101,
+ },
+ uidplus.uid_mapping
+ )
+ end
+
+ test "#assigned_uid_for(source_uid)" do
+ uidplus = CopyUIDData.new(1, "495:500,20:19", "92:97,101:100")
+ assert_equal 92, uidplus.assigned_uid_for(495)
+ assert_equal 93, uidplus.assigned_uid_for(496)
+ assert_equal 94, uidplus.assigned_uid_for(497)
+ assert_equal 95, uidplus.assigned_uid_for(498)
+ assert_equal 96, uidplus.assigned_uid_for(499)
+ assert_equal 97, uidplus.assigned_uid_for(500)
+ assert_equal 100, uidplus.assigned_uid_for( 19)
+ assert_equal 101, uidplus.assigned_uid_for( 20)
+ end
+
+ test "#[](source_uid)" do
+ uidplus = CopyUIDData.new(1, "495:500,20:19", "92:97,101:100")
+ assert_equal 92, uidplus[495]
+ assert_equal 93, uidplus[496]
+ assert_equal 94, uidplus[497]
+ assert_equal 95, uidplus[498]
+ assert_equal 96, uidplus[499]
+ assert_equal 97, uidplus[500]
+ assert_equal 100, uidplus[ 19]
+ assert_equal 101, uidplus[ 20]
+ end
+
+ test "#source_uid_for(assigned_uid)" do
+ uidplus = CopyUIDData.new(1, "495:500,20:19", "92:97,101:100")
+ assert_equal 495, uidplus.source_uid_for( 92)
+ assert_equal 496, uidplus.source_uid_for( 93)
+ assert_equal 497, uidplus.source_uid_for( 94)
+ assert_equal 498, uidplus.source_uid_for( 95)
+ assert_equal 499, uidplus.source_uid_for( 96)
+ assert_equal 500, uidplus.source_uid_for( 97)
+ assert_equal 19, uidplus.source_uid_for(100)
+ assert_equal 20, uidplus.source_uid_for(101)
+ end
+
+ test "#each_uid_pair" do
+ uidplus = CopyUIDData.new(1, "495:500,20:19", "92:97,101:100")
+ expected = {
+ 495 => 92,
+ 496 => 93,
+ 497 => 94,
+ 498 => 95,
+ 499 => 96,
+ 500 => 97,
+ 19 => 100,
+ 20 => 101,
+ }
+ actual = {}
+ uidplus.each_uid_pair do |src, dst| actual[src] = dst end
+ assert_equal expected, actual
+ assert_equal expected, uidplus.each_uid_pair.to_h
+ assert_equal expected.to_a, uidplus.each_uid_pair.to_a
+ assert_equal expected, uidplus.each_pair.to_h
+ assert_equal expected, uidplus.each.to_h
+ end
+
+end