Skip to content

Commit c7b54bf

Browse files
committed
🚧✨ Support VANISHED response to #expunge
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.
1 parent 74efb58 commit c7b54bf

File tree

2 files changed

+125
-12
lines changed

2 files changed

+125
-12
lines changed

‎lib/net/imap.rb‎

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,18 +1885,39 @@ def unselect
18851885
send_command("UNSELECT")
18861886
end
18871887

1888+
# call-seq:
1889+
# expunge -> array of message sequence numbers
1890+
# expunge -> VanishedData of UIDs
1891+
#
18881892
# Sends an {EXPUNGE command [IMAP4rev1 §6.4.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.3]
1889-
# Sends a EXPUNGE command to permanently remove from the currently
1890-
# selected mailbox all messages that have the \Deleted flag set.
1893+
# to permanently remove all messages with the +\Deleted+ flag from the
1894+
# currently selected mailbox.
18911895
#
18921896
# Related: #uid_expunge
1897+
#
1898+
# ===== Capabilities
1899+
#
1900+
# When either QRESYNC[https://tools.ietf.org/html/rfc7162] or
1901+
# UIDONLY[https://tools.ietf.org/html/rfc9586] are enabled, #expunge
1902+
# returns VanishedData, which contains UIDs---<em>not message sequence
1903+
# numbers</em>.
1904+
#
1905+
# *NOTE:* Any unhandled +VANISHED+ #responses without the +EARLIER+ modifier
1906+
# will be merged into the VanishedData and deleted from #responses. This is
1907+
# consistent with how Net::IMAP handles +EXPUNGE+ responses. Unhandled
1908+
# <tt>VANISHED (EARLIER)</tt> responses will _not_ be merged or returned.
1909+
#
1910+
# *NOTE:* When no messages are expunged, Net::IMAP currently returns an
1911+
# empty array, regardless of which extensions have been enabled. In the
1912+
# future, an empty VanishedData will be returned instead.
18931913
def expunge
1894-
synchronize do
1895-
send_command("EXPUNGE")
1896-
clear_responses("EXPUNGE")
1897-
end
1914+
expunge_internal("EXPUNGE")
18981915
end
18991916

1917+
# call-seq:
1918+
# uid_expunge -> array of message sequence numbers
1919+
# uid_expunge -> VanishedData of UIDs
1920+
#
19001921
# Sends a {UID EXPUNGE command [RFC4315 §2.1]}[https://www.rfc-editor.org/rfc/rfc4315#section-2.1]
19011922
# {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9]
19021923
# to permanently remove all messages that have both the <tt>\\Deleted</tt>
@@ -1920,13 +1941,13 @@ def expunge
19201941
#
19211942
# ===== Capabilities
19221943
#
1923-
# The server's capabilities must include +UIDPLUS+
1944+
# The server's capabilities must include either +IMAP4rev2+ or +UIDPLUS+
19241945
# [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]].
1946+
#
1947+
# Otherwise, #uid_expunge is updated by extensions in the same way as
1948+
# #expunge.
19251949
def uid_expunge(uid_set)
1926-
synchronize do
1927-
send_command("UID EXPUNGE", SequenceSet.new(uid_set))
1928-
clear_responses("EXPUNGE")
1929-
end
1950+
expunge_internal("UID EXPUNGE", SequenceSet.new(uid_set))
19301951
end
19311952

19321953
# :call-seq:
@@ -3131,6 +3152,21 @@ def enforce_logindisabled?
31313152
end
31323153
end
31333154

3155+
def expunge_internal(...)
3156+
synchronize do
3157+
send_command(...)
3158+
vanished_array = extract_responses("VANISHED") { !_1.earlier? }
3159+
if vanished_array.empty?
3160+
clear_responses("EXPUNGE")
3161+
elsif vanished_array.length == 1
3162+
vanished_array.first
3163+
else
3164+
merged_uids = SequenceSet[*vanished_array.map(&:uids)]
3165+
VanishedData[uids: merged_uids, earlier: false]
3166+
end
3167+
end
3168+
end
3169+
31343170
def search_internal(cmd, keys, charset = nil)
31353171
keys = normalize_searching_criteria(keys)
31363172
args = charset ? ["CHARSET", charset, *keys] : keys

‎test/net/imap/test_imap.rb‎

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1014,7 +1014,7 @@ def test_id
10141014
end
10151015
end
10161016

1017-
def test_uidplus_uid_expunge
1017+
test "#uid_expunge with EXPUNGE responses" do
10181018
with_fake_server(select: "INBOX",
10191019
extensions: %i[UIDPLUS]) do |server, imap|
10201020
server.on "UID EXPUNGE" do |resp|
@@ -1030,6 +1030,24 @@ def test_uidplus_uid_expunge
10301030
end
10311031
end
10321032

1033+
test "#uid_expunge with VANISHED response" do
1034+
with_fake_server(select: "INBOX",
1035+
extensions: %i[UIDPLUS]) do |server, imap|
1036+
server.on "UID EXPUNGE" do |resp|
1037+
resp.untagged("VANISHED 1001,1003")
1038+
resp.done_ok
1039+
end
1040+
response = imap.uid_expunge(1000..1003)
1041+
cmd = server.commands.pop
1042+
assert_equal ["UID EXPUNGE", "1000:1003"], [cmd.name, cmd.args]
1043+
assert_equal(
1044+
Net::IMAP::VanishedData[uids: [1001, 1003], earlier: false],
1045+
response
1046+
)
1047+
assert_equal([], imap.clear_responses("VANISHED"))
1048+
end
1049+
end
1050+
10331051
def test_uidplus_appenduid
10341052
with_fake_server(select: "INBOX",
10351053
extensions: %i[UIDPLUS]) do |server, imap|
@@ -1166,6 +1184,65 @@ def test_enable
11661184
end
11671185
end
11681186

1187+
test "#expunge with EXPUNGE responses" do
1188+
with_fake_server(select: "INBOX") do |server, imap|
1189+
server.on "EXPUNGE" do |resp|
1190+
resp.untagged("1 EXPUNGE")
1191+
resp.untagged("1 EXPUNGE")
1192+
resp.untagged("99 EXPUNGE")
1193+
resp.done_ok
1194+
end
1195+
response = imap.expunge
1196+
cmd = server.commands.pop
1197+
assert_equal ["EXPUNGE", nil], [cmd.name, cmd.args]
1198+
assert_equal [1, 1, 99], response
1199+
assert_equal [], imap.clear_responses("EXPUNGED")
1200+
end
1201+
end
1202+
1203+
test "#expunge with a VANISHED response" do
1204+
with_fake_server(select: "INBOX") do |server, imap|
1205+
server.on "EXPUNGE" do |resp|
1206+
resp.untagged("VANISHED 15:456")
1207+
resp.done_ok
1208+
end
1209+
response = imap.expunge
1210+
cmd = server.commands.pop
1211+
assert_equal ["EXPUNGE", nil], [cmd.name, cmd.args]
1212+
assert_equal(
1213+
Net::IMAP::VanishedData[uids: [15..456], earlier: false],
1214+
response
1215+
)
1216+
assert_equal([], imap.clear_responses("VANISHED"))
1217+
end
1218+
end
1219+
1220+
test "#expunge with multiple VANISHED responses" do
1221+
with_fake_server(select: "INBOX") do |server, imap|
1222+
server.unsolicited("VANISHED 86")
1223+
server.on "EXPUNGE" do |resp|
1224+
resp.untagged("VANISHED (EARLIER) 1:5,99,123")
1225+
resp.untagged("VANISHED 15,456")
1226+
resp.untagged("VANISHED (EARLIER) 987,1001")
1227+
resp.done_ok
1228+
end
1229+
response = imap.expunge
1230+
cmd = server.commands.pop
1231+
assert_equal ["EXPUNGE", nil], [cmd.name, cmd.args]
1232+
assert_equal(
1233+
Net::IMAP::VanishedData[uids: [15, 86, 456], earlier: false],
1234+
response
1235+
)
1236+
assert_equal(
1237+
[
1238+
Net::IMAP::VanishedData[uids: [1..5, 99, 123], earlier: true],
1239+
Net::IMAP::VanishedData[uids: [987, 1001], earlier: true],
1240+
],
1241+
imap.clear_responses("VANISHED")
1242+
)
1243+
end
1244+
end
1245+
11691246
def test_close
11701247
with_fake_server(select: "inbox") do |server, imap|
11711248
resp = imap.close

0 commit comments

Comments
 (0)