Skip to content

Commit 992beea

Browse files
committed
🥅 Fix premature tagged response guard for IDLE
The premature tagged `OK` response guard that was added for `STARTTLS`, in #664, didn't correctly drop the connection when it was triggered for `IDLE`. Note that `finish_command` was moved until _after_ sending the command, so the raised error would be consistent with all other commands.
1 parent 9d47ed5 commit 992beea

3 files changed

Lines changed: 61 additions & 1 deletion

File tree

lib/net/imap.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3202,8 +3202,8 @@ def idle(timeout = nil, &response_handler)
32023202
synchronize do
32033203
tag = Thread.current[:net_imap_tag] = generate_tag
32043204
command = Command[tag:, name: "IDLE"]
3205-
finish_sending_command(command)
32063205
put_string("#{tag} IDLE#{CRLF}")
3206+
finish_sending_command(command)
32073207

32083208
begin
32093209
add_response_handler(&response_handler)
@@ -3220,6 +3220,9 @@ def idle(timeout = nil, &response_handler)
32203220
response = get_tagged_response(tag, "IDLE", idle_response_timeout)
32213221
end
32223222
end
3223+
rescue InvalidResponseError
3224+
disconnect
3225+
raise
32233226
end
32243227

32253228
return response

test/lib/helper.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ def assert_pattern
184184
end
185185
end
186186

187+
def assert_stream_closed_error
188+
assert_raise_with_message(IOError, /\A(?:stream closed|closed stream)\z/) do
189+
yield
190+
end
191+
end
192+
187193
def pend_if(condition, *args, &block)
188194
if condition
189195
pend(*args, &block)

test/net/imap/test_imap.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,57 @@ def test_starttls_stripping_ok_sent_before_response
210210
end
211211
end
212212

213+
# Similar to STARTTLS stripping test, but checks other commands too
214+
data(
215+
"IDLE" => ->imap do imap.idle(1) do end end,
216+
"NOOP" => ->imap do imap.noop end,
217+
"SELECT" => ->imap do imap.select("inbox") end,
218+
)
219+
test "premature tagged OK response" do |cmd|
220+
timeout = 5
221+
timeout *= EnvUtil.timeout_scale || 1 if defined?(EnvUtil.timeout_scale)
222+
Timeout.timeout(timeout) do
223+
server_to_client = Queue.new
224+
client_to_server = Queue.new
225+
rcvr_to_client = Queue.new
226+
server = create_tcp_server
227+
port = server.addr[1]
228+
start_server do
229+
sock = server.accept
230+
begin
231+
sock.print("* OK test server\r\n")
232+
assert_equal :send_malicious_responses, client_to_server.pop
233+
sock.print("RUBY0001 OK invalid\r\n")
234+
sock.print("RUBY0002 OK false\r\n")
235+
sock.print("RUBY0003 OK tricky\r\n")
236+
server_to_client << :sent_malicious_responses
237+
sock.gets
238+
ensure
239+
sock.close
240+
server.close
241+
end
242+
end
243+
begin
244+
imap = Net::IMAP.new(server_addr, port:)
245+
i = 0
246+
imap.add_response_handler do |resp|
247+
rcvr_to_client << (i += 1)
248+
end
249+
client_to_server << :send_malicious_responses
250+
assert_equal :sent_malicious_responses, server_to_client.pop
251+
assert_equal [1, 2, 3], 3.times.map { rcvr_to_client.pop }
252+
# should respond this way for _any_ command
253+
assert_raise(Net::IMAP::InvalidTaggedResponseError) do cmd.(imap) end
254+
assert imap.disconnected?
255+
assert_stream_closed_error do cmd.(imap) end
256+
assert_stream_closed_error do cmd.(imap) end
257+
assert_stream_closed_error do cmd.(imap) end
258+
ensure
259+
imap.disconnect if imap
260+
end
261+
end
262+
end
263+
213264
def start_server
214265
th = Thread.new do
215266
yield

0 commit comments

Comments
 (0)