Skip to content

Commit

Permalink
🔀 Merge branch 'RFC9586-UIDONLY' into yahoo-extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
nevans committed Nov 8, 2024
2 parents d86e849 + ef9a56d commit fb6fa2d
Show file tree
Hide file tree
Showing 12 changed files with 765 additions and 153 deletions.
160 changes: 130 additions & 30 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,12 @@ module Net
# See FetchData#emailid and FetchData#emailid.
# - Updates #status with support for the +MAILBOXID+ status attribute.
#
# ==== RFC9586: +UIDONLY+
# - Updates #enable with +UIDONLY+ parameter.
# - Updates #uid_fetch and #uid_store to return +UIDFETCH+ response.
# - Updates #expunge and #uid_expunge to return +VANISHED+ response.
# - Prohibits use of message sequence numbers in responses or requests.
#
# == References
#
# [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
Expand Down Expand Up @@ -697,6 +703,11 @@ module Net
# Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
# RFC 8474, DOI 10.17487/RFC8474, September 2018,
# <https://www.rfc-editor.org/info/rfc8474>.
# [UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.pdf]]::
# Melnikov, A., Achuthan, A., Nagulakonda, V., Singh, A., and L. Alves,
# "\IMAP Extension for Using and Returning Unique Identifiers (UIDs) Only",
# RFC 9586, DOI 10.17487/RFC9586, May 2024,
# <https://www.rfc-editor.org/info/rfc9586>.
#
# === IANA registries
# * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
Expand Down Expand Up @@ -1885,18 +1896,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 @@ -1920,13 +1952,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 @@ -2205,6 +2237,10 @@ def uid_expunge(uid_set)
# result = imap.search(["SUBJECT", "hi there", "not", "new"])
# #=> Net::IMAP::SearchResult[1, 6, 7, 8, modseq: 5594]
# result.modseq # => 5594
#
# The +SEARCH+ command is prohibited when
# UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] has been enabled.
# Use #uid_search instead.
def search(...)
search_internal("SEARCH", ...)
end
Expand All @@ -2221,6 +2257,15 @@ def search(...)
# capability has been enabled.
#
# See #search for documentation of parameters.
#
# ===== Capabilities
#
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
# <tt><message set></tt> search criterion is prohibited. Use +ALL+ or
# <tt>UID sequence-set</tt> instead.
#
# Otherwise, #uid_search is updated by extensions in the same way as
# #search.
def uid_search(...)
search_internal("UID SEARCH", ...)
end
Expand Down Expand Up @@ -2277,12 +2322,15 @@ def uid_search(...)
# {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the
# +changedsince+ argument. Using +changedsince+ implicitly enables the
# +CONDSTORE+ extension.
#
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
# +FETCH+ command is prohibited. Use #uid_fetch instead.
def fetch(set, attr, mod = nil, changedsince: nil)
fetch_internal("FETCH", set, attr, mod, changedsince: changedsince)
end

# :call-seq:
# uid_fetch(set, attr, changedsince: nil) -> array of FetchData
# uid_fetch(set, attr, changedsince: nil) -> array of 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 @@ -2298,7 +2346,12 @@ def fetch(set, attr, mod = nil, changedsince: nil)
# Related: #fetch, FetchData
#
# ===== Capabilities
# Same as #fetch.
#
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] has been
# enabled, #uid_fetch must be used instead of #fetch, and UIDFetchData will
# be returned instead of FetchData.
#
# Otherwise, #uid_store is updated by extensions in the same way as #store.
def uid_fetch(set, attr, mod = nil, changedsince: nil)
fetch_internal("UID FETCH", set, attr, mod, changedsince: changedsince)
end
Expand Down Expand Up @@ -2346,12 +2399,16 @@ def uid_fetch(set, attr, mod = nil, changedsince: nil)
# {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the
# +unchangedsince+ argument. Using +unchangedsince+ implicitly enables the
# +CONDSTORE+ extension.
#
# The +STORE+ command is prohibited when
# UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] has been enabled.
# Use #uid_store instead.
def store(set, attr, flags, unchangedsince: nil)
store_internal("STORE", set, attr, flags, unchangedsince: unchangedsince)
end

# :call-seq:
# uid_store(set, attr, value, unchangedsince: nil) -> array of FetchData
# uid_store(set, attr, value, unchangedsince: nil) -> array of FetchData (or UIDFetchData)
#
# Sends a {UID STORE command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
# to alter data associated with messages in the mailbox, in particular their
Expand All @@ -2363,7 +2420,12 @@ def store(set, attr, flags, unchangedsince: nil)
# Related: #store
#
# ===== Capabilities
# Same as #store.
#
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] has been
# enabled, #uid_store must be used instead of #store, and UIDFetchData will
# be returned instead of FetchData.
#
# Otherwise, #uid_store is updated by extensions in the same way as #store.
def uid_store(set, attr, flags, unchangedsince: nil)
store_internal("UID STORE", set, attr, flags, unchangedsince: unchangedsince)
end
Expand All @@ -2382,6 +2444,9 @@ def uid_store(set, attr, flags, unchangedsince: nil)
# with UIDPlusData. This will report the UIDVALIDITY of the destination
# mailbox, the UID set of the source messages, and the assigned UID set of
# the moved messages.
#
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
# +COPY+ command is prohibited. Use #uid_copy instead.
def copy(set, mailbox)
copy_internal("COPY", set, mailbox)
end
Expand All @@ -2394,7 +2459,10 @@ def copy(set, mailbox)
#
# ===== Capabilities
#
# +UIDPLUS+ affects #uid_copy the same way it affects #copy.
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] has been
# enabled, #uid_copy must be used instead of #copy.
#
# Otherwise, #uid_copy is updated by extensions in the same way as #copy.
def uid_copy(set, mailbox)
copy_internal("UID COPY", set, mailbox)
end
Expand All @@ -2418,6 +2486,8 @@ def uid_copy(set, mailbox)
# mailbox, the UID set of the source messages, and the assigned UID set of
# the moved messages.
#
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
# +MOVE+ command is prohibited. Use #uid_move instead.
def move(set, mailbox)
copy_internal("MOVE", set, mailbox)
end
Expand All @@ -2433,9 +2503,13 @@ def move(set, mailbox)
#
# ===== Capabilities
#
# Same as #move: The server's capabilities must include +MOVE+
# [RFC6851[https://tools.ietf.org/html/rfc6851]]. +UIDPLUS+ also affects
# #uid_move the same way it affects #move.
# The server's capabilities must include either +IMAP4rev2+ or +MOVE+
# [RFC6851[https://tools.ietf.org/html/rfc6851]].
#
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] has been
# enabled, #uid_move must be used instead of #move.
#
# Otherwise, #uid_move is updated by extensions in the same way as #move.
def uid_move(set, mailbox)
copy_internal("UID MOVE", set, mailbox)
end
Expand Down Expand Up @@ -2580,6 +2654,16 @@ def uid_thread(algorithm, search_keys, charset)
# selected. For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
# <tt>enable("UTF8=ACCEPT")</tt>.
#
# [+UIDONLY+ {[RFC9586]}[https://www.rfc-editor.org/rfc/rfc9586.pdf]]
#
# When UIDONLY is enabled, the #fetch, #store, #search, #copy, and #move
# commands are prohibited and result in a tagged BAD response. Clients
# should instead use uid_fetch, uid_store, uid_search, uid_copy, or
# uid_move, respectively. All +FETCH+ responses that would be returned are
# replaced by +UIDFETCH+ responses. All +EXPUNGED+ responses that would be
# returned are replaced by +VANISHED+ responses. The "<sequence set>"
# uid_search criterion is prohibited.
#
# ===== Unsupported capabilities
#
# *Note:* Some extensions that use ENABLE permit the server to send syntax
Expand Down Expand Up @@ -3150,6 +3234,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

def search_internal(cmd, keys, charset = nil, esearch: false)
keys = normalize_searching_criteria(keys)
args = charset ? ["CHARSET", charset, *keys] : keys
Expand Down Expand Up @@ -3185,26 +3284,27 @@ def fetch_internal(cmd, set, attr, mod = nil, changedsince: nil)
}
end

synchronize do
clear_responses("FETCH")
if mod
send_command(cmd, SequenceSet.new(set), attr, mod)
else
send_command(cmd, SequenceSet.new(set), attr)
end
clear_responses("FETCH")
end
args = [cmd, SequenceSet.new(set), attr]
args << mod if mod
send_command_returning_fetch_results(*args)
end

def store_internal(cmd, set, attr, flags, unchangedsince: nil)
attr = RawData.new(attr) if attr.instance_of?(String)
args = [SequenceSet.new(set)]
args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
args << attr << flags
send_command_returning_fetch_results(cmd, *args)
end

def send_command_returning_fetch_results(...)
synchronize do
clear_responses("FETCH")
send_command(cmd, *args)
clear_responses("FETCH")
clear_responses("UIDFETCH")
send_command(...)
fetches = clear_responses("FETCH")
uidfetches = clear_responses("UIDFETCH")
uidfetches.any? ? uidfetches : fetches
end
end

Expand Down
Loading

0 comments on commit fb6fa2d

Please sign in to comment.