Skip to content

Commit d28879e

Browse files
authored
🔀 Merge pull request #433 from ruby/response_reader
♻️ Extract ResponseReader from `get_response`
2 parents b1413c6 + 18bc621 commit d28879e

File tree

3 files changed

+96
-23
lines changed

3 files changed

+96
-23
lines changed

lib/net/imap.rb

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -792,10 +792,12 @@ class IMAP < Protocol
792792
"UTF8=ONLY" => "UTF8=ACCEPT",
793793
}.freeze
794794

795-
autoload :ConnectionState, File.expand_path("imap/connection_state", __dir__)
796-
autoload :SASL, File.expand_path("imap/sasl", __dir__)
797-
autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
798-
autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
795+
dir = File.expand_path("imap", __dir__)
796+
autoload :ConnectionState, "#{dir}/connection_state"
797+
autoload :ResponseReader, "#{dir}/response_reader"
798+
autoload :SASL, "#{dir}/sasl"
799+
autoload :SASLAdapter, "#{dir}/sasl_adapter"
800+
autoload :StringPrep, "#{dir}/stringprep"
799801

800802
include MonitorMixin
801803
if defined?(OpenSSL::SSL)
@@ -1088,6 +1090,7 @@ def initialize(host, port: nil, ssl: nil, response_handlers: nil,
10881090

10891091
# Connection
10901092
@sock = tcp_socket(@host, @port)
1093+
@reader = ResponseReader.new(self, @sock)
10911094
start_tls_session if ssl_ctx
10921095
start_imap_connection
10931096
end
@@ -3445,30 +3448,12 @@ def get_tagged_response(tag, cmd, timeout = nil)
34453448
end
34463449

34473450
def get_response
3448-
buff = String.new
3449-
catch :eof do
3450-
while true
3451-
get_response_line(buff)
3452-
break unless /\{(\d+)\}\r\n\z/n =~ buff
3453-
get_response_literal(buff, $1.to_i)
3454-
end
3455-
end
3451+
buff = @reader.read_response_buffer
34563452
return nil if buff.length == 0
34573453
$stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
34583454
@parser.parse(buff)
34593455
end
34603456

3461-
def get_response_line(buff)
3462-
line = @sock.gets(CRLF) or throw :eof
3463-
buff << line
3464-
end
3465-
3466-
def get_response_literal(buff, literal_size)
3467-
literal = String.new(capacity: literal_size)
3468-
@sock.read(literal_size, literal) or throw :eof
3469-
buff << literal
3470-
end
3471-
34723457
#############################
34733458
# built-in response handlers
34743459

@@ -3770,6 +3755,7 @@ def start_tls_session
37703755
raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
37713756
raise "cannot start TLS without SSLContext" unless ssl_ctx
37723757
@sock = SSLSocket.new(@sock, ssl_ctx)
3758+
@reader = ResponseReader.new(self, @sock)
37733759
@sock.sync_close = true
37743760
@sock.hostname = @host if @sock.respond_to? :hostname=
37753761
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
14+
end
15+
16+
def literal(str) = "{#{str.bytesize}}\r\n#{str}"
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)