Skip to content

Commit c64edec

Browse files
authored
🔀 Merge pull request #443 from ruby/backport/v0.2-response_reader
♻️ Backport `ResponseReader` to v0.2
2 parents a945c89 + 335872f commit c64edec

File tree

3 files changed

+102
-16
lines changed

3 files changed

+102
-16
lines changed

lib/net/imap.rb

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ module Net
253253
class IMAP < Protocol
254254
VERSION = "0.2.4"
255255

256+
autoload :ResponseReader, File.expand_path("imap/response_reader", __dir__)
257+
256258
include MonitorMixin
257259
if defined?(OpenSSL::SSL)
258260
include OpenSSL
@@ -1144,6 +1146,7 @@ def initialize(host, port_or_options = {},
11441146
@idle_response_timeout = options[:idle_response_timeout] || 5
11451147
@parser = ResponseParser.new
11461148
@sock = tcp_socket(@host, @port)
1149+
@reader = ResponseReader.new(self, @sock)
11471150
begin
11481151
if options[:ssl]
11491152
start_tls_session(options[:ssl])
@@ -1295,25 +1298,14 @@ def get_tagged_response(tag, cmd, timeout = nil)
12951298
end
12961299

12971300
def get_response
1298-
buff = String.new
1299-
while true
1300-
s = @sock.gets(CRLF)
1301-
break unless s
1302-
buff.concat(s)
1303-
if /\{(\d+)\}\r\n/n =~ s
1304-
s = @sock.read($1.to_i)
1305-
buff.concat(s)
1306-
else
1307-
break
1308-
end
1309-
end
1301+
buff = @reader.read_response_buffer
13101302
return nil if buff.length == 0
1311-
if @@debug
1312-
$stderr.print(buff.gsub(/^/n, "S: "))
1313-
end
1314-
return @parser.parse(buff)
1303+
$stderr.print(buff.gsub(/^/n, "S: ")) if @@debug
1304+
@parser.parse(buff)
13151305
end
13161306

1307+
#############################
1308+
13171309
def record_response(name, data)
13181310
unless @responses.has_key?(name)
13191311
@responses[name] = []
@@ -1491,6 +1483,7 @@ def start_tls_session(params = {})
14911483
context.verify_callback = VerifyCallbackProc
14921484
end
14931485
@sock = SSLSocket.new(@sock, context)
1486+
@reader = ResponseReader.new(self, @sock)
14941487
@sock.sync_close = true
14951488
@sock.hostname = @host if @sock.respond_to? :hostname=
14961489
ssl_socket_connect(@sock, @open_timeout)

lib/net/imap/response_reader.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
module Net
4+
class IMAP
5+
# See https://www.rfc-editor.org/rfc/rfc9051#section-2.2.2
6+
class ResponseReader # :nodoc:
7+
attr_reader :client
8+
9+
def initialize(client, sock)
10+
@client, @sock = client, sock
11+
end
12+
13+
def read_response_buffer
14+
@buff = String.new
15+
catch :eof do
16+
while true
17+
read_line
18+
break unless (@literal_size = get_literal_size)
19+
read_literal
20+
end
21+
end
22+
buff
23+
ensure
24+
@buff = nil
25+
end
26+
27+
private
28+
29+
attr_reader :buff, :literal_size
30+
31+
def get_literal_size; /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i end
32+
33+
def read_line
34+
buff << (@sock.gets(CRLF) or throw :eof)
35+
end
36+
37+
def read_literal
38+
literal = String.new(capacity: literal_size)
39+
buff << (@sock.read(literal_size, literal) or throw :eof)
40+
ensure
41+
@literal_size = nil
42+
end
43+
44+
end
45+
end
46+
end

test/net/imap/test_response_reader.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
require "net/imap"
4+
require "stringio"
5+
require "test/unit"
6+
7+
class ResponseReaderTest < Test::Unit::TestCase
8+
class FakeClient
9+
end
10+
11+
def literal(str) "{#{str.bytesize}}\r\n#{str}" end
12+
13+
test "#read_response_buffer" do
14+
client = FakeClient.new
15+
aaaaaaaaa = "a" * (20 << 10)
16+
many_crs = "\r" * 1000
17+
many_crlfs = "\r\n" * 500
18+
simple = "* OK greeting\r\n"
19+
long_line = "tag ok #{aaaaaaaaa} #{aaaaaaaaa}\r\n"
20+
literal_aaaa = "* fake #{literal aaaaaaaaa}\r\n"
21+
literal_crlf = "tag ok #{literal many_crlfs} #{literal many_crlfs}\r\n"
22+
zero_literal = "tag ok #{literal ""} #{literal ""}\r\n"
23+
illegal_crs = "tag ok #{many_crs} #{many_crs}\r\n"
24+
illegal_lfs = "tag ok #{literal "\r"}\n#{literal "\r"}\n\r\n"
25+
io = StringIO.new([
26+
simple,
27+
long_line,
28+
literal_aaaa,
29+
literal_crlf,
30+
zero_literal,
31+
illegal_crs,
32+
illegal_lfs,
33+
simple,
34+
].join)
35+
rcvr = Net::IMAP::ResponseReader.new(client, io)
36+
assert_equal simple, rcvr.read_response_buffer.to_str
37+
assert_equal long_line, rcvr.read_response_buffer.to_str
38+
assert_equal literal_aaaa, rcvr.read_response_buffer.to_str
39+
assert_equal literal_crlf, rcvr.read_response_buffer.to_str
40+
assert_equal zero_literal, rcvr.read_response_buffer.to_str
41+
assert_equal illegal_crs, rcvr.read_response_buffer.to_str
42+
assert_equal illegal_lfs, rcvr.read_response_buffer.to_str
43+
assert_equal simple, rcvr.read_response_buffer.to_str
44+
assert_equal "", rcvr.read_response_buffer.to_str
45+
end
46+
47+
end

0 commit comments

Comments
 (0)