Skip to content

Commit

Permalink
🚧✨ Support VANISHED response to #expunge
Browse files Browse the repository at this point in the history
Both the `QRESYNC` and `UIDONLY` extensions replace `EXPUNGE` responses
with `VANISHED` responses.  This updates the #expunge and #uid_expunge
commands to return VanishedData, rather than a (misleading) empty array.
  • Loading branch information
nevans committed Dec 16, 2024
1 parent a82c1d0 commit b6553f2
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 12 deletions.
58 changes: 47 additions & 11 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1889,18 +1889,39 @@ 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.
#
# 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---<em>not message sequence
# numbers</em>.
#
# *NOTE:* Any unhandled +VANISHED+ #responses without the +EARLIER+ modifier
# will be merged into the VanishedData and deleted from #responses. This is
# consistent with how Net::IMAP handles +EXPUNGE+ responses. Unhandled
# <tt>VANISHED (EARLIER)</tt> responses will _not_ be merged or returned.
#
# *NOTE:* When no messages are expunged, Net::IMAP currently returns an
# empty array, regardless of which extensions have been enabled. In the
# future, an empty VanishedData will be returned instead.
def expunge
synchronize do
send_command("EXPUNGE")
clear_responses("EXPUNGE")
end
expunge_internal("EXPUNGE")
end

# call-seq:
# uid_expunge -> array of message sequence numbers
# uid_expunge -> 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 <tt>\\Deleted</tt>
Expand All @@ -1924,13 +1945,13 @@ def 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:
Expand Down Expand Up @@ -3261,6 +3282,21 @@ def enforce_logindisabled?
end
end

def expunge_internal(...)
synchronize do
send_command(...)
vanished_array = extract_responses("VANISHED") { !_1.earlier? }
if vanished_array.empty?
clear_responses("EXPUNGE")
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
Expand Down
79 changes: 78 additions & 1 deletion test/net/imap/test_imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand All @@ -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|
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit b6553f2

Please sign in to comment.