Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 46 additions & 17 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
# <em>The +QRESYNC+ capabability must be enabled.</em>
# {[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.
# <tt>1..500</tt> returns the first 500 messages in +set+ (in mailbox
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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

Expand Down
61 changes: 61 additions & 0 deletions test/net/imap/test_imap_fetch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Loading