@@ -173,6 +173,7 @@ module Net
173
173
# == What's here?
174
174
#
175
175
# * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods]
176
+ # * {Server capabilities}[rdoc-ref:Net::IMAP@Server+capabilities]
176
177
# * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
177
178
# * {...for any state}[rdoc-ref:Net::IMAP@IMAP+commands+for+any+state]
178
179
# * {...for the "not authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Not+Authenticated-22+state]
@@ -191,6 +192,16 @@ module Net
191
192
# - #disconnect: Disconnects the connection (without sending #logout first).
192
193
# - #disconnected?: True if the connection has been closed.
193
194
#
195
+ # === Server capabilities
196
+ #
197
+ # - #capable?: Returns whether the server supports a given capability.
198
+ # - #capabilities: Returns the server's capabilities as a list of strings.
199
+ # - #clear_cached_capabilities: Clears cached capabilities.
200
+ #
201
+ # <em>The capabilities cache is automatically cleared after completing
202
+ # #starttls, #login, or #authenticate.</em>
203
+ # - #capability: Sends the +CAPABILITY+ command and returns the #capabilities.
204
+ #
194
205
# === Core \IMAP commands
195
206
#
196
207
# The following commands are defined either by
@@ -227,8 +238,8 @@ module Net
227
238
#
228
239
# - #capability: Returns the server's capabilities as an array of strings.
229
240
#
230
- # <em>Capabilities may change after</em> #starttls, #authenticate, or #login
231
- # <em>and cached capabilities must be reloaded .</em>
241
+ # <em>In general, #capable? should be used rather than explicitly sending a
242
+ # +CAPABILITY+ command to the server .</em>
232
243
# - #noop: Allows the server to send unsolicited untagged #responses.
233
244
# - #logout: Tells the server to end the session. Enters the "_logout_" state.
234
245
#
@@ -725,6 +736,9 @@ class IMAP < Protocol
725
736
# Returns the initial greeting the server, an UntaggedResponse.
726
737
attr_reader :greeting
727
738
739
+ # Implementation detail; only exposed for testing
740
+ attr_reader :cached_capabilities # :nodoc:
741
+
728
742
# Seconds to wait until a connection is opened.
729
743
# If the IMAP object cannot open a connection within this time,
730
744
# it raises a Net::OpenTimeout exception. The default value is 30 seconds.
@@ -803,12 +817,11 @@ def disconnected?
803
817
end
804
818
805
819
# Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
806
- # and returns an array of capabilities that the server supports. Each
807
- # capability is a string.
820
+ # and returns an array of capabilities that are supported by the server.
821
+ # Each capability is a string. Capabilities are case-insensitive .
808
822
#
809
- # See the {IANA IMAP4 capabilities
810
- # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list
811
- # of all standard capabilities, and their reference RFCs.
823
+ # **NOTE**: Prefer to use #capable? or #capabilities instead, to avoid
824
+ # sending unnecessary commands and correctly invalidate cached capabilities.
812
825
#
813
826
# >>>
814
827
# <em>*Note* that Net::IMAP does not currently modify its
@@ -817,49 +830,94 @@ def disconnected?
817
830
# a certain capability is supported by a server before
818
831
# using it.</em>
819
832
#
820
- # Capability requirements—other than +IMAP4rev1+—are listed in the
821
- # documentation for each command method.
833
+ # Related: #capable?, #capabilities, #enable
822
834
#
823
- # Related: #enable
835
+ def capability
836
+ synchronize do
837
+ send_command ( "CAPABILITY" )
838
+ return @responses . delete ( "CAPABILITY" ) [ -1 ]
839
+ end
840
+ end
841
+
842
+ # Returns whether the server supports a given capability. When available,
843
+ # cached capabilities are used without sending a new #capability command to
844
+ # the server.
845
+ #
846
+ # See the {IANA IMAP4 capabilities
847
+ # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list
848
+ # of all standard capabilities, and their reference RFCs.
849
+ #
850
+ # >>>
851
+ # *Note* that Net::IMAP does not <em>currently</em> modify its behaviour
852
+ # according to the capabilities of the server; it is up to the user of the
853
+ # class to ensure that a certain capability is supported by a server
854
+ # before using it.
855
+ #
856
+ # Capability requirements—other than +IMAP4rev1+—are listed in the
857
+ # documentation for each command method.
858
+ #
859
+ # Related: #capabilities, #capability, #enable
824
860
#
825
861
# ===== Basic IMAP4rev1 capabilities
826
862
#
827
- # All IMAP4rev1 servers must include +IMAP4rev1+ in their capabilities list.
828
- # All IMAP4rev1 servers must _implement_ the +STARTTLS+,
829
- # <tt>AUTH=PLAIN</tt>, and +LOGINDISABLED+ capabilities, and clients must
830
- # respect their presence or absence. See the capabilities requirements on
831
- # #starttls, #login, and # authenticate.
863
+ # IMAP4rev1 servers must include +IMAP4rev1+ in their capabilities list.
864
+ # IMAP4rev1 servers must _implement_ the +STARTTLS+, <tt>AUTH=PLAIN</tt> ,
865
+ # and +LOGINDISABLED+ capabilities, and clients must respect their presence
866
+ # or absence. See the capabilities requirements on #starttls, #login, and
867
+ # #authenticate.
832
868
#
833
869
# ===== Using IMAP4rev1 extensions
834
870
#
835
- # IMAP4rev1 servers must not activate incompatible behavior until an
836
- # explicit client action invokes a capability, e.g. sending a command or
837
- # command argument specific to that capability. Extensions with backward
838
- # compatible behavior, such as response codes or mailbox attributes, may
839
- # be sent at any time.
871
+ # IMAP4rev1 servers must not activate behavior that is incompatible with the
872
+ # base specification until an explicit client action invokes a capability,
873
+ # e.g. sending a command or command argument specific to that capability.
874
+ # Servers may send data with backward compatible behavior, such as response
875
+ # codes or mailbox attributes, at any time without client action .
840
876
#
841
877
# Invoking capabilities which are unknown to Net::IMAP may cause unexpected
842
878
# behavior and errors, for example ResponseParseError is raised when unknown
843
879
# response syntax is received. Invoking commands or command parameters that
844
880
# are unsupported by the server may raise NoResponseError, BadResponseError,
845
881
# or cause other unexpected behavior.
846
882
#
883
+ # Some capabilities must be explicitly activated using the #enable command.
884
+ # See #enable for more details.
885
+ #
847
886
# ===== Caching +CAPABILITY+ responses
848
887
#
849
- # Servers may send their capability list, unsolicited, using the
850
- # +CAPABILITY+ response code or an untagged +CAPABILITY+ response. These
851
- # responses can be retrieved and cached using #responses or
852
- # #add_response_handler .
888
+ # Servers may send their capability list unsolicited, using the +CAPABILITY+
889
+ # response code or an untagged +CAPABILITY+ response. Cached capabilities
890
+ # are discarded after #starttls, #login, or #authenticate. Both caching and
891
+ # cache invalidation are handled internally by Net::IMAP .
853
892
#
854
- # But cached capabilities _must_ be discarded after #starttls, #login, or
855
- # #authenticate. The OK TaggedResponse to #login and #authenticate may
856
- # include +CAPABILITY+ response code data, but the TaggedResponse for
857
- # #starttls is sent clear-text and cannot be trusted.
893
+ def capable? ( capability ) capabilities . include? capability . to_s . upcase end
894
+
895
+ # Returns the server capabilities.
858
896
#
859
- def capability
897
+ # Cached capabilities are used without sending a new #capability command to
898
+ # the server.
899
+ #
900
+ # In general, #capable? should be preferred because it doesn't rely on the
901
+ # representation of capabilities as an array of uppercase strings.
902
+ #
903
+ # Related: #capable?, #capability
904
+ def capabilities
905
+ @cached_capabilities ||= capability . freeze
906
+ end
907
+
908
+ # Returns whether capabilities have been cached. When true, #capable? and
909
+ # #capabilities don't require sending a #capability command to the server.
910
+ def capabilities_cached?
911
+ !!@cached_capabilities
912
+ end
913
+
914
+ # Clears capabilities that are currently cached by the Net::IMAP client.
915
+ # This forces a #capability command to be sent the next time that #capable?
916
+ # or #capabilities? are called.
917
+ def clear_cached_capabilities
860
918
synchronize do
861
- send_command ( "CAPABILITY" )
862
- return @responses . delete ( "CAPABILITY" ) [ - 1 ]
919
+ clear_responses ( "CAPABILITY" )
920
+ @cached_capabilities = nil
863
921
end
864
922
end
865
923
@@ -870,8 +928,7 @@ def capability
870
928
# Note that the user should first check if the server supports the ID
871
929
# capability. For example:
872
930
#
873
- # capabilities = imap.capability
874
- # if capabilities.include?("ID")
931
+ # if capable?(:ID)
875
932
# id = imap.id(
876
933
# name: "my IMAP client (ruby)",
877
934
# version: MyIMAP::VERSION,
@@ -940,15 +997,15 @@ def logout
940
997
# The server's capabilities must include +STARTTLS+.
941
998
#
942
999
# Server capabilities may change after #starttls, #login, and #authenticate.
943
- # Cached capabilities _must_ be invalidated after this method completes.
1000
+ # Cached capabilities are invalidated after this method completes.
944
1001
#
945
1002
# The TaggedResponse to #starttls is sent clear-text, so the server <em>must
946
1003
# *not*</em> send capabilities in the #starttls response and clients <em>must
947
1004
# not</em> use them if they are sent. Servers will generally send an
948
1005
# unsolicited untagged response immeditely _after_ #starttls completes.
949
1006
#
950
1007
def starttls ( options = { } , verify = true )
951
- send_command ( "STARTTLS" ) do |resp |
1008
+ ok_response = send_command ( "STARTTLS" ) do |resp |
952
1009
if resp . kind_of? ( TaggedResponse ) && resp . name == "OK"
953
1010
begin
954
1011
# for backward compatibility
@@ -959,6 +1016,8 @@ def starttls(options = {}, verify = true)
959
1016
start_tls_session ( options )
960
1017
end
961
1018
end
1019
+ clear_cached_capabilities
1020
+ ok_response
962
1021
end
963
1022
964
1023
# :call-seq:
@@ -1015,9 +1074,9 @@ def starttls(options = {}, verify = true)
1015
1074
# <tt>"AUTH=#{mechanism}"</tt> for that mechanism is a server capability.
1016
1075
#
1017
1076
# Server capabilities may change after #starttls, #login, and #authenticate.
1018
- # Cached capabilities _must_ be invalidated after this method completes.
1019
- # The TaggedResponse to #authenticate may include updated capabilities in
1020
- # its ResponseCode.
1077
+ # Cached capabilities are invalidated after this method completes. The
1078
+ # TaggedResponse to #authenticate may include updated capabilities in its
1079
+ # ResponseCode.
1021
1080
#
1022
1081
# ===== Example
1023
1082
# If the authenticators ignore unhandled keyword arguments, the same config
@@ -1030,33 +1089,35 @@ def starttls(options = {}, verify = true)
1030
1089
# password: proc { password ||= ui.prompt_for_password },
1031
1090
# oauth2_token: proc { accesstok ||= kms.fresh_access_token },
1032
1091
# }
1033
- # capa = imap.capability
1034
- # if capa.include? "AUTH=OAUTHBEARER"
1092
+ # if capable? "AUTH=OAUTHBEARER"
1035
1093
# imap.authenticate "OAUTHBEARER", **creds # authcid, oauth2_token
1036
- # elsif capa.include ? "AUTH=XOAUTH2"
1094
+ # elsif capable ? "AUTH=XOAUTH2"
1037
1095
# imap.authenticate "XOAUTH2", **creds # authcid, oauth2_token
1038
- # elsif capa.include ? "AUTH=SCRAM-SHA-256"
1096
+ # elsif capable ? "AUTH=SCRAM-SHA-256"
1039
1097
# imap.authenticate "SCRAM-SHA-256", **creds # authcid, password
1040
- # elsif capa.include ? "AUTH=PLAIN"
1098
+ # elsif capable ? "AUTH=PLAIN"
1041
1099
# imap.authenticate "PLAIN", **creds # authcid, password
1042
- # elsif capa.include ? "AUTH=DIGEST-MD5"
1100
+ # elsif capable ? "AUTH=DIGEST-MD5"
1043
1101
# imap.authenticate "DIGEST-MD5", **creds # authcid, password
1044
- # elsif capa.include ? "LOGINDISABLED"
1102
+ # elsif capable ? "LOGINDISABLED"
1045
1103
# raise "the server has disabled login"
1046
1104
# else
1047
1105
# imap.login username, password
1048
1106
# end
1049
1107
#
1050
1108
def authenticate ( mechanism , ...)
1051
1109
authenticator = self . class . authenticator ( mechanism , ...)
1052
- send_command ( "AUTHENTICATE" , mechanism ) do |resp |
1110
+ ok_response = send_command ( "AUTHENTICATE" , mechanism ) do |resp |
1053
1111
if resp . instance_of? ( ContinuationRequest )
1054
1112
data = authenticator . process ( resp . data . text . unpack ( "m" ) [ 0 ] )
1055
1113
s = [ data ] . pack ( "m0" )
1056
1114
send_string_data ( s )
1057
1115
put_string ( CRLF )
1058
1116
end
1059
1117
end
1118
+ clear_cached_capabilities
1119
+ # TODO: use capabilities from ok_response
1120
+ ok_response
1060
1121
end
1061
1122
1062
1123
# Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1081,7 +1142,10 @@ def authenticate(mechanism, ...)
1081
1142
# ResponseCode.
1082
1143
#
1083
1144
def login ( user , password )
1084
- send_command ( "LOGIN" , user , password )
1145
+ ok_response = send_command ( "LOGIN" , user , password )
1146
+ clear_cached_capabilities
1147
+ # TODO: use capabilities from ok_response
1148
+ ok_response
1085
1149
end
1086
1150
1087
1151
# Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
@@ -1261,8 +1325,7 @@ def list(refname, mailbox)
1261
1325
#
1262
1326
# ===== For example:
1263
1327
#
1264
- # capabilities = imap.capability
1265
- # if capabilities.include?("NAMESPACE")
1328
+ # if capable?("NAMESPACE")
1266
1329
# namespaces = imap.namespace
1267
1330
# if namespace = namespaces.personal.first
1268
1331
# prefix = namespace.prefix # e.g. "" or "INBOX."
@@ -2394,6 +2457,9 @@ def record_untagged_response_code(resp)
2394
2457
if resp . data . instance_of? ( ResponseText ) &&
2395
2458
( code = resp . data . code )
2396
2459
record_response ( code . name , code . data )
2460
+ if code . name . casecmp? ( "CAPABILITY" )
2461
+ @cached_capabilities ||= code . data . freeze
2462
+ end
2397
2463
end
2398
2464
end
2399
2465
0 commit comments