@@ -414,7 +414,7 @@ module Net
414
414
# >>>
415
415
# <em>The following are folded into +IMAP4rev2+ but are currently
416
416
# unsupported or incompletely supported by</em> Net::IMAP<em>: RFC4466
417
- # extensions, +ESEARCH+, + SEARCHRES+, +LIST-EXTENDED+, +LIST-STATUS+,
417
+ # extensions, +SEARCHRES+, +LIST-EXTENDED+, +LIST-STATUS+,
418
418
# +LITERAL-+, and +SPECIAL-USE+.</em>
419
419
#
420
420
# ==== RFC2087: +QUOTA+
@@ -466,6 +466,10 @@ module Net
466
466
# - Updates #append with the +APPENDUID+ ResponseCode
467
467
# - Updates #copy, #move with the +COPYUID+ ResponseCode
468
468
#
469
+ # ==== RFC4731: +ESEARCH+
470
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
471
+ # - Updates #search, #uid_search with +return+ options and ESearchResult.
472
+ #
469
473
# ==== RFC4959: +SASL-IR+
470
474
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
471
475
# - Updates #authenticate with the option to send an initial response.
@@ -1935,9 +1939,11 @@ def uid_expunge(uid_set)
1935
1939
#
1936
1940
# Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4]
1937
1941
# to search the mailbox for messages that match the given search +criteria+,
1938
- # and returns a SearchResult. SearchResult inherits from Array (for
1939
- # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
1940
- # capability has been enabled.
1942
+ # and returns either a SearchResult or an ESearchResult. SearchResult
1943
+ # inherits from Array (for backward compatibility) but adds
1944
+ # SearchResult#modseq when the +CONDSTORE+ capability has been enabled.
1945
+ # ESearchResult also implements to_a{rdoc-ref:ESearchResult#to_a}, for
1946
+ # compatibility with SearchResult.
1941
1947
#
1942
1948
# +criteria+ is one or more search keys and their arguments, which may be
1943
1949
# provided as an array or a string.
@@ -1948,8 +1954,11 @@ def uid_expunge(uid_set)
1948
1954
# set}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
1949
1955
# used by strings in the search +criteria+. When +charset+ isn't specified,
1950
1956
# either <tt>"US-ASCII"</tt> or <tt>"UTF-8"</tt> is assumed, depending on
1951
- # the server's capabilities. +charset+ may be sent inside +criteria+
1952
- # instead of as a separate argument.
1957
+ # the server's capabilities.
1958
+ #
1959
+ # _NOTE:_ Return options and charset may be sent as part of +criteria+. Do
1960
+ # not use the +charset+ argument when either return options or charset are
1961
+ # embedded in +criteria+.
1953
1962
#
1954
1963
# Related: #uid_search
1955
1964
#
@@ -1969,6 +1978,12 @@ def uid_expunge(uid_set)
1969
1978
# # criteria string contains charset arg
1970
1979
# imap.search("CHARSET UTF-8 OR UNSEEN (FLAGGED SUBJECT foo)")
1971
1980
#
1981
+ # Sending return options and charset embedded in the +criteria+ arg:
1982
+ # imap.search("RETURN (MIN MAX) CHARSET UTF-8 (OR UNSEEN FLAGGED)")
1983
+ # imap.search(["RETURN", %w(MIN MAX),
1984
+ # "CHARSET", "UTF-8",
1985
+ # %w(OR UNSEEN FLAGGED)])
1986
+ #
1972
1987
# ==== Argument translation
1973
1988
#
1974
1989
# [When +criteria+ is an Array]
@@ -2007,6 +2022,49 @@ def uid_expunge(uid_set)
2007
2022
# <em>*WARNING:* This is vulnerable to injection attacks when external
2008
2023
# inputs are used.</em>
2009
2024
#
2025
+ # ==== Return options
2026
+ #
2027
+ # For full definitions of the standard return options and return data, see
2028
+ # the relevant RFCs.
2029
+ #
2030
+ # ===== +ESEARCH+ or +IMAP4rev2+
2031
+ #
2032
+ # The following return options require either +ESEARCH+ or +IMAP4rev2+.
2033
+ # See [{RFC4731 §3.1}[https://rfc-editor.org/rfc/rfc4731#section-3.1]] or
2034
+ # [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]].
2035
+ #
2036
+ # [+ALL+]
2037
+ # Returns ESearchResult#all with a SequenceSet of all matching sequence
2038
+ # numbers or UIDs. This is the default, when return options are empty.
2039
+ #
2040
+ # For compatibility with SearchResult, ESearchResult#to_a returns an
2041
+ # Array of message sequence numbers or UIDs.
2042
+ # [+COUNT+]
2043
+ # Returns ESearchResult#count with the number of matching messages.
2044
+ # [+MAX+]
2045
+ # Returns ESearchResult#max with the highest matching sequence number or
2046
+ # UID.
2047
+ # [+MIN+]
2048
+ # Returns ESearchResult#min with the lowest matching sequence number or
2049
+ # UID.
2050
+ #
2051
+ # ===== +CONDSTORE+
2052
+ #
2053
+ # ESearchResult#modseq return data does not have a corresponding return
2054
+ # option. Instead, it is returned if the +MODSEQ+ search key is used or
2055
+ # when the +CONDSTORE+ extension is enabled for the selected mailbox.
2056
+ # See [{RFC4731 §3.2}[https://www.rfc-editor.org/rfc/rfc4731#section-3.2]]
2057
+ # or [{RFC7162 §2.1.5}[https://www.rfc-editor.org/rfc/rfc7162#section-3.1.5]].
2058
+ #
2059
+ # ===== +RFC4466+ compatible extensions
2060
+ #
2061
+ # {RFC4466 §2.6}[https://www.rfc-editor.org/rfc/rfc4466.html#section-2.6]
2062
+ # defines standard syntax for search extensions. Net::IMAP allows sending
2063
+ # unknown search return options and will parse unknown search extensions'
2064
+ # return values into ExtensionData. Please note that this is an
2065
+ # intentionally _unstable_ API. Future releases may return different
2066
+ # (incompatible) objects, <em>without deprecation or warning</em>.
2067
+ #
2010
2068
# ==== Search keys
2011
2069
#
2012
2070
# For full definitions of the standard search +criteria+,
@@ -2198,6 +2256,13 @@ def uid_expunge(uid_set)
2198
2256
#
2199
2257
# ==== Capabilities
2200
2258
#
2259
+ # Return options should only be specified when the server supports
2260
+ # +IMAP4rev2+ or an extension that allows them, such as +ESEARCH+
2261
+ # [RFC4731[https://rfc-editor.org/rfc/rfc4731#section-3.1]].
2262
+ #
2263
+ # When +IMAP4rev2+ is enabled, or when the server supports +IMAP4rev2+ but
2264
+ # not +IMAP4rev1+, ESearchResult is always returned instead of SearchResult.
2265
+ #
2201
2266
# If CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html] is supported
2202
2267
# and enabled for the selected mailbox, a non-empty SearchResult will
2203
2268
# include a +MODSEQ+ value.
@@ -3153,6 +3218,7 @@ def enforce_logindisabled?
3153
3218
end
3154
3219
3155
3220
def search_args ( keys , charset_arg = nil , charset : nil )
3221
+ esearch = ( keys in /\A RETURN\b /i | Array [ /\A RETURN\z /i , *] )
3156
3222
if charset && charset_arg
3157
3223
raise ArgumentError , "multiple charset arguments"
3158
3224
end
@@ -3163,16 +3229,28 @@ def search_args(keys, charset_arg = nil, charset: nil)
3163
3229
end
3164
3230
args = normalize_searching_criteria ( keys )
3165
3231
args . prepend ( "CHARSET" , charset ) if charset
3166
- args
3232
+ return args , esearch
3167
3233
end
3168
3234
3169
3235
def search_internal ( cmd , ...)
3170
- args = search_args ( ...)
3236
+ args , esearch = search_args ( ...)
3171
3237
synchronize do
3172
- send_command ( cmd , *args )
3238
+ tagged = send_command ( cmd , *args )
3239
+ tag = tagged . tag
3240
+ # Only the last ESEARCH or SEARCH is used. Excess results are ignored.
3241
+ esearch_result = extract_responses ( "ESEARCH" ) { |response |
3242
+ response in ESearchResult ( tag : ^tag )
3243
+ } . last
3173
3244
search_result = clear_responses ( "SEARCH" ) . last
3174
- if search_result
3245
+ if esearch_result
3246
+ # silently ignore SEARCH results, if any
3247
+ esearch_result
3248
+ elsif search_result
3249
+ # warn EXPECTED_ESEARCH_RESULT if esearch
3175
3250
search_result
3251
+ elsif esearch
3252
+ # warn NO_SEARCH_RESPONSE
3253
+ ESearchResult [ tag :, uid : cmd . start_with? ( "UID " ) ]
3176
3254
else
3177
3255
# warn NO_SEARCH_RESPONSE
3178
3256
SearchResult [ ]
0 commit comments