Skip to content

Commit c4a1c79

Browse files
authored
🔀 Merge pull request #434 from ruby/backport/v0.4-GH433-response_reader
♻️ Extract ResponseReader from get_response (backport #433)
2 parents 4b30b02 + adef9ff commit c4a1c79

File tree

3 files changed

+91
-19
lines changed

3 files changed

+91
-19
lines changed

lib/net/imap.rb

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,7 @@ class IMAP < Protocol
765765
"UTF8=ONLY" => "UTF8=ACCEPT",
766766
}.freeze
767767

768+
autoload :ResponseReader, File.expand_path("imap/response_reader", __dir__)
768769
autoload :SASL, File.expand_path("imap/sasl", __dir__)
769770
autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
770771
autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
@@ -998,6 +999,7 @@ def initialize(host, port: nil, ssl: nil, response_handlers: nil,
998999
# Connection
9991000
@tls_verified = false
10001001
@sock = tcp_socket(@host, @port)
1002+
@reader = ResponseReader.new(self, @sock)
10011003
start_tls_session if ssl_ctx
10021004
start_imap_connection
10031005

@@ -2923,30 +2925,12 @@ def get_tagged_response(tag, cmd, timeout = nil)
29232925
end
29242926

29252927
def get_response
2926-
buff = String.new
2927-
catch :eof do
2928-
while true
2929-
get_response_line(buff)
2930-
break unless /\{(\d+)\}\r\n\z/n =~ buff
2931-
get_response_literal(buff, $1.to_i)
2932-
end
2933-
end
2928+
buff = @reader.read_response_buffer
29342929
return nil if buff.length == 0
29352930
$stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
29362931
@parser.parse(buff)
29372932
end
29382933

2939-
def get_response_line(buff)
2940-
line = @sock.gets(CRLF) or throw :eof
2941-
buff << line
2942-
end
2943-
2944-
def get_response_literal(buff, literal_size)
2945-
literal = String.new(capacity: literal_size)
2946-
@sock.read(literal_size, literal) or throw :eof
2947-
buff << literal
2948-
end
2949-
29502934
#############################
29512935
# built-in response handlers
29522936

@@ -3145,6 +3129,7 @@ def start_tls_session
31453129
raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
31463130
raise "cannot start TLS without SSLContext" unless ssl_ctx
31473131
@sock = SSLSocket.new(@sock, ssl_ctx)
3132+
@reader = ResponseReader.new(self, @sock)
31483133
@sock.sync_close = true
31493134
@sock.hostname = @host if @sock.respond_to? :hostname=
31503135
ssl_socket_connect(@sock, open_timeout)

lib/net/imap/response_reader.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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(buff)
18+
break unless /\{(\d+)\}\r\n\z/n =~ buff
19+
read_literal(buff, $1.to_i)
20+
end
21+
end
22+
buff
23+
end
24+
25+
private
26+
27+
def read_line(buff)
28+
buff << (@sock.gets(CRLF) or throw :eof)
29+
end
30+
31+
def read_literal(buff, literal_size)
32+
literal = String.new(capacity: literal_size)
33+
buff << (@sock.read(literal_size, literal) or throw :eof)
34+
end
35+
36+
end
37+
end
38+
end

test/net/imap/test_response_reader.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
def setup
9+
Net::IMAP.config.reset
10+
end
11+
12+
class FakeClient
13+
def config; @config ||= Net::IMAP.config.new end
14+
end
15+
16+
def literal(str) "{#{str.bytesize}}\r\n#{str}" end
17+
18+
test "#read_response_buffer" do
19+
client = FakeClient.new
20+
aaaaaaaaa = "a" * (20 << 10)
21+
many_crs = "\r" * 1000
22+
many_crlfs = "\r\n" * 500
23+
simple = "* OK greeting\r\n"
24+
long_line = "tag ok #{aaaaaaaaa} #{aaaaaaaaa}\r\n"
25+
literal_aaaa = "* fake #{literal aaaaaaaaa}\r\n"
26+
literal_crlf = "tag ok #{literal many_crlfs} #{literal many_crlfs}\r\n"
27+
illegal_crs = "tag ok #{many_crs} #{many_crs}\r\n"
28+
illegal_lfs = "tag ok #{literal "\r"}\n#{literal "\r"}\n\r\n"
29+
io = StringIO.new([
30+
simple,
31+
long_line,
32+
literal_aaaa,
33+
literal_crlf,
34+
illegal_crs,
35+
illegal_lfs,
36+
simple,
37+
].join)
38+
rcvr = Net::IMAP::ResponseReader.new(client, io)
39+
assert_equal simple, rcvr.read_response_buffer.to_str
40+
assert_equal long_line, rcvr.read_response_buffer.to_str
41+
assert_equal literal_aaaa, rcvr.read_response_buffer.to_str
42+
assert_equal literal_crlf, rcvr.read_response_buffer.to_str
43+
assert_equal illegal_crs, rcvr.read_response_buffer.to_str
44+
assert_equal illegal_lfs, rcvr.read_response_buffer.to_str
45+
assert_equal simple, rcvr.read_response_buffer.to_str
46+
assert_equal "", rcvr.read_response_buffer.to_str
47+
end
48+
49+
end

0 commit comments

Comments
 (0)