Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC9586 UIDONLY support #366

Merged
merged 16 commits into from
Jan 3, 2025
Merged
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
110 changes: 86 additions & 24 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,12 @@ module Net
# ESearchResult#partial return data.
# - Updates #uid_fetch with the +partial+ modifier.
#
# ==== 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 @@ -711,6 +717,11 @@ module Net
# "IMAP PARTIAL Extension for Paged SEARCH and FETCH", RFC 9394,
# DOI 10.17487/RFC9394, June 2023,
# <https://www.rfc-editor.org/info/rfc9394>.
# [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 All @@ -724,7 +735,7 @@ module Net
# * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
# +imap+
# * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
# ===== For currently unsupported features:
# ==== For currently unsupported features:
# * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
# * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
# * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
Expand Down Expand Up @@ -2366,6 +2377,9 @@ 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
#
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
# the +SEARCH+ command is prohibited. Use #uid_search instead.
def search(...)
search_internal("SEARCH", ...)
end
Expand All @@ -2383,6 +2397,16 @@ 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,
# #uid_search must be used instead of #search, and 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 All @@ -2397,8 +2421,8 @@ def uid_search(...)
# to {SequenceSet[...]}[rdoc-ref:SequenceSet@Creating+sequence+sets].
# (For UIDs, use #uid_fetch instead.)
#
# +attr+ is a list of attributes to fetch; see the documentation
# for FetchData for a list of valid attributes.
# +attr+ is a list of attributes to fetch; see FetchStruct documentation for
# a list of supported attributes.
#
# +changedsince+ is an optional integer mod-sequence. It limits results to
# messages with a mod-sequence greater than +changedsince+.
Expand Down Expand Up @@ -2427,19 +2451,22 @@ def uid_search(...)
#
# ==== Capabilities
#
# Many extensions define new message +attr+ names. See FetchData for a list
# of supported extension fields.
# Many extensions define new message +attr+ names. See FetchStruct for a
# list of supported extension fields.
#
# The server's capabilities must include +CONDSTORE+
# {[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(...)
fetch_internal("FETCH", ...)
end

# :call-seq:
# uid_fetch(set, attr, changedsince: nil, partial: nil) -> array of FetchData
# uid_fetch(set, attr, changedsince: nil, partial: 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 Down Expand Up @@ -2491,7 +2518,11 @@ def fetch(...)
# {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394] in order to use the
# +partial+ argument.
#
# Otherwise, the same as #fetch.
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
# #uid_fetch must be used instead of #fetch, and UIDFetchData will be
# returned instead of FetchData.
#
# Otherwise, #uid_fetch is updated by extensions in the same way as #fetch.
def uid_fetch(...)
fetch_internal("UID FETCH", ...)
end
Expand Down Expand Up @@ -2539,12 +2570,15 @@ def uid_fetch(...)
# {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the
# +unchangedsince+ argument. Using +unchangedsince+ implicitly enables the
# +CONDSTORE+ extension.
#
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
# +STORE+ command is prohibited. 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 @@ -2556,7 +2590,12 @@ def store(set, attr, flags, unchangedsince: nil)
# Related: #store
#
# ==== Capabilities
# Same as #store.
#
# When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is 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 @@ -2575,6 +2614,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 @@ -2587,7 +2629,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] in 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 @@ -2611,6 +2656,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 @@ -2626,9 +2673,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] is 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 @@ -2773,6 +2824,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 @@ -3458,26 +3519,27 @@ def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
}
end

synchronize do
clear_responses("FETCH")
if mod
send_command(cmd, set, attr, mod)
else
send_command(cmd, set, attr)
end
clear_responses("FETCH")
end
args = [cmd, 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
Loading