diff --git a/lib/net/imap.rb b/lib/net/imap.rb
index 5b19558f..4efe99e0 100644
--- a/lib/net/imap.rb
+++ b/lib/net/imap.rb
@@ -1949,9 +1949,13 @@ def uid_expunge(uid_set)
# * +Range+
# * -1 and +:*+ -- both translate to *
# * responds to +#to_sequence_set+
- # * nested +Array+
+ # * +Array+, when each element is one of the above types, a positive
+ # +Integer+, a sequence-set formatted +String+, or a deeply nested
+ # +Array+ of these same types.
# * Any +String+ is sent verbatim when it is a valid \IMAP atom,
# and encoded as an \IMAP quoted or literal string otherwise.
+ # * Any other nested +Array+ is encoded as a parenthesized list, to group
+ # multiple search keys (e.g., for use with +OR+ and +NOT+).
# * Any other +Integer+ (besides -1) will be sent as +#to_s+.
# * +Date+ objects will be encoded as an \IMAP date (see ::encode_date).
#
@@ -1976,13 +1980,13 @@ def uid_expunge(uid_set)
# The following searches send the exact same command to the server:
#
# # criteria array, charset arg
- # imap.search(%w[OR UNSEEN FLAGGED SUBJECT foo], "UTF-8")
+ # imap.search(["OR", "UNSEEN", %w(FLAGGED SUBJECT foo)], "UTF-8")
# # criteria string, charset arg
- # imap.search("OR UNSEEN FLAGGED SUBJECT foo", "UTF-8")
+ # imap.search("OR UNSEEN (FLAGGED SUBJECT foo)", "UTF-8")
# # criteria array contains charset arg
- # imap.search(%w[CHARSET UTF-8 OR UNSEEN FLAGGED SUBJECT foo])
+ # imap.search([*%w[CHARSET UTF-8], "OR", "UNSEEN", %w(FLAGGED SUBJECT foo)])
# # criteria string contains charset arg
- # imap.search("CHARSET UTF-8 OR UNSEEN FLAGGED SUBJECT foo")
+ # imap.search("CHARSET UTF-8 OR UNSEEN (FLAGGED SUBJECT foo)")
#
# ===== Search keys
#
@@ -3208,11 +3212,20 @@ def coerce_search_arg_to_seqset?(obj)
case obj
when Set, -1, :* then true
when Range then true
- when Array then true
+ when Array then obj.all? { coerce_search_array_arg_to_seqset? _1 }
else obj.respond_to?(:to_sequence_set)
end
end
+ def coerce_search_array_arg_to_seqset?(obj)
+ case obj
+ when Integer then obj.positive? || obj == -1
+ when String then ResponseParser::Patterns::SEQUENCE_SET_STR.match?(obj.b)
+ else
+ coerce_search_arg_to_seqset?(obj)
+ end
+ end
+
def build_ssl_ctx(ssl)
if ssl
params = (Hash.try_convert(ssl) || {}).freeze
diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb
index 895892d9..c9fe5ae1 100644
--- a/test/net/imap/test_imap.rb
+++ b/test/net/imap/test_imap.rb
@@ -1242,6 +1242,18 @@ def seqset_coercible.to_sequence_set
[1..22, 30..-1]])
cmd = server.commands.pop
assert_equal ["UID SEARCH", "subject hello 1:22,30:*"], [cmd.name, cmd.args]
+
+ assert_equal search_result, imap.search(
+ "RETURN (COUNT) NOT (FLAGGED (OR SEEN ANSWERED))"
+ )
+ cmd = server.commands.pop
+ assert_equal "RETURN (COUNT) NOT (FLAGGED (OR SEEN ANSWERED))", cmd.args
+
+ assert_equal search_result, imap.search([
+ "RETURN", %w(MIN MAX COUNT), "NOT", ["FLAGGED", %w(OR SEEN ANSWERED)]
+ ])
+ cmd = server.commands.pop
+ assert_equal "RETURN (MIN MAX COUNT) NOT (FLAGGED (OR SEEN ANSWERED))", cmd.args
end
end