diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb
index 13e1f1f..087bec7 100644
--- a/lib/net/smtp.rb
+++ b/lib/net/smtp.rb
@@ -642,7 +642,7 @@ def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil
do_start helo, user, secret, authtype
return yield(self)
ensure
- do_finish
+ quit!
end
else
do_start helo, user, secret, authtype
@@ -654,7 +654,7 @@ def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil
# Raises IOError if not started.
def finish
raise IOError, 'not yet started' unless started?
- do_finish
+ quit!
end
private
@@ -728,15 +728,47 @@ def do_helo(helo_domain)
raise
end
- def do_finish
+ public
+
+ # Calls #quit and ensures that #disconnect is called. Returns the result
+ # from #quit. Returns +nil+ when the client is already disconnected or when
+ # a prior error prevents the client from calling #quit. Unlike #finish, an
+ # exception will not be raised when the client has not started.
+ #
+ # If #quit raises a StandardError, the connection is dropped and the
+ # exception is re-raised. When exception: :warn is specified, a
+ # warning is printed and the exception is returned. When warning:
+ # false, the warning is not printed. This is useful when the
+ # connection must be dropped, for example in a test suite or due to security
+ # concerns.
+ #
+ # Related: #finish, #quit, #disconnect
+ def quit!(exception: true)
quit if @socket and not @socket.closed? and not @error_occurred
+ rescue => ex
+ if exception == :warn
+ warn "%s during %p #%s: %s" % [ex.class, self, __method__, ex]
+ elsif exception
+ raise ex
+ end
+ ex
ensure
+ disconnect
+ end
+
+ # Disconnects the socket without checking if the connection has started yet,
+ # and without sending a final QUIT message to the server.
+ #
+ # Generally, either #finish or #quit! should be used instead.
+ def disconnect
@started = false
@error_occurred = false
@socket.close if @socket
@socket = nil
end
+ private
+
def requires_smtputf8(address)
if address.kind_of? Address
!address.address.ascii_only?
diff --git a/test/net/smtp/test_smtp.rb b/test/net/smtp/test_smtp.rb
index 320e75d..b394cb8 100644
--- a/test/net/smtp/test_smtp.rb
+++ b/test/net/smtp/test_smtp.rb
@@ -594,6 +594,72 @@ def test_mailfrom_with_smtputf_detection
assert_equal "MAIL FROM: SMTPUTF8\r\n", server.commands.last
end
+ def test_quit
+ server = FakeServer.start
+ smtp = Net::SMTP.start 'localhost', server.port
+ smtp.quit
+ assert_equal "QUIT\r\n", server.commands.last
+
+ # Already finished:
+ assert_raise EOFError do
+ smtp.quit
+ end
+
+ server = FakeServer.start
+ def server.quit
+ @sock.puts "400 BUSY\r\n"
+ end
+ smtp = Net::SMTP.start 'localhost', server.port
+ assert_raise Net::SMTPServerBusy do
+ smtp.quit
+ end
+ assert_equal "QUIT\r\n", server.commands.last
+ assert smtp.started?
+ end
+
+ def test_quit!
+ server = FakeServer.start
+ smtp = Net::SMTP.start 'localhost', server.port
+ smtp.quit!
+ assert_equal "QUIT\r\n", server.commands.last
+ refute smtp.started?
+
+ # Already finished:
+ smtp.quit!
+ end
+
+ def test_quit_and_reraise
+ server = FakeServer.start
+ def server.quit
+ @sock.puts "400 BUSY\r\n"
+ end
+ smtp = Net::SMTP.start 'localhost', server.port
+ assert_raise Net::SMTPServerBusy do
+ smtp.quit!
+ end
+ assert_equal "QUIT\r\n", server.commands.last
+ refute smtp.started?
+ end
+
+ def test_quit_and_ignore
+ server = FakeServer.start
+ def server.quit
+ @sock.puts "400 BUSY\r\n"
+ end
+ smtp = Net::SMTP.start 'localhost', server.port
+ smtp.quit!(exception: false)
+ assert_equal "QUIT\r\n", server.commands.last
+ refute smtp.started?
+ end
+
+ def test_disconnect
+ server = FakeServer.start
+ smtp = Net::SMTP.start 'localhost', server.port
+ smtp.disconnect
+ assert_equal "EHLO localhost\r\n", server.commands.last
+ refute smtp.started?
+ end
+
def fake_server_start(**kw)
server = FakeServer.new
server.start(**kw)