Skip to content

Commit

Permalink
✨ Gather ESEARCH response to #search/#uid_search
Browse files Browse the repository at this point in the history
* Return empty ESearchResult when no result
* Clear SEARCH responses before new search.  Note that we don't need to
  do this for ESEARCH responses, since they are tagged.
  • Loading branch information
nevans committed Nov 8, 2024
1 parent c8d85c5 commit efd2760
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 4 deletions.
25 changes: 21 additions & 4 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1930,7 +1930,7 @@ def uid_expunge(uid_set)
end

# :call-seq:
# search(criteria, charset = nil) -> result
# search(criteria, charset = nil, esearch: false) -> result
#
# Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4]
# to search the mailbox for messages that match the given search +criteria+,
Expand Down Expand Up @@ -1973,6 +1973,11 @@ def uid_expunge(uid_set)
# Do not use the +charset+ argument when either return options or charset
# are embedded in +criteria+.
#
# +esearch+ controls the return type when the server does not return any
# search results. If +esearch+ is +true+ or +criteria+ begins with
# +RETURN+, an empty ESearchResult will be returned. When +esearch+ is
# +false+, an empty SearchResult will be returned.
#
# Related: #uid_search
#
# ===== For example:
Expand Down Expand Up @@ -3145,12 +3150,24 @@ def enforce_logindisabled?
end
end

def search_internal(cmd, keys, charset = nil)
def search_internal(cmd, keys, charset = nil, esearch: false)
keys = normalize_searching_criteria(keys)
args = charset ? ["CHARSET", charset, *keys] : keys
synchronize do
send_command(cmd, *args)
clear_responses("SEARCH").last || []
clear_responses("SEARCH")
result = nil
send_command(cmd, *args) do |response, tag|
if response in data: ESearchResult(tag: ^tag) => result
responses("ESEARCH") { _1.delete(result) }
end
end
if result
result
elsif esearch || keys in RawData[/\ARETURN /] | Array[/\ARETURN\z/i, *]
ESearchResult.new
else
clear_responses("SEARCH").last || []
end
end
end

Expand Down
39 changes: 39 additions & 0 deletions test/net/imap/test_imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,45 @@ def seqset_coercible.to_sequence_set
end
end

test("#search/#uid_search with ESEARCH or IMAP4rev2") do
with_fake_server do |server, imap|
# Example from RFC9051, 6.4.4:
# C: A282 SEARCH RETURN (MIN COUNT) FLAGGED
# SINCE 1-Feb-1994 NOT FROM "Smith"
# S: * ESEARCH (TAG "A282") MIN 2 COUNT 3
# S: A282 OK SEARCH completed
server.on "SEARCH" do |cmd|
cmd.untagged "ESEARCH", "(TAG \"unrelated1\") MIN 1 COUNT 2"
cmd.untagged "ESEARCH", "(TAG %p) MIN 2 COUNT 3" % [cmd.tag]
cmd.untagged "ESEARCH", "(TAG \"unrelated2\") MIN 222 COUNT 333"
cmd.done_ok
end
result = imap.search(
'RETURN (MIN COUNT) FLAGGED SINCE 1-Feb-1994 NOT FROM "Smith"'
)
cmd = server.commands.pop
assert_equal Net::IMAP::ESearchResult.new(
cmd.tag, false, [["MIN", 2], ["COUNT", 3]]
), result
esearch_responses = imap.clear_responses("ESEARCH")
assert_equal 2, esearch_responses.count
refute esearch_responses.include?(result)
end
end

test("missing server ESEARCH response") do
with_fake_server do |server, imap|
# Example from RFC9051, 6.4.4:
# C: A282 SEARCH RETURN (SAVE) FLAGGED SINCE 1-Feb-1994 NOT FROM "Smith"
# S: A282 OK SEARCH completed, result saved
server.on "SEARCH" do |cmd| cmd.done_ok "result saved" end
result = imap.search(
'RETURN (SAVE) FLAGGED SINCE 1-Feb-1994 NOT FROM "Smith"'
)
assert_equal Net::IMAP::ESearchResult.new, result
end
end

test("missing server SEARCH response") do
with_fake_server do |server, imap|
server.on "SEARCH", &:done_ok
Expand Down

0 comments on commit efd2760

Please sign in to comment.