diff --git a/test/net/imap/fake_server/command_reader.rb b/test/net/imap/fake_server/command_reader.rb index 03d1da50..8857cd9b 100644 --- a/test/net/imap/fake_server/command_reader.rb +++ b/test/net/imap/fake_server/command_reader.rb @@ -3,13 +3,15 @@ require "net/imap" class Net::IMAP::FakeServer - CommandParseError = RuntimeError + CommandParseError = Class.new(RuntimeError) class CommandReader + attr_reader :config attr_reader :last_command attr_accessor :literal_acceptor - def initialize(socket) + def initialize(socket, config:) + @config = config @socket = socket @last_command = nil @literal_acceptor = proc {|buff, size| true } @@ -35,8 +37,11 @@ def get_command end throw :eof if buf.empty? @last_command = parse(buf) - rescue CommandParseError => err - raise IOError, err.message if socket.eof? && !buf.end_with?("\r\n") + rescue CommandParseError + if config.ignore_abrupt_eof? && socket.eof? && !buf.end_with?("\r\n") + throw :eof + end + raise end private @@ -46,7 +51,7 @@ def get_command # TODO: convert bad command exception to tagged BAD response, when possible def parse(buf) /\A([^ ]+) ((?:UID )?\w+)(?: (.+))?\r\n\z/min =~ buf or - raise CommandParseError, "bad request: %p" [buf] + raise CommandParseError, "bad request: %p" % [buf] case $2.upcase when "LOGIN", "SELECT", "EXAMINE", "ENABLE", "AUTHENTICATE" Command.new $1, $2, scan_astrings($3), buf diff --git a/test/net/imap/fake_server/configuration.rb b/test/net/imap/fake_server/configuration.rb index 91fe72a4..fde14aa7 100644 --- a/test/net/imap/fake_server/configuration.rb +++ b/test/net/imap/fake_server/configuration.rb @@ -45,6 +45,8 @@ class Configuration mailboxes: { "INBOX" => { name: "INBOX" }.freeze, }.freeze, + + ignore_abrupt_eof: false, } def initialize(with_extensions: [], without_extensions: [], **opts, &block) @@ -68,6 +70,7 @@ def initialize(with_extensions: [], without_extensions: [], **opts, &block) alias greeting_bye? greeting_bye alias greeting_capabilities? greeting_capabilities alias sasl_ir? sasl_ir + alias ignore_abrupt_eof? ignore_abrupt_eof def on(event, &handler) handler or raise ArgumentError diff --git a/test/net/imap/fake_server/connection.rb b/test/net/imap/fake_server/connection.rb index 7be9902b..bd922577 100644 --- a/test/net/imap/fake_server/connection.rb +++ b/test/net/imap/fake_server/connection.rb @@ -12,7 +12,7 @@ def initialize(server, tcp_socket:) @config = server.config @socket = Socket.new tcp_socket, config: config @state = ConnectionState.new socket: socket, config: config - @reader = CommandReader.new socket + @reader = CommandReader.new socket, config: config @writer = ResponseWriter.new socket, config: config, state: state @router = CommandRouter.new writer, config: config, state: state @mutex = Thread::Mutex.new diff --git a/test/net/imap/fake_server/socket.rb b/test/net/imap/fake_server/socket.rb index 65593d86..84e94d58 100644 --- a/test/net/imap/fake_server/socket.rb +++ b/test/net/imap/fake_server/socket.rb @@ -18,10 +18,10 @@ def initialize(tcp_socket, config:) def tls?; !!@tls_socket end def closed?; @closed end - def eof?; socket.eof? end - def gets(...) socket.gets(...) end - def read(...) socket.read(...) end - def print(...) socket.print(...) end + def eof?; ignore_closed?(true) { socket.eof? } end + def gets(...) ignore_closed?(nil) { socket.gets(...) } end + def read(...) ignore_closed?(nil) { socket.read(...) } end + def print(...) ignore_closed?(nil) { socket.print(...) } end def use_tls @tls_socket ||= OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_ctx).tap do |s| @@ -48,5 +48,13 @@ def ssl_ctx end end + def ignore_closed?(fallback) + yield + rescue IOError => err + close if !closed? && (@tcp_socket.closed? || @tls_socket.closed?) + return fallback if err.message.match?(/stream closed|closed stream/i) + raise + end + end end