diff --git a/lib/net/imap.rb b/lib/net/imap.rb
index 54753b5a..3ee77208 100644
--- a/lib/net/imap.rb
+++ b/lib/net/imap.rb
@@ -404,6 +404,12 @@ module Net
# - #connection_state: Returns the connection state.
# - #disconnected?: True if the connection has been closed.
# - #tls_verified?: Returns whether TLS is used and #host has been verified.
+ # - #tls_connected?: Returns +true+ after TLS negotiation has completed.
+ #
+ # *NOTE:* This does _NOT_ indicate a secure TLS connection.
+ # - #tls_socket?: Returns +true+ after TLS negotiation has started.
+ #
+ # *NOTE:* This does _NOT_ indicate a secure TLS connection.
# - #ssl_ctx: Returns the {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]
# after attempting to start TLS.
#
@@ -1035,8 +1041,10 @@ def max_response_size=(val) config.max_response_size = val end
# Returns the
# {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]
- # used by the SSLSocket when TLS is attempted, even when the TLS handshake
- # is unsuccessful. The context object will be frozen.
+ # used by the
+ # {OpenSSL::SSL::SSLSocket}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLSocket.html].
+ # when TLS is attempted, even when the TLS handshake is unsuccessful. The
+ # context object will be frozen.
#
# Returns +nil+ for a plaintext connection.
#
@@ -1299,12 +1307,11 @@ def inspect
end
private def inspect_tls_state
- if tls_verified?
- "TLS"
- elsif ssl_ctx && @sock.kind_of?(OpenSSL::SSL::SSLSocket)
- "TLS (#{@tls_connected ? "NOT VERIFIED" : "NOT ESTABLISHED"})"
- else
- "PLAINTEXT#{" (TLS NOT STARTED)" if ssl_ctx}"
+ if tls_verified? then "TLS"
+ elsif tls_connected? then "TLS (NOT VERIFIED)"
+ elsif tls_socket? then "TLS (NOT ESTABLISHED)"
+ elsif ssl_ctx then "PLAINTEXT (TLS NOT STARTED)"
+ else "PLAINTEXT"
end
end
@@ -1313,6 +1320,30 @@ def inspect
# but peer verification was disabled.
def tls_verified?; @tls_verified end
+ # Returns +true+ after
+ # {OpenSSL::SSL::SSLSocket#connect}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLSocket.html#method-i-connect]
+ # completes successfully.
+ #
+ # *NOTE:* This does _NOT_ indicate that the remote hostname has been
+ # verified.
+ #
+ # This does _not_ indicate current connection state. It will continue to
+ # return +true+ even after a successful connection has disconnected.
+ #
+ # See #tls_verified?
+ def tls_connected?; @tls_connected end
+
+ # Returns +true+ when the connection is a
+ # {OpenSSL::SSL::SSLSocket}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLSocket.html]
+ #
+ # *NOTE:* This does _NOT_ indicate that a TLS session has been
+ # established or that remote hostname has been verified.
+ #
+ # This only indicates that TLS negotiation has started.
+ #
+ # See #tls_verified?
+ def tls_socket?; @sock.kind_of?(OpenSSL::SSL::SSLSocket) end
+
# Disconnects from the server.
#
# Waits for receiver thread to close before returning, except when called
diff --git a/test/net/imap/test_imap_tls.rb b/test/net/imap/test_imap_tls.rb
index b5873299..12ad1476 100644
--- a/test/net/imap/test_imap_tls.rb
+++ b/test/net/imap/test_imap_tls.rb
@@ -35,6 +35,8 @@ def assert_tls_incomplete(imap) = assert_tls_stage imap, :incomplete
def assert_tls_unverified(imap) = assert_tls_stage imap, :unverified
def assert_tls_verified(imap) = assert_tls_stage imap, :verified
+ STAGES = %i[unstarted incomplete unverified verified].freeze
+
INSPECT_INCLUDES = {
unstarted: " PLAINTEXT (TLS NOT STARTED) disconnected",
incomplete: " TLS (NOT ESTABLISHED) disconnected",
@@ -43,12 +45,16 @@ def assert_tls_verified(imap) = assert_tls_stage imap, :verified
}
def assert_tls_stage(imap, stage)
- verified = stage == :verified
+ socket, connected, verified = Array.new(3) { _1 < STAGES.index(stage) }
assert imap.ssl_ctx_params.frozen?, "#ssl_ctx_params should be frozen"
assert_kind_of Hash, imap.ssl_ctx_params,
"#ssl_ctx_params should be a Hash"
assert_kind_of OpenSSL::SSL::SSLContext, imap.ssl_ctx,
"#ssl_ctx should be an OpenSSL::SSL::SSLContext"
+ assert_equal socket, imap.tls_socket?,
+ "#tls_socket? should be #{socket}"
+ assert_equal connected, imap.tls_connected?,
+ "#tls_connected? should be #{connected}"
assert_equal verified, imap.tls_verified?,
"#tls_verified? should be #{verified}"
assert_include imap.inspect, INSPECT_INCLUDES.fetch(stage)