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 Oct 25, 2024
1 parent 8780b9f commit 7dc2006
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 @@ -1966,6 +1966,11 @@ def uid_expunge(uid_set)
# "CHARSET", "UTF-8",
# %w(OR UNSEEN FLAGGED)])
#
# +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
#
# ===== Search criteria
Expand Down Expand Up @@ -2974,12 +2979,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 @@ -1234,6 +1234,45 @@ def test_unselect
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 7dc2006

Please sign in to comment.