diff --git a/lib/net/imap.rb b/lib/net/imap.rb
index 4ac9d7a8..874b3a04 100644
--- a/lib/net/imap.rb
+++ b/lib/net/imap.rb
@@ -2632,6 +2632,7 @@ def fetch(...)
# :call-seq:
# uid_fetch(set, attr, changedsince: nil, partial: nil) -> array of FetchData (or UIDFetchData)
+ # uid_fetch(set, attr, changedsince:, vanished: true, partial: nil) -> array of VanishedData and FetchData (or UIDFetchData)
#
# Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
# to retrieve data associated with a message in the mailbox.
@@ -2648,6 +2649,22 @@ def fetch(...)
#
# +changedsince+ (optional) behaves the same as with #fetch.
#
+ # +vanished+ can be used to request a list all of the message UIDs in +set+
+ # that have been expunged since +changedsince+. Setting +vanished+ to true
+ # prepends a VanishedData object to the returned array. If the server does
+ # not return a +VANISHED+ response, an empty VanishedData object will still
+ # be added.
+ # The +QRESYNC+ capabability must be enabled.
+ # {[RFC7162]}[https://rfc-editor.org/rfc/rfc7162]
+ #
+ # For example:
+ #
+ # imap.enable("QRESYNC") # must enable before selecting the mailbox
+ # imap.select("INBOX")
+ # # first value in the array is VanishedData
+ # vanished, *fetched = imap.uid_fetch(301..500, %w[flags],
+ # changedsince: 12345, vanished: true)
+ #
# +partial+ is an optional range to limit the number of results returned.
# It's useful when +set+ contains an unknown number of messages.
# 1..500 returns the first 500 messages in +set+ (in mailbox
@@ -2680,6 +2697,9 @@ def fetch(...)
#
# ==== Capabilities
#
+ # QRESYNC[https://www.rfc-editor.org/rfc/rfc7162] must be enabled in order
+ # to use the +vanished+ fetch modifier.
+ #
# The server's capabilities must include +PARTIAL+
# {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394] in order to use the
# +partial+ argument.
@@ -2959,9 +2979,8 @@ def uid_thread(algorithm, search_keys, charset)
# See {[RFC7162 §3.1]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1].
#
# [+QRESYNC+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]]
- # *NOTE:* Enabling QRESYNC will replace +EXPUNGE+ with +VANISHED+, but
- # the extension arguments to #select, #examine, and #uid_fetch are not
- # supported yet.
+ # *NOTE:* The +QRESYNC+ argument to #select and #examine is not supported
+ # yet.
#
# Adds quick resynchronization options to #select, #examine, and
# #uid_fetch. +QRESYNC+ _must_ be explicitly enabled before using any of
@@ -3680,19 +3699,23 @@ def search_internal(cmd, ...)
end
end
- def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
- if partial && !cmd.start_with?("UID ")
+ def fetch_internal(cmd, set, attr, mod = nil,
+ partial: nil,
+ changedsince: nil,
+ vanished: false)
+ if cmd.start_with?("UID ")
+ if vanished && !changedsince
+ raise ArgumentError, "vanished must be used with changedsince"
+ end
+ elsif vanished
+ raise ArgumentError, "vanished can only be used with uid_fetch"
+ elsif partial
raise ArgumentError, "partial can only be used with uid_fetch"
end
set = SequenceSet[set]
- if partial
- mod ||= []
- mod << "PARTIAL" << PartialRange[partial]
- end
- if changedsince
- mod ||= []
- mod << "CHANGEDSINCE" << Integer(changedsince)
- end
+ (mod ||= []) << "PARTIAL" << PartialRange[partial] if partial
+ (mod ||= []) << "CHANGEDSINCE" << Integer(changedsince) if changedsince
+ (mod ||= []) << "VANISHED" if vanished
case attr
when String then
attr = RawData.new(attr)
@@ -3704,7 +3727,7 @@ def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
args = [cmd, set, attr]
args << mod if mod
- send_command_returning_fetch_results(*args)
+ send_command_returning_fetch_results(*args, vanished:)
end
def store_internal(cmd, set, attr, flags, unchangedsince: nil)
@@ -3715,14 +3738,20 @@ def store_internal(cmd, set, attr, flags, unchangedsince: nil)
send_command_returning_fetch_results(cmd, *args)
end
- def send_command_returning_fetch_results(...)
+ def send_command_returning_fetch_results(*args, vanished: false)
synchronize do
clear_responses("FETCH")
clear_responses("UIDFETCH")
- send_command(...)
+ send_command(*args)
fetches = clear_responses("FETCH")
uidfetches = clear_responses("UIDFETCH")
- uidfetches.any? ? uidfetches : fetches
+ fetches = uidfetches if uidfetches.any?
+ if vanished
+ vanished = extract_responses("VANISHED", &:earlier?).last ||
+ VanishedData[uids: SequenceSet.empty, earlier: true]
+ fetches = [vanished, *fetches].freeze
+ end
+ fetches
end
end
diff --git a/test/net/imap/test_imap_fetch.rb b/test/net/imap/test_imap_fetch.rb
index 32e2a639..79a397c1 100644
--- a/test/net/imap/test_imap_fetch.rb
+++ b/test/net/imap/test_imap_fetch.rb
@@ -12,6 +12,12 @@ class IMAPFetchTest < Net::IMAP::TestCase
assert_raise_with_message(ArgumentError, /\Apartial.*uid_fetch/) do
imap.fetch(1, "FAST", partial: 1..10)
end
+ assert_raise_with_message(ArgumentError, /\Avanished.*uid_fetch/) do
+ imap.fetch(1, "FAST", changedsince: 1234, vanished: true)
+ end
+ assert_raise_with_message(ArgumentError, /\Avanished.*changedsince/) do
+ imap.uid_fetch(1, "FAST", vanished: true)
+ end
end
end
@@ -107,4 +113,59 @@ class IMAPFetchTest < Net::IMAP::TestCase
end
end
+ test "#uid_fetch with changedsince and vanished" do
+ with_fake_server select: "inbox" do |server, imap|
+ server.on("UID FETCH") do |resp|
+ resp.untagged "VANISHED (EARLIER) 300:310,405,411"
+ resp.untagged "1 FETCH (UID 404 MODSEQ (65402) FLAGS (\\Seen))"
+ resp.untagged "2 FETCH (UID 406 MODSEQ (75403) FLAGS (\\Deleted))"
+ resp.untagged "4 FETCH (UID 408 MODSEQ (29738) " \
+ "FLAGS ($NoJunk $AutoJunk $MDNSent))"
+ resp.done_ok
+ end
+ # vanished: true changes the output to begin with VanishedData
+ vanished, *fetched = imap.uid_fetch(300..500, %w[FLAGS],
+ changedsince: 12345, vanished: true)
+ assert_equal(
+ "RUBY0002 UID FETCH 300:500 (FLAGS) (CHANGEDSINCE 12345 VANISHED)",
+ server.commands.pop.raw.strip
+ )
+ assert_equal Net::IMAP::VanishedData["300:310,405,411", true], vanished
+ expected = [
+ [1, 404, 65402, %i[Seen]],
+ [2, 406, 75403, %i[Deleted]],
+ [4, 408, 29738, %w[$NoJunk $AutoJunk $MDNSent]],
+ ]
+ assert_equal expected.size, fetched.size
+ fetched.zip(expected).each do |fetch, (seqno, uid, modseq, flags)|
+ assert_instance_of Net::IMAP::FetchData, fetch
+ assert_equal seqno, fetch.seqno
+ assert_equal uid, fetch.uid
+ assert_equal modseq, fetch.modseq
+ assert_equal flags, fetch.flags
+ end
+
+ # without VANISHED
+ server.on("UID FETCH") do |resp|
+ resp.untagged "1 FETCH (UID 404 MODSEQ (65402) FLAGS (\\Seen))"
+ resp.untagged "2 FETCH (UID 406 MODSEQ (75403) FLAGS (\\Deleted))"
+ resp.untagged "4 FETCH (UID 408 MODSEQ (29738) " \
+ "FLAGS ($NoJunk $AutoJunk $MDNSent))"
+ resp.done_ok
+ end
+ vanished, *fetched = imap.uid_fetch(300..500, %w[FLAGS],
+ changedsince: 12345, vanished: true)
+ assert_equal(Net::IMAP::VanishedData[Net::IMAP::SequenceSet.empty, true],
+ vanished)
+ assert_equal expected.size, fetched.size
+ fetched.zip(expected).each do |fetch, (seqno, uid, modseq, flags)|
+ assert_instance_of Net::IMAP::FetchData, fetch
+ assert_equal seqno, fetch.seqno
+ assert_equal uid, fetch.uid
+ assert_equal modseq, fetch.modseq
+ assert_equal flags, fetch.flags
+ end
+ end
+ end
+
end