From 53ceba1e97cbc3ac4d141077732178cc8bc79476 Mon Sep 17 00:00:00 2001 From: nick evans Date: Sat, 19 Apr 2025 22:21:59 -0400 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20Limit=20max=20response=20size?= =?UTF-8?q?=20to=20512MiB=20(hard-coded)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _Please note:_ this only limits the size per response. It does _not_ limit how many unhandled responses may be stored on the responses hash. --- lib/net/imap/errors.rb | 33 ++++++++++++++++++++++ lib/net/imap/response_reader.rb | 31 +++++++++++++++++++-- test/net/imap/test_errors.rb | 40 +++++++++++++++++++++++++++ test/net/imap/test_response_reader.rb | 23 +++++++++++++++ 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 test/net/imap/test_errors.rb diff --git a/lib/net/imap/errors.rb b/lib/net/imap/errors.rb index b353756fc..dcb5091b0 100644 --- a/lib/net/imap/errors.rb +++ b/lib/net/imap/errors.rb @@ -11,6 +11,39 @@ class Error < StandardError class DataFormatError < Error end + # Error raised when the socket cannot be read, due to a configured limit. + class ResponseReadError < Error + end + + # Error raised when a response is larger than IMAP#max_response_size. + class ResponseTooLargeError < ResponseReadError + attr_reader :bytes_read, :literal_size + attr_reader :max_response_size + + def initialize(msg = nil, *args, + bytes_read: nil, + literal_size: nil, + max_response_size: nil, + **kwargs) + @bytes_read = bytes_read + @literal_size = literal_size + @max_response_size = max_response_size + msg ||= [ + "Response size", response_size_msg, "exceeds max_response_size", + max_response_size && "(#{max_response_size}B)", + ].compact.join(" ") + super(msg, *args, **kwargs) + end + + private + + def response_size_msg + if bytes_read && literal_size + "(#{bytes_read}B read + #{literal_size}B literal)" + end + end + end + # Error raised when a response from the server is non-parseable. class ResponseParseError < Error end diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb index 6a608b163..3c33dea39 100644 --- a/lib/net/imap/response_reader.rb +++ b/lib/net/imap/response_reader.rb @@ -28,19 +28,46 @@ def read_response_buffer attr_reader :buff, :literal_size + def bytes_read = buff.bytesize + def empty? = buff.empty? + def done? = line_done? && !get_literal_size + def line_done? = buff.end_with?(CRLF) def get_literal_size; /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i end def read_line - buff << (@sock.gets(CRLF) or throw :eof) + buff << (@sock.gets(CRLF, read_limit) or throw :eof) + max_response_remaining! unless line_done? end def read_literal + # check before allocating memory for literal + max_response_remaining! literal = String.new(capacity: literal_size) - buff << (@sock.read(literal_size, literal) or throw :eof) + buff << (@sock.read(read_limit(literal_size), literal) or throw :eof) ensure @literal_size = nil end + def read_limit(limit = nil) + [limit, max_response_remaining!].compact.min + end + + def max_response_size = 512 << 20 # TODO: Config#max_response_size + def max_response_remaining = max_response_size &.- bytes_read + def response_too_large? = max_response_size &.< min_response_size + def min_response_size = bytes_read + min_response_remaining + + def min_response_remaining + empty? ? 3 : done? ? 0 : (literal_size || 0) + 2 + end + + def max_response_remaining! + return max_response_remaining unless response_too_large? + raise ResponseTooLargeError.new( + max_response_size:, bytes_read:, literal_size:, + ) + end + end end end diff --git a/test/net/imap/test_errors.rb b/test/net/imap/test_errors.rb new file mode 100644 index 000000000..a6a7cb0f4 --- /dev/null +++ b/test/net/imap/test_errors.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "net/imap" +require "test/unit" + +class IMAPErrorsTest < Test::Unit::TestCase + + test "ResponseTooLargeError" do + err = Net::IMAP::ResponseTooLargeError.new + assert_nil err.bytes_read + assert_nil err.literal_size + assert_nil err.max_response_size + + err = Net::IMAP::ResponseTooLargeError.new("manually set message") + assert_equal "manually set message", err.message + assert_nil err.bytes_read + assert_nil err.literal_size + assert_nil err.max_response_size + + err = Net::IMAP::ResponseTooLargeError.new(max_response_size: 1024) + assert_equal "Response size exceeds max_response_size (1024B)", err.message + assert_nil err.bytes_read + assert_nil err.literal_size + assert_equal 1024, err.max_response_size + + err = Net::IMAP::ResponseTooLargeError.new(bytes_read: 1200, + max_response_size: 1200) + assert_equal 1200, err.bytes_read + assert_equal "Response size exceeds max_response_size (1200B)", err.message + + err = Net::IMAP::ResponseTooLargeError.new(bytes_read: 800, + literal_size: 1000, + max_response_size: 1200) + assert_equal 800, err.bytes_read + assert_equal 1000, err.literal_size + assert_equal("Response size (800B read + 1000B literal) " \ + "exceeds max_response_size (1200B)", err.message) + end + +end diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb index 9a8c63dcd..5a1491bee 100644 --- a/test/net/imap/test_response_reader.rb +++ b/test/net/imap/test_response_reader.rb @@ -44,4 +44,27 @@ def literal(str) "{#{str.bytesize}}\r\n#{str}" end assert_equal "", rcvr.read_response_buffer.to_str end + class LimitedResponseReader < Net::IMAP::ResponseReader + attr_reader :max_response_size + def initialize(*args, max_response_size:) + super(*args) + @max_response_size = max_response_size + end + end + + test "#read_response_buffer with max_response_size" do + client = FakeClient.new + max_response_size = 10 + under = "+ 3456\r\n" + exact = "+ 345678\r\n" + over = "+ 3456789\r\n" + io = StringIO.new([under, exact, over].join) + rcvr = LimitedResponseReader.new(client, io, max_response_size:) + assert_equal under, rcvr.read_response_buffer.to_str + assert_equal exact, rcvr.read_response_buffer.to_str + assert_raise Net::IMAP::ResponseTooLargeError do + rcvr.read_response_buffer + end + end + end From 158cfdff54f3961b0ec628136444e3b0b0bb1736 Mon Sep 17 00:00:00 2001 From: nick evans Date: Sun, 20 Apr 2025 17:38:43 -0400 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=A8=20Make=20max=5Fresponse=5Fsize=20?= =?UTF-8?q?configurable=20[=F0=9F=9A=A7=20partial]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note that this cherry-picked commit is missing key paits that are incompatible with net-imap before 0.4. I'm keeping the conflict resolution here, and the updates for net-imap 0.3 in the next commit. ------ Though it would be useful to also have limits based on response type and what commands are currently running, that's out of scope for now. _Please note:_ this only limits the size per response. It does _not_ limit how many unhandled responses may be stored on the responses hash. --- lib/net/imap.rb | 15 +++++ lib/net/imap/response_reader.rb | 2 +- test/net/imap/test_imap_max_response_size.rb | 67 ++++++++++++++++++++ test/net/imap/test_response_reader.rb | 13 +--- 4 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 test/net/imap/test_imap_max_response_size.rb diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 9560d0740..954dffafb 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -159,6 +159,10 @@ module Net # # Use paginated or limited versions of commands whenever possible. # + # Use Config#max_response_size to impose a limit on incoming server responses + # as they are being read. This is especially important for untrusted + # servers. + # # Use #add_response_handler to handle responses after each one is received. # Use the +response_handlers+ argument to ::new to assign response handlers # before the receiver thread is started. @@ -800,6 +804,17 @@ class << self alias default_ssl_port default_tls_port end + ## + # :attr_accessor: max_response_size + # + # The maximum allowed server response size, in bytes. + # Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size]. + + # :stopdoc: + def max_response_size; config.max_response_size end + def max_response_size=(val) config.max_response_size = val end + # :startdoc: + # Disconnects from the server. # # Related: #logout diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb index 3c33dea39..bcf360172 100644 --- a/lib/net/imap/response_reader.rb +++ b/lib/net/imap/response_reader.rb @@ -52,7 +52,7 @@ def read_limit(limit = nil) [limit, max_response_remaining!].compact.min end - def max_response_size = 512 << 20 # TODO: Config#max_response_size + def max_response_size = client.max_response_size def max_response_remaining = max_response_size &.- bytes_read def response_too_large? = max_response_size &.< min_response_size def min_response_size = bytes_read + min_response_remaining diff --git a/test/net/imap/test_imap_max_response_size.rb b/test/net/imap/test_imap_max_response_size.rb new file mode 100644 index 000000000..3751d0bc8 --- /dev/null +++ b/test/net/imap/test_imap_max_response_size.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "net/imap" +require "test/unit" +require_relative "fake_server" + +class IMAPMaxResponseSizeTest < Test::Unit::TestCase + include Net::IMAP::FakeServer::TestHelper + + def setup + Net::IMAP.config.reset + @do_not_reverse_lookup = Socket.do_not_reverse_lookup + Socket.do_not_reverse_lookup = true + @threads = [] + end + + def teardown + if !@threads.empty? + assert_join_threads(@threads) + end + ensure + Socket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + test "#max_response_size reading literals" do + with_fake_server(preauth: true) do |server, imap| + imap.max_response_size = 12_345 + 30 + server.on("NOOP") do |resp| + resp.untagged("1 FETCH (BODY[] {12345}\r\n" + "a" * 12_345 + ")") + resp.done_ok + end + imap.noop + assert_equal "a" * 12_345, imap.responses("FETCH").first.message + end + end + + test "#max_response_size closes connection for too long line" do + Net::IMAP.config.max_response_size = 10 + run_fake_server_in_thread(preauth: false, ignore_io_error: true) do |server| + assert_raise_with_message( + Net::IMAP::ResponseTooLargeError, /exceeds max_response_size .*\b10B\b/ + ) do + with_client("localhost", port: server.port) do + fail "should not get here (greeting longer than max_response_size)" + end + end + end + end + + test "#max_response_size closes connection for too long literal" do + Net::IMAP.config.max_response_size = 1<<20 + with_fake_server(preauth: false, ignore_io_error: true) do |server, client| + client.max_response_size = 50 + server.on("NOOP") do |resp| + resp.untagged("1 FETCH (BODY[] {1000}\r\n" + "a" * 1000 + ")") + end + assert_raise_with_message( + Net::IMAP::ResponseTooLargeError, + /\d+B read \+ 1000B literal.* exceeds max_response_size .*\b50B\b/ + ) do + client.noop + fail "should not get here (FETCH literal longer than max_response_size)" + end + end + end + +end diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb index 5a1491bee..6b58d555b 100644 --- a/test/net/imap/test_response_reader.rb +++ b/test/net/imap/test_response_reader.rb @@ -6,6 +6,7 @@ class ResponseReaderTest < Test::Unit::TestCase class FakeClient + def max_response_size = config.max_response_size end def literal(str) "{#{str.bytesize}}\r\n#{str}" end @@ -44,22 +45,14 @@ def literal(str) "{#{str.bytesize}}\r\n#{str}" end assert_equal "", rcvr.read_response_buffer.to_str end - class LimitedResponseReader < Net::IMAP::ResponseReader - attr_reader :max_response_size - def initialize(*args, max_response_size:) - super(*args) - @max_response_size = max_response_size - end - end - test "#read_response_buffer with max_response_size" do client = FakeClient.new - max_response_size = 10 + client.config.max_response_size = 10 under = "+ 3456\r\n" exact = "+ 345678\r\n" over = "+ 3456789\r\n" io = StringIO.new([under, exact, over].join) - rcvr = LimitedResponseReader.new(client, io, max_response_size:) + rcvr = Net::IMAP::ResponseReader.new(client, io) assert_equal under, rcvr.read_response_buffer.to_str assert_equal exact, rcvr.read_response_buffer.to_str assert_raise Net::IMAP::ResponseTooLargeError do From ae0fa010bb5e3c95b9beee31af607d4dba619d63 Mon Sep 17 00:00:00 2001 From: nick evans Date: Sun, 20 Apr 2025 21:01:48 -0400 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=85=20Fix=20backport=20compatibility?= =?UTF-8?q?=20with=20ruby=202.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/net/imap/response_reader.rb | 20 +++++++++++--------- test/net/imap/test_response_reader.rb | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb index bcf360172..fd7561fa7 100644 --- a/lib/net/imap/response_reader.rb +++ b/lib/net/imap/response_reader.rb @@ -28,10 +28,10 @@ def read_response_buffer attr_reader :buff, :literal_size - def bytes_read = buff.bytesize - def empty? = buff.empty? - def done? = line_done? && !get_literal_size - def line_done? = buff.end_with?(CRLF) + def bytes_read; buff.bytesize end + def empty?; buff.empty? end + def done?; line_done? && !get_literal_size end + def line_done?; buff.end_with?(CRLF) end def get_literal_size; /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i end def read_line @@ -52,10 +52,10 @@ def read_limit(limit = nil) [limit, max_response_remaining!].compact.min end - def max_response_size = client.max_response_size - def max_response_remaining = max_response_size &.- bytes_read - def response_too_large? = max_response_size &.< min_response_size - def min_response_size = bytes_read + min_response_remaining + def max_response_size; client.max_response_size end + def max_response_remaining; max_response_size &.- bytes_read end + def response_too_large?; max_response_size &.< min_response_size end + def min_response_size; bytes_read + min_response_remaining end def min_response_remaining empty? ? 3 : done? ? 0 : (literal_size || 0) + 2 @@ -64,7 +64,9 @@ def min_response_remaining def max_response_remaining! return max_response_remaining unless response_too_large? raise ResponseTooLargeError.new( - max_response_size:, bytes_read:, literal_size:, + max_response_size: max_response_size, + bytes_read: bytes_read, + literal_size: literal_size, ) end diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb index 6b58d555b..716922d99 100644 --- a/test/net/imap/test_response_reader.rb +++ b/test/net/imap/test_response_reader.rb @@ -6,7 +6,7 @@ class ResponseReaderTest < Test::Unit::TestCase class FakeClient - def max_response_size = config.max_response_size + def max_response_size; config.max_response_size end end def literal(str) "{#{str.bytesize}}\r\n#{str}" end From e0059251e854cb03d5209c682ba3484fcb6953cd Mon Sep 17 00:00:00 2001 From: nick evans Date: Wed, 9 Apr 2025 09:54:51 -0400 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=85=20Fix=20backport=20to=20not-imap?= =?UTF-8?q?=200.3=20and=20ruby=202.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For the net-imap v0.3 backport, two major changes were needed: * the tests needed to be almost completely rewritten because FakeServer was added for v0.4. * `max_response_size` needed to be on Net::IMAP directly, because Config was added for v0.4. --- lib/net/imap.rb | 49 +++++++-- lib/net/imap/errors.rb | 1 + test/net/imap/test_imap_max_response_size.rb | 109 +++++++++++++------ test/net/imap/test_response_reader.rb | 4 +- 4 files changed, 118 insertions(+), 45 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 954dffafb..283fee289 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -159,7 +159,7 @@ module Net # # Use paginated or limited versions of commands whenever possible. # - # Use Config#max_response_size to impose a limit on incoming server responses + # Use #max_response_size to impose a limit on incoming server responses # as they are being read. This is especially important for untrusted # servers. # @@ -776,6 +776,40 @@ class IMAP < Protocol # Seconds to wait until an IDLE response is received. attr_reader :idle_response_timeout + # The maximum allowed server response size. When +nil+, there is no limit + # on response size. + # + # The default value is _unlimited_ (after +v0.5.8+, the default is 512 MiB). + # A _much_ lower value should be used with untrusted servers (for example, + # when connecting to a user-provided hostname). When using a lower limit, + # message bodies should be fetched in chunks rather than all at once. + # + # Please Note: this only limits the size per response. It does + # not prevent a flood of individual responses and it does not limit how + # many unhandled responses may be stored on the responses hash. See + # Net::IMAP@Unbounded+memory+use. + # + # Socket reads are limited to the maximum remaining bytes for the current + # response: max_response_size minus the bytes that have already been read. + # When the limit is reached, or reading a +literal+ _would_ go over the + # limit, ResponseTooLargeError is raised and the connection is closed. + # See also #socket_read_limit. + # + # Note that changes will not take effect immediately, because the receiver + # thread may already be waiting for the next response using the previous + # value. Net::IMAP#noop can force a response and enforce the new setting + # immediately. + # + # ==== Versioned Defaults + # + # Net::IMAP#max_response_size was added in +v0.2.5+ and +v0.3.9+ as an + # attr_accessor, and in +v0.4.20+ and +v0.5.7+ as a delegator to a config + # attribute. + # + # * original: +nil+ (no limit) + # * +0.5+: 512 MiB + attr_accessor :max_response_size + attr_accessor :client_thread # :nodoc: # Returns the debug mode. @@ -804,17 +838,6 @@ class << self alias default_ssl_port default_tls_port end - ## - # :attr_accessor: max_response_size - # - # The maximum allowed server response size, in bytes. - # Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size]. - - # :stopdoc: - def max_response_size; config.max_response_size end - def max_response_size=(val) config.max_response_size = val end - # :startdoc: - # Disconnects from the server. # # Related: #logout @@ -2059,6 +2082,7 @@ def remove_response_handler(handler) # that the greeting is handled in the current thread, # but all other responses are handled in the receiver # thread. + # max_response_size:: See #max_response_size. # # The most common errors are: # @@ -2089,6 +2113,7 @@ def initialize(host, port_or_options = {}, @tagno = 0 @open_timeout = options[:open_timeout] || 30 @idle_response_timeout = options[:idle_response_timeout] || 5 + @max_response_size = options[:max_response_size] @parser = ResponseParser.new @sock = tcp_socket(@host, @port) @reader = ResponseReader.new(self, @sock) diff --git a/lib/net/imap/errors.rb b/lib/net/imap/errors.rb index dcb5091b0..52cb936b4 100644 --- a/lib/net/imap/errors.rb +++ b/lib/net/imap/errors.rb @@ -32,6 +32,7 @@ def initialize(msg = nil, *args, "Response size", response_size_msg, "exceeds max_response_size", max_response_size && "(#{max_response_size}B)", ].compact.join(" ") + return super(msg, *args) if kwargs.empty? # ruby 2.6 compatibility super(msg, *args, **kwargs) end diff --git a/test/net/imap/test_imap_max_response_size.rb b/test/net/imap/test_imap_max_response_size.rb index 3751d0bc8..7ec554c3b 100644 --- a/test/net/imap/test_imap_max_response_size.rb +++ b/test/net/imap/test_imap_max_response_size.rb @@ -2,13 +2,10 @@ require "net/imap" require "test/unit" -require_relative "fake_server" class IMAPMaxResponseSizeTest < Test::Unit::TestCase - include Net::IMAP::FakeServer::TestHelper def setup - Net::IMAP.config.reset @do_not_reverse_lookup = Socket.do_not_reverse_lookup Socket.do_not_reverse_lookup = true @threads = [] @@ -23,45 +20,95 @@ def teardown end test "#max_response_size reading literals" do - with_fake_server(preauth: true) do |server, imap| + _, port = with_server_socket do |sock| + sock.gets # => NOOP + sock.print("RUBY0001 OK done\r\n") + sock.gets # => NOOP + sock.print("* 1 FETCH (BODY[] {12345}\r\n" + "a" * 12_345 + ")\r\n") + sock.print("RUBY0002 OK done\r\n") + "RUBY0003" + end + Timeout.timeout(5) do + imap = Net::IMAP.new("localhost", port: port, max_response_size: 640 << 20) + assert_equal 640 << 20, imap.max_response_size imap.max_response_size = 12_345 + 30 - server.on("NOOP") do |resp| - resp.untagged("1 FETCH (BODY[] {12345}\r\n" + "a" * 12_345 + ")") - resp.done_ok - end - imap.noop - assert_equal "a" * 12_345, imap.responses("FETCH").first.message + assert_equal 12_345 + 30, imap.max_response_size + imap.noop # to reset the get_response limit + imap.noop # to send the FETCH + assert_equal "a" * 12_345, imap.responses["FETCH"].first.attr["BODY[]"] + ensure + imap.logout rescue nil + imap.disconnect rescue nil end end test "#max_response_size closes connection for too long line" do - Net::IMAP.config.max_response_size = 10 - run_fake_server_in_thread(preauth: false, ignore_io_error: true) do |server| - assert_raise_with_message( - Net::IMAP::ResponseTooLargeError, /exceeds max_response_size .*\b10B\b/ - ) do - with_client("localhost", port: server.port) do - fail "should not get here (greeting longer than max_response_size)" - end - end + _, port = with_server_socket do |sock| + sock.gets or next # => never called + fail "client disconnects first" + end + assert_raise_with_message( + Net::IMAP::ResponseTooLargeError, /exceeds max_response_size .*\b10B\b/ + ) do + Net::IMAP.new("localhost", port: port, max_response_size: 10) + fail "should not get here (greeting longer than max_response_size)" end end test "#max_response_size closes connection for too long literal" do - Net::IMAP.config.max_response_size = 1<<20 - with_fake_server(preauth: false, ignore_io_error: true) do |server, client| - client.max_response_size = 50 - server.on("NOOP") do |resp| - resp.untagged("1 FETCH (BODY[] {1000}\r\n" + "a" * 1000 + ")") - end - assert_raise_with_message( - Net::IMAP::ResponseTooLargeError, - /\d+B read \+ 1000B literal.* exceeds max_response_size .*\b50B\b/ - ) do - client.noop - fail "should not get here (FETCH literal longer than max_response_size)" + _, port = with_server_socket(ignore_io_error: true) do |sock| + sock.gets # => NOOP + sock.print "* 1 FETCH (BODY[] {1000}\r\n" + "a" * 1000 + ")\r\n" + sock.print("RUBY0001 OK done\r\n") + end + client = Net::IMAP.new("localhost", port: port, max_response_size: 1000) + assert_equal 1000, client.max_response_size + client.max_response_size = 50 + assert_equal 50, client.max_response_size + assert_raise_with_message( + Net::IMAP::ResponseTooLargeError, + /\d+B read \+ 1000B literal.* exceeds max_response_size .*\b50B\b/ + ) do + client.noop + fail "should not get here (FETCH literal longer than max_response_size)" + end + end + + def with_server_socket(ignore_io_error: false) + server = create_tcp_server + port = server.addr[1] + start_server do + Timeout.timeout(5) do + sock = server.accept + sock.print("* OK connection established\r\n") + logout_tag = yield sock if block_given? + sock.gets # => LOGOUT + sock.print("* BYE terminating connection\r\n") + sock.print("#{logout_tag} OK LOGOUT completed\r\n") if logout_tag + rescue IOError, EOFError, Errno::ECONNABORTED, Errno::ECONNRESET, + Errno::EPIPE, Errno::ETIMEDOUT + ignore_io_error or raise + ensure + sock.close rescue nil + server.close rescue nil end end + return server, port + end + + def start_server + th = Thread.new do + yield + end + @threads << th + sleep 0.1 until th.stop? end + def create_tcp_server + return TCPServer.new(server_addr, 0) + end + + def server_addr + Addrinfo.tcp("localhost", 0).ip_address + end end diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb index 716922d99..d2c1c11aa 100644 --- a/test/net/imap/test_response_reader.rb +++ b/test/net/imap/test_response_reader.rb @@ -6,7 +6,7 @@ class ResponseReaderTest < Test::Unit::TestCase class FakeClient - def max_response_size; config.max_response_size end + attr_accessor :max_response_size end def literal(str) "{#{str.bytesize}}\r\n#{str}" end @@ -47,7 +47,7 @@ def literal(str) "{#{str.bytesize}}\r\n#{str}" end test "#read_response_buffer with max_response_size" do client = FakeClient.new - client.config.max_response_size = 10 + client.max_response_size = 10 under = "+ 3456\r\n" exact = "+ 345678\r\n" over = "+ 3456789\r\n"