Thanks to visit codestin.com
Credit goes to github.com

Skip to content

♻️ Extract ResponseReader from get_response #433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 9 additions & 23 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -792,10 +792,12 @@ class IMAP < Protocol
"UTF8=ONLY" => "UTF8=ACCEPT",
}.freeze

autoload :ConnectionState, File.expand_path("imap/connection_state", __dir__)
autoload :SASL, File.expand_path("imap/sasl", __dir__)
autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
dir = File.expand_path("imap", __dir__)
autoload :ConnectionState, "#{dir}/connection_state"
autoload :ResponseReader, "#{dir}/response_reader"
autoload :SASL, "#{dir}/sasl"
autoload :SASLAdapter, "#{dir}/sasl_adapter"
autoload :StringPrep, "#{dir}/stringprep"

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

# Connection
@sock = tcp_socket(@host, @port)
@reader = ResponseReader.new(self, @sock)
start_tls_session if ssl_ctx
start_imap_connection
end
Expand Down Expand Up @@ -3445,30 +3448,12 @@ def get_tagged_response(tag, cmd, timeout = nil)
end

def get_response
buff = String.new
catch :eof do
while true
get_response_line(buff)
break unless /\{(\d+)\}\r\n\z/n =~ buff
get_response_literal(buff, $1.to_i)
end
end
buff = @reader.read_response_buffer
return nil if buff.length == 0
$stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
@parser.parse(buff)
end

def get_response_line(buff)
line = @sock.gets(CRLF) or throw :eof
buff << line
end

def get_response_literal(buff, literal_size)
literal = String.new(capacity: literal_size)
@sock.read(literal_size, literal) or throw :eof
buff << literal
end

#############################
# built-in response handlers

Expand Down Expand Up @@ -3770,6 +3755,7 @@ def start_tls_session
raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
raise "cannot start TLS without SSLContext" unless ssl_ctx
@sock = SSLSocket.new(@sock, ssl_ctx)
@reader = ResponseReader.new(self, @sock)
@sock.sync_close = true
@sock.hostname = @host if @sock.respond_to? :hostname=
ssl_socket_connect(@sock, open_timeout)
Expand Down
38 changes: 38 additions & 0 deletions lib/net/imap/response_reader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module Net
class IMAP
# See https://www.rfc-editor.org/rfc/rfc9051#section-2.2.2
class ResponseReader # :nodoc:
attr_reader :client

def initialize(client, sock)
@client, @sock = client, sock
end
Comment on lines +7 to +11
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This version of the ResponseReader isn't actually using client yet, so I could've left that out of this PR. But upcoming PRs will need the client for passing to callbacks (and also for its config).


def read_response_buffer
buff = String.new
catch :eof do
while true
read_line(buff)
break unless /\{(\d+)\}\r\n\z/n =~ buff
read_literal(buff, $1.to_i)
end
end
buff
end

private

def read_line(buff)
buff << (@sock.gets(CRLF) or throw :eof)
end

def read_literal(buff, literal_size)
literal = String.new(capacity: literal_size)
buff << (@sock.read(literal_size, literal) or throw :eof)
end

end
end
end
49 changes: 49 additions & 0 deletions test/net/imap/test_response_reader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require "net/imap"
require "stringio"
require "test/unit"

class ResponseReaderTest < Test::Unit::TestCase
def setup
Net::IMAP.config.reset
end

class FakeClient
def config = @config ||= Net::IMAP.config.new
end

def literal(str) = "{#{str.bytesize}}\r\n#{str}"

test "#read_response_buffer" do
client = FakeClient.new
aaaaaaaaa = "a" * (20 << 10)
many_crs = "\r" * 1000
many_crlfs = "\r\n" * 500
simple = "* OK greeting\r\n"
long_line = "tag ok #{aaaaaaaaa} #{aaaaaaaaa}\r\n"
literal_aaaa = "* fake #{literal aaaaaaaaa}\r\n"
literal_crlf = "tag ok #{literal many_crlfs} #{literal many_crlfs}\r\n"
illegal_crs = "tag ok #{many_crs} #{many_crs}\r\n"
illegal_lfs = "tag ok #{literal "\r"}\n#{literal "\r"}\n\r\n"
io = StringIO.new([
simple,
long_line,
literal_aaaa,
literal_crlf,
illegal_crs,
illegal_lfs,
simple,
].join)
rcvr = Net::IMAP::ResponseReader.new(client, io)
assert_equal simple, rcvr.read_response_buffer.to_str
assert_equal long_line, rcvr.read_response_buffer.to_str
assert_equal literal_aaaa, rcvr.read_response_buffer.to_str
assert_equal literal_crlf, rcvr.read_response_buffer.to_str
assert_equal illegal_crs, rcvr.read_response_buffer.to_str
assert_equal illegal_lfs, rcvr.read_response_buffer.to_str
assert_equal simple, rcvr.read_response_buffer.to_str
assert_equal "", rcvr.read_response_buffer.to_str
end

end