diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 3534b573..37fe6c69 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -1889,48 +1889,64 @@ def unselect send_command("UNSELECT") end + # call-seq: + # expunge -> array of message sequence numbers + # expunge -> VanishedData of UIDs + # # Sends an {EXPUNGE command [IMAP4rev1 §6.4.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.3] - # Sends a EXPUNGE command to permanently remove from the currently - # selected mailbox all messages that have the \Deleted flag set. + # to permanently remove all messages with the +\Deleted+ flag from the + # currently selected mailbox. + # + # Returns either an array of expunged message sequence numbers or + # (when the appropriate capability is enabled) VanishedData of expunged + # UIDs. Previously unhandled +EXPUNGE+ or +VANISHED+ responses are merged + # with the direct response to this command. VANISHED (EARLIER) + # responses will _not_ be merged. + # + # When no messages have been expunged, an empty array is returned, + # regardless of which extensions are enabled. In a future release, an empty + # VanishedData may be returned, based on the currently enabled extensions. # # Related: #uid_expunge + # + # ==== Capabilities + # + # When either QRESYNC[https://tools.ietf.org/html/rfc7162] or + # UIDONLY[https://tools.ietf.org/html/rfc9586] are enabled, #expunge + # returns VanishedData, which contains UIDs---not message sequence + # numbers. def expunge - synchronize do - send_command("EXPUNGE") - clear_responses("EXPUNGE") - end + expunge_internal("EXPUNGE") end + # call-seq: + # uid_expunge{uid_set) -> array of message sequence numbers + # uid_expunge{uid_set) -> VanishedData of UIDs + # # Sends a {UID EXPUNGE command [RFC4315 §2.1]}[https://www.rfc-editor.org/rfc/rfc4315#section-2.1] # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9] # to permanently remove all messages that have both the \\Deleted # flag set and a UID that is included in +uid_set+. # + # Returns the same result type as #expunge. + # # By using #uid_expunge instead of #expunge when resynchronizing with # the server, the client can ensure that it does not inadvertantly # remove any messages that have been marked as \\Deleted by other # clients between the time that the client was last connected and # the time the client resynchronizes. # - # *Note:* - # >>> - # Although the command takes a set of UIDs for its argument, the - # server still returns regular EXPUNGE responses, which contain - # a sequence number. These will be deleted from - # #responses and this method returns them as an array of - # sequence number integers. - # # Related: #expunge # # ==== Capabilities # - # The server's capabilities must include +UIDPLUS+ + # The server's capabilities must include either +IMAP4rev2+ or +UIDPLUS+ # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]]. + # + # Otherwise, #uid_expunge is updated by extensions in the same way as + # #expunge. def uid_expunge(uid_set) - synchronize do - send_command("UID EXPUNGE", SequenceSet.new(uid_set)) - clear_responses("EXPUNGE") - end + expunge_internal("UID EXPUNGE", SequenceSet.new(uid_set)) end # :call-seq: @@ -3261,6 +3277,22 @@ def enforce_logindisabled? end end + def expunge_internal(...) + synchronize do + send_command(...) + expunged_array = clear_responses("EXPUNGE") + vanished_array = extract_responses("VANISHED") { !_1.earlier? } + if vanished_array.empty? + expunged_array + elsif vanished_array.length == 1 + vanished_array.first + else + merged_uids = SequenceSet[*vanished_array.map(&:uids)] + VanishedData[uids: merged_uids, earlier: false] + end + end + end + RETURN_WHOLE = /\ARETURN\z/i RETURN_START = /\ARETURN\b/i private_constant :RETURN_WHOLE, :RETURN_START diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb index 73e630f2..25dfc013 100644 --- a/test/net/imap/test_imap.rb +++ b/test/net/imap/test_imap.rb @@ -1016,7 +1016,7 @@ def test_id end end - def test_uidplus_uid_expunge + test "#uid_expunge with EXPUNGE responses" do with_fake_server(select: "INBOX", extensions: %i[UIDPLUS]) do |server, imap| server.on "UID EXPUNGE" do |resp| @@ -1032,6 +1032,24 @@ def test_uidplus_uid_expunge end end + test "#uid_expunge with VANISHED response" do + with_fake_server(select: "INBOX", + extensions: %i[UIDPLUS]) do |server, imap| + server.on "UID EXPUNGE" do |resp| + resp.untagged("VANISHED 1001,1003") + resp.done_ok + end + response = imap.uid_expunge(1000..1003) + cmd = server.commands.pop + assert_equal ["UID EXPUNGE", "1000:1003"], [cmd.name, cmd.args] + assert_equal( + Net::IMAP::VanishedData[uids: [1001, 1003], earlier: false], + response + ) + assert_equal([], imap.clear_responses("VANISHED")) + end + end + def test_uidplus_appenduid with_fake_server(select: "INBOX", extensions: %i[UIDPLUS]) do |server, imap| @@ -1168,6 +1186,65 @@ def test_enable end end + test "#expunge with EXPUNGE responses" do + with_fake_server(select: "INBOX") do |server, imap| + server.on "EXPUNGE" do |resp| + resp.untagged("1 EXPUNGE") + resp.untagged("1 EXPUNGE") + resp.untagged("99 EXPUNGE") + resp.done_ok + end + response = imap.expunge + cmd = server.commands.pop + assert_equal ["EXPUNGE", nil], [cmd.name, cmd.args] + assert_equal [1, 1, 99], response + assert_equal [], imap.clear_responses("EXPUNGED") + end + end + + test "#expunge with a VANISHED response" do + with_fake_server(select: "INBOX") do |server, imap| + server.on "EXPUNGE" do |resp| + resp.untagged("VANISHED 15:456") + resp.done_ok + end + response = imap.expunge + cmd = server.commands.pop + assert_equal ["EXPUNGE", nil], [cmd.name, cmd.args] + assert_equal( + Net::IMAP::VanishedData[uids: [15..456], earlier: false], + response + ) + assert_equal([], imap.clear_responses("VANISHED")) + end + end + + test "#expunge with multiple VANISHED responses" do + with_fake_server(select: "INBOX") do |server, imap| + server.unsolicited("VANISHED 86") + server.on "EXPUNGE" do |resp| + resp.untagged("VANISHED (EARLIER) 1:5,99,123") + resp.untagged("VANISHED 15,456") + resp.untagged("VANISHED (EARLIER) 987,1001") + resp.done_ok + end + response = imap.expunge + cmd = server.commands.pop + assert_equal ["EXPUNGE", nil], [cmd.name, cmd.args] + assert_equal( + Net::IMAP::VanishedData[uids: [15, 86, 456], earlier: false], + response + ) + assert_equal( + [ + Net::IMAP::VanishedData[uids: [1..5, 99, 123], earlier: true], + Net::IMAP::VanishedData[uids: [987, 1001], earlier: true], + ], + imap.clear_responses("VANISHED") + ) + end + end + def test_close with_fake_server(select: "inbox") do |server, imap| resp = imap.close