net-imap 0.4.20 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of net-imap might be problematic. Click here for more details.

@@ -5,9 +5,6 @@ module Net
5
5
  autoload :FetchData, "#{__dir__}/fetch_data"
6
6
  autoload :SearchResult, "#{__dir__}/search_result"
7
7
  autoload :SequenceSet, "#{__dir__}/sequence_set"
8
- autoload :UIDPlusData, "#{__dir__}/uidplus_data"
9
- autoload :AppendUIDData, "#{__dir__}/uidplus_data"
10
- autoload :CopyUIDData, "#{__dir__}/uidplus_data"
11
8
 
12
9
  # Net::IMAP::ContinuationRequest represents command continuation requests.
13
10
  #
@@ -327,6 +324,60 @@ module Net
327
324
  # code data can take.
328
325
  end
329
326
 
327
+ # Net::IMAP::UIDPlusData represents the ResponseCode#data that accompanies
328
+ # the +APPENDUID+ and +COPYUID+ response codes.
329
+ #
330
+ # See [[UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]].
331
+ #
332
+ # ==== Capability requirement
333
+ #
334
+ # The +UIDPLUS+ capability[rdoc-ref:Net::IMAP#capability] must be supported.
335
+ # A server that supports +UIDPLUS+ should send a UIDPlusData object inside
336
+ # every TaggedResponse returned by the append[rdoc-ref:Net::IMAP#append],
337
+ # copy[rdoc-ref:Net::IMAP#copy], move[rdoc-ref:Net::IMAP#move], {uid
338
+ # copy}[rdoc-ref:Net::IMAP#uid_copy], and {uid
339
+ # move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the destination
340
+ # mailbox reports +UIDNOTSTICKY+.
341
+ #
342
+ #--
343
+ # TODO: support MULTIAPPEND
344
+ #++
345
+ #
346
+ class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
347
+ ##
348
+ # method: uidvalidity
349
+ # :call-seq: uidvalidity -> nonzero uint32
350
+ #
351
+ # The UIDVALIDITY of the destination mailbox.
352
+
353
+ ##
354
+ # method: source_uids
355
+ # :call-seq: source_uids -> nil or an array of nonzero uint32
356
+ #
357
+ # The UIDs of the copied or moved messages.
358
+ #
359
+ # Note:: Returns +nil+ for Net::IMAP#append.
360
+
361
+ ##
362
+ # method: assigned_uids
363
+ # :call-seq: assigned_uids -> an array of nonzero uint32
364
+ #
365
+ # The newly assigned UIDs of the copied, moved, or appended messages.
366
+ #
367
+ # Note:: This always returns an array, even when it contains only one UID.
368
+
369
+ ##
370
+ # :call-seq: uid_mapping -> nil or a hash
371
+ #
372
+ # Returns a hash mapping each source UID to the newly assigned destination
373
+ # UID.
374
+ #
375
+ # Note:: Returns +nil+ for Net::IMAP#append.
376
+ def uid_mapping
377
+ source_uids&.zip(assigned_uids)&.to_h
378
+ end
379
+ end
380
+
330
381
  # Net::IMAP::MailboxList represents contents of the LIST response,
331
382
  # representing a single mailbox path.
332
383
  #
@@ -888,7 +939,8 @@ module Net
888
939
  # for something else?
889
940
  #++
890
941
  def media_subtype
891
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
942
+ warn("media_subtype is obsolete, use subtype instead.\n",
943
+ uplevel: 1, category: :deprecated)
892
944
  return subtype
893
945
  end
894
946
  end
@@ -933,7 +985,8 @@ module Net
933
985
  # generate a warning message to +stderr+, then return
934
986
  # the value of +subtype+.
935
987
  def media_subtype
936
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
988
+ warn("media_subtype is obsolete, use subtype instead.\n",
989
+ uplevel: 1, category: :deprecated)
937
990
  return subtype
938
991
  end
939
992
  end
@@ -989,77 +1042,6 @@ module Net
989
1042
  end
990
1043
  end
991
1044
 
992
- # BodyTypeAttachment is not used and will be removed in an upcoming release.
993
- #
994
- # === Bug Analysis
995
- #
996
- # \IMAP body structures are parenthesized lists and assign their fields
997
- # positionally, so missing fields change the interpretation of all
998
- # following fields. Additionally, different body types have a different
999
- # number of required fields, followed by optional "extension" fields.
1000
- #
1001
- # BodyTypeAttachment was previously returned when a "message/rfc822" part,
1002
- # which should be sent as <tt>body-type-msg</tt> with ten required fields,
1003
- # was actually sent as a <tt>body-type-basic</tt> with _seven_ required
1004
- # fields.
1005
- #
1006
- # basic => type, subtype, param, id, desc, enc, octets, md5=nil, dsp=nil, lang=nil, loc=nil, *ext
1007
- # msg => type, subtype, param, id, desc, enc, octets, envelope, body, lines, md5=nil, ...
1008
- #
1009
- # Normally, +envelope+ and +md5+ are incompatible, but Net::IMAP leniently
1010
- # allowed buggy servers to send +NIL+ for +envelope+. As a result, when a
1011
- # server sent a <tt>message/rfc822</tt> part with +NIL+ for +md5+ and a
1012
- # non-<tt>NIL</tt> +dsp+, Net::IMAP misinterpreted the
1013
- # <tt>Content-Disposition</tt> as if it were a strange body type. In all
1014
- # reported cases, the <tt>Content-Disposition</tt> was "attachment", so
1015
- # BodyTypeAttachment was created as the workaround.
1016
- #
1017
- # === Current behavior
1018
- #
1019
- # When interpreted strictly, +envelope+ and +md5+ are incompatible. So the
1020
- # current parsing algorithm peeks ahead after it has received the seventh
1021
- # body field. If the next token is not the start of an +envelope+, we assume
1022
- # the server has incorrectly sent us a <tt>body-type-basic</tt> and return
1023
- # BodyTypeBasic. As a result, what was previously BodyTypeMessage#body =>
1024
- # BodyTypeAttachment is now BodyTypeBasic#disposition => ContentDisposition.
1025
- #
1026
- class BodyTypeAttachment < Struct.new(:dsp_type, :_unused_, :param)
1027
- # *invalid for BodyTypeAttachment*
1028
- def media_type
1029
- warn(<<~WARN, uplevel: 1)
1030
- BodyTypeAttachment#media_type is obsolete. Use dsp_type instead.
1031
- WARN
1032
- dsp_type
1033
- end
1034
-
1035
- # *invalid for BodyTypeAttachment*
1036
- def subtype
1037
- warn("BodyTypeAttachment#subtype is obsolete.\n", uplevel: 1)
1038
- nil
1039
- end
1040
-
1041
- ##
1042
- # method: dsp_type
1043
- # :call-seq: dsp_type -> string
1044
- #
1045
- # Returns the content disposition type, as defined by
1046
- # [DISPOSITION[https://tools.ietf.org/html/rfc2183]].
1047
-
1048
- ##
1049
- # method: param
1050
- # :call-seq: param -> hash
1051
- #
1052
- # Returns a hash representing parameters of the Content-Disposition
1053
- # field, as defined by [DISPOSITION[https://tools.ietf.org/html/rfc2183]].
1054
-
1055
- ##
1056
- def multipart?
1057
- return false
1058
- end
1059
- end
1060
-
1061
- deprecate_constant :BodyTypeAttachment
1062
-
1063
1045
  # Net::IMAP::BodyTypeMultipart represents body structures of messages and
1064
1046
  # message parts, when <tt>Content-Type</tt> is <tt>multipart/*</tt>.
1065
1047
  class BodyTypeMultipart < Struct.new(:media_type, :subtype,
@@ -1131,29 +1113,11 @@ module Net
1131
1113
  # generate a warning message to +stderr+, then return
1132
1114
  # the value of +subtype+.
1133
1115
  def media_subtype
1134
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
1116
+ warn("media_subtype is obsolete, use subtype instead.\n",
1117
+ uplevel: 1, category: :deprecated)
1135
1118
  return subtype
1136
1119
  end
1137
1120
  end
1138
1121
 
1139
- # === Obsolete
1140
- # BodyTypeExtension is not used and will be removed in an upcoming release.
1141
- #
1142
- # >>>
1143
- # BodyTypeExtension was (incorrectly) used for <tt>message/*</tt> parts
1144
- # (besides <tt>message/rfc822</tt>, which correctly uses BodyTypeMessage).
1145
- #
1146
- # Net::IMAP now (correctly) parses all message types (other than
1147
- # <tt>message/rfc822</tt> or <tt>message/global</tt>) as BodyTypeBasic.
1148
- class BodyTypeExtension < Struct.new(:media_type, :subtype,
1149
- :params, :content_id,
1150
- :description, :encoding, :size)
1151
- def multipart?
1152
- return false
1153
- end
1154
- end
1155
-
1156
- deprecate_constant :BodyTypeExtension
1157
-
1158
1122
  end
1159
1123
  end
@@ -13,17 +13,13 @@ module Net
13
13
 
14
14
  attr_reader :config
15
15
 
16
- # Creates a new ResponseParser.
17
- #
18
- # When +config+ is frozen or global, the parser #config inherits from it.
19
- # Otherwise, +config+ will be used directly.
16
+ # :call-seq: Net::IMAP::ResponseParser.new -> Net::IMAP::ResponseParser
20
17
  def initialize(config: Config.global)
21
18
  @str = nil
22
19
  @pos = nil
23
20
  @lex_state = nil
24
21
  @token = nil
25
22
  @config = Config[config]
26
- @config = @config.new if @config == Config.global || @config.frozen?
27
23
  end
28
24
 
29
25
  # :call-seq:
@@ -1321,31 +1317,19 @@ module Net
1321
1317
  # header-fld-name = astring
1322
1318
  #
1323
1319
  # NOTE: Previously, Net::IMAP recreated the raw original source string.
1324
- # Now, it grabs the raw encoded value using @str and @pos. A future
1325
- # version may simply return the decoded astring value. Although that is
1326
- # technically incompatible, it should almost never make a difference: all
1327
- # standard header field names are valid atoms:
1320
+ # Now, it returns the decoded astring value. Although this is technically
1321
+ # incompatible, it should almost never make a difference: all standard
1322
+ # header field names are valid atoms:
1328
1323
  #
1329
1324
  # https://www.iana.org/assignments/message-headers/message-headers.xhtml
1330
1325
  #
1331
- # Although RFC3501 allows any astring, RFC5322-valid header names are one
1332
- # or more of the printable US-ASCII characters, except SP and colon. So
1333
- # empty string isn't valid, and literals aren't needed and should not be
1334
- # used. This is explicitly unchanged by [I18N-HDRS] (RFC6532).
1335
- #
1336
- # RFC5233:
1326
+ # See also RFC5233:
1337
1327
  # optional-field = field-name ":" unstructured CRLF
1338
1328
  # field-name = 1*ftext
1339
1329
  # ftext = %d33-57 / ; Printable US-ASCII
1340
1330
  # %d59-126 ; characters not including
1341
1331
  # ; ":".
1342
- def header_fld_name
1343
- assert_no_lookahead
1344
- start = @pos
1345
- astring
1346
- end_pos = @token ? @pos - 1 : @pos
1347
- @str[start...end_pos]
1348
- end
1332
+ alias header_fld_name astring
1349
1333
 
1350
1334
  # mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list /
1351
1335
  # "LSUB" SP mailbox-list / "SEARCH" *(SP nz-number) /
@@ -1867,10 +1851,11 @@ module Net
1867
1851
  #
1868
1852
  # n.b, uniqueid ⊂ uid-set. To avoid inconsistent return types, we always
1869
1853
  # match uid_set even if that returns a single-member array.
1854
+ #
1870
1855
  def resp_code_apnd__data
1871
1856
  validity = number; SP!
1872
1857
  dst_uids = uid_set # uniqueid ⊂ uid-set
1873
- AppendUID(validity, dst_uids)
1858
+ UIDPlusData.new(validity, nil, dst_uids)
1874
1859
  end
1875
1860
 
1876
1861
  # already matched: "COPYUID"
@@ -1880,25 +1865,7 @@ module Net
1880
1865
  validity = number; SP!
1881
1866
  src_uids = uid_set; SP!
1882
1867
  dst_uids = uid_set
1883
- CopyUID(validity, src_uids, dst_uids)
1884
- end
1885
-
1886
- def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
1887
- def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end
1888
-
1889
- # TODO: remove this code in the v0.6.0 release
1890
- def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids)
1891
- return unless config.parser_use_deprecated_uidplus_data
1892
- compact_uid_sets = [src_uids, dst_uids].compact
1893
- count = compact_uid_sets.map { _1.count_with_duplicates }.max
1894
- max = config.parser_max_deprecated_uidplus_data_size
1895
- if count <= max
1896
- src_uids &&= src_uids.each_ordered_number.to_a
1897
- dst_uids = dst_uids.each_ordered_number.to_a
1898
- UIDPlusData.new(validity, src_uids, dst_uids)
1899
- elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size
1900
- parse_error("uid-set is too large: %d > %d", count, max)
1901
- end
1868
+ UIDPlusData.new(validity, src_uids, dst_uids)
1902
1869
  end
1903
1870
 
1904
1871
  ADDRESS_REGEXP = /\G
@@ -2024,9 +1991,15 @@ module Net
2024
1991
  # uniqueid = nz-number
2025
1992
  # ; Strictly ascending
2026
1993
  def uid_set
2027
- set = sequence_set
2028
- parse_error("uid-set cannot contain '*'") if set.include_star?
2029
- set
1994
+ token = match(T_NUMBER, T_ATOM)
1995
+ case token.symbol
1996
+ when T_NUMBER then [Integer(token.value)]
1997
+ when T_ATOM
1998
+ token.value.split(",").flat_map {|range|
1999
+ range = range.split(":").map {|uniqueid| Integer(uniqueid) }
2000
+ range.size == 1 ? range : Range.new(range.min, range.max).to_a
2001
+ }
2002
+ end
2030
2003
  end
2031
2004
 
2032
2005
  def nil_atom
@@ -4,44 +4,76 @@ module Net
4
4
  class IMAP
5
5
  module SASL
6
6
 
7
- # This API is *experimental*, and may change.
7
+ # AuthenticationExchange is used internally by Net::IMAP#authenticate.
8
+ # But the API is still *experimental*, and may change.
8
9
  #
9
10
  # TODO: catch exceptions in #process and send #cancel_response.
10
11
  # TODO: raise an error if the command succeeds after being canceled.
11
12
  # TODO: use with more clients, to verify the API can accommodate them.
13
+ # TODO: pass ClientAdapter#service to SASL.authenticator
12
14
  #
13
- # Create an AuthenticationExchange from a client adapter and a mechanism
14
- # authenticator:
15
- # def authenticate(mechanism, ...)
16
- # authenticator = SASL.authenticator(mechanism, ...)
17
- # SASL::AuthenticationExchange.new(
18
- # sasl_adapter, mechanism, authenticator
19
- # ).authenticate
20
- # end
21
- #
22
- # private
15
+ # An AuthenticationExchange represents a single attempt to authenticate
16
+ # a SASL client to a SASL server. It is created from a client adapter, a
17
+ # mechanism name, and a mechanism authenticator. When #authenticate is
18
+ # called, it will send the appropriate authenticate command to the server,
19
+ # returning the client response on success and raising an exception on
20
+ # failure.
23
21
  #
24
- # def sasl_adapter = MyClientAdapter.new(self, &method(:send_command))
22
+ # In most cases, the client will not need to use
23
+ # SASL::AuthenticationExchange directly at all. Instead, use
24
+ # SASL::ClientAdapter#authenticate. If customizations are needed, the
25
+ # custom client adapter is probably the best place for that code.
25
26
  #
26
- # Or delegate creation of the authenticator to ::build:
27
27
  # def authenticate(...)
28
- # SASL::AuthenticationExchange.build(sasl_adapter, ...)
29
- # .authenticate
28
+ # MyClient::SASLAdapter.new(self).authenticate(...)
30
29
  # end
31
30
  #
32
- # As a convenience, ::authenticate combines ::build and #authenticate:
31
+ # SASL::ClientAdapter#authenticate delegates to ::authenticate, like so:
32
+ #
33
33
  # def authenticate(...)
34
+ # sasl_adapter = MyClient::SASLAdapter.new(self)
34
35
  # SASL::AuthenticationExchange.authenticate(sasl_adapter, ...)
35
36
  # end
36
37
  #
37
- # Likewise, ClientAdapter#authenticate delegates to #authenticate:
38
- # def authenticate(...) = sasl_adapter.authenticate(...)
38
+ # ::authenticate simply delegates to ::build and #authenticate, like so:
39
+ #
40
+ # def authenticate(...)
41
+ # sasl_adapter = MyClient::SASLAdapter.new(self)
42
+ # SASL::AuthenticationExchange
43
+ # .build(sasl_adapter, ...)
44
+ # .authenticate
45
+ # end
46
+ #
47
+ # And ::build delegates to SASL.authenticator and ::new, like so:
48
+ #
49
+ # def authenticate(mechanism, ...)
50
+ # sasl_adapter = MyClient::SASLAdapter.new(self)
51
+ # authenticator = SASL.authenticator(mechanism, ...)
52
+ # SASL::AuthenticationExchange
53
+ # .new(sasl_adapter, mechanism, authenticator)
54
+ # .authenticate
55
+ # end
39
56
  #
40
57
  class AuthenticationExchange
41
58
  # Convenience method for <tt>build(...).authenticate</tt>
59
+ #
60
+ # See also: SASL::ClientAdapter#authenticate
42
61
  def self.authenticate(...) build(...).authenticate end
43
62
 
44
- # Use +registry+ to override the global Authenticators registry.
63
+ # Convenience method to combine the creation of a new authenticator and
64
+ # a new Authentication exchange.
65
+ #
66
+ # +client+ must be an instance of SASL::ClientAdapter.
67
+ #
68
+ # +mechanism+ must be a SASL mechanism name, as a string or symbol.
69
+ #
70
+ # +sasl_ir+ allows or disallows sending an "initial response", depending
71
+ # also on whether the server capabilities, mechanism authenticator, and
72
+ # client adapter all support it. Defaults to +true+.
73
+ #
74
+ # +mechanism+, +args+, +kwargs+, and +block+ are all forwarded to
75
+ # SASL.authenticator. Use the +registry+ kwarg to override the global
76
+ # SASL::Authenticators registry.
45
77
  def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block)
46
78
  authenticator = SASL.authenticator(mechanism, *args, **kwargs, &block)
47
79
  new(client, mechanism, authenticator, sasl_ir: sasl_ir)
@@ -51,7 +83,7 @@ module Net
51
83
 
52
84
  def initialize(client, mechanism, authenticator, sasl_ir: true)
53
85
  @client = client
54
- @mechanism = -mechanism.to_s.upcase.tr(?_, ?-)
86
+ @mechanism = Authenticators.normalize_name(mechanism)
55
87
  @authenticator = authenticator
56
88
  @sasl_ir = sasl_ir
57
89
  @processed = false
@@ -21,6 +21,10 @@ module Net::IMAP::SASL
21
21
  # ScramSHA1Authenticator for examples.
22
22
  class Authenticators
23
23
 
24
+ # Normalize the mechanism name as an uppercase string, with underscores
25
+ # converted to dashes.
26
+ def self.normalize_name(mechanism) -(mechanism.to_s.upcase.tr(?_, ?-)) end
27
+
24
28
  # Create a new Authenticators registry.
25
29
  #
26
30
  # This class is usually not instantiated directly. Use SASL.authenticators
@@ -65,7 +69,6 @@ module Net::IMAP::SASL
65
69
  # lazily loaded from <tt>Net::IMAP::SASL::#{name}Authenticator</tt> (case is
66
70
  # preserved and non-alphanumeric characters are removed..
67
71
  def add_authenticator(name, authenticator = nil)
68
- key = -name.to_s.upcase.tr(?_, ?-)
69
72
  authenticator ||= begin
70
73
  class_name = "#{name.gsub(/[^a-zA-Z0-9]/, "")}Authenticator".to_sym
71
74
  auth_class = nil
@@ -74,17 +77,18 @@ module Net::IMAP::SASL
74
77
  auth_class.new(*creds, **props, &block)
75
78
  }
76
79
  end
80
+ key = Authenticators.normalize_name(name)
77
81
  @authenticators[key] = authenticator
78
82
  end
79
83
 
80
84
  # Removes the authenticator registered for +name+
81
85
  def remove_authenticator(name)
82
- key = -name.to_s.upcase.tr(?_, ?-)
86
+ key = Authenticators.normalize_name(name)
83
87
  @authenticators.delete(key)
84
88
  end
85
89
 
86
90
  def mechanism?(name)
87
- key = -name.to_s.upcase.tr(?_, ?-)
91
+ key = Authenticators.normalize_name(name)
88
92
  @authenticators.key?(key)
89
93
  end
90
94
 
@@ -105,7 +109,7 @@ module Net::IMAP::SASL
105
109
  # only. Protocol client users should see refer to their client's
106
110
  # documentation, e.g. Net::IMAP#authenticate.
107
111
  def authenticator(mechanism, ...)
108
- key = -mechanism.to_s.upcase.tr(?_, ?-)
112
+ key = Authenticators.normalize_name(mechanism)
109
113
  auth = @authenticators.fetch(key) do
110
114
  raise ArgumentError, 'unknown auth type - "%s"' % key
111
115
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+
3
5
  module Net
4
6
  class IMAP
5
7
  module SASL
@@ -8,42 +10,76 @@ module Net
8
10
  #
9
11
  # TODO: use with more clients, to verify the API can accommodate them.
10
12
  #
11
- # An abstract base class for implementing a SASL authentication exchange.
12
- # Different clients will each have their own adapter subclass, overridden
13
- # to match their needs.
13
+ # Represents the client to a SASL::AuthenticationExchange. By default,
14
+ # most methods simply delegate to #client. Clients should subclass
15
+ # SASL::ClientAdapter and override methods as needed to match the
16
+ # semantics of this API to their API.
14
17
  #
15
- # Although the default implementations _may_ be sufficient, subclasses
16
- # will probably need to override some methods. Additionally, subclasses
17
- # may need to include a protocol adapter mixin, if the default
18
+ # Subclasses should also include a protocol adapter mixin when the default
18
19
  # ProtocolAdapters::Generic isn't sufficient.
20
+ #
21
+ # === Protocol Requirements
22
+ #
23
+ # {RFC4422 §4}[https://www.rfc-editor.org/rfc/rfc4422.html#section-4]
24
+ # lists requirements for protocol specifications to offer SASL. Where
25
+ # possible, ClientAdapter delegates the handling of these requirements to
26
+ # SASL::ProtocolAdapters.
19
27
  class ClientAdapter
28
+ extend Forwardable
29
+
20
30
  include ProtocolAdapters::Generic
21
31
 
22
- attr_reader :client, :command_proc
32
+ # The client that handles communication with the protocol server.
33
+ #
34
+ # Most ClientAdapter methods are simply delegated to #client by default.
35
+ attr_reader :client
23
36
 
24
37
  # +command_proc+ can used to avoid exposing private methods on #client.
25
- # It should run a command with the arguments sent to it, yield each
26
- # continuation payload, respond to the server with the result of each
27
- # yield, and return the result. Non-successful results *MUST* raise an
28
- # exception. Exceptions in the block *MUST* cause the command to fail.
38
+ # It's value is set by the block that is passed to ::new, and it is used
39
+ # by the default implementation of #run_command. Subclasses that
40
+ # override #run_command may use #command_proc for any other purpose they
41
+ # find useful.
29
42
  #
30
- # Subclasses that override #run_command may use #command_proc for
31
- # other purposes.
43
+ # In the default implementation of #run_command, command_proc is called
44
+ # with the protocols authenticate +command+ name, the +mechanism+ name,
45
+ # an _optional_ +initial_response+ argument, and a +continuations+
46
+ # block. command_proc must run the protocol command with the arguments
47
+ # sent to it, _yield_ the payload of each continuation, respond to the
48
+ # continuation with the result of each _yield_, and _return_ the
49
+ # command's successful result. Non-successful results *MUST* raise
50
+ # an exception.
51
+ attr_reader :command_proc
52
+
53
+ # By default, this simply sets the #client and #command_proc attributes.
54
+ # Subclasses may override it, for example: to set the appropriate
55
+ # command_proc automatically.
32
56
  def initialize(client, &command_proc)
33
57
  @client, @command_proc = client, command_proc
34
58
  end
35
59
 
36
- # Delegates to AuthenticationExchange.authenticate.
60
+ # Attempt to authenticate #client to the server.
61
+ #
62
+ # By default, this simply delegates to
63
+ # AuthenticationExchange.authenticate.
37
64
  def authenticate(...) AuthenticationExchange.authenticate(self, ...) end
38
65
 
39
- # Do the protocol and server both support an initial response?
40
- def sasl_ir_capable?; client.sasl_ir_capable? end
66
+ ##
67
+ # method: sasl_ir_capable?
68
+ # Do the protocol, server, and client all support an initial response?
69
+ def_delegator :client, :sasl_ir_capable?
41
70
 
42
- # Does the server advertise support for the mechanism?
43
- def auth_capable?(mechanism); client.auth_capable?(mechanism) end
71
+ ##
72
+ # method: auth_capable?
73
+ # call-seq: auth_capable?(mechanism)
74
+ #
75
+ # Does the server advertise support for the +mechanism+?
76
+ def_delegator :client, :auth_capable?
44
77
 
45
- # Runs the authenticate command with +mechanism+ and +initial_response+.
46
- # When +initial_response+ is nil, an initial response must NOT be sent.
78
+ # Calls command_proc with +command_name+ (see
79
+ # SASL::ProtocolAdapters::Generic#command_name),
80
+ # +mechanism+, +initial_response+, and a +continuations_handler+ block.
81
+ # The +initial_response+ is optional; when it's nil, it won't be sent to
82
+ # command_proc.
47
83
  #
48
84
  # Yields each continuation payload, responds to the server with the
49
85
  # result of each yield, and returns the result. Non-successful results
@@ -51,21 +87,36 @@ module Net
51
87
  # command to fail.
52
88
  #
53
89
  # Subclasses that override this may use #command_proc differently.
54
- def run_command(mechanism, initial_response = nil, &block)
90
+ def run_command(mechanism, initial_response = nil, &continuations_handler)
55
91
  command_proc or raise Error, "initialize with block or override"
56
92
  args = [command_name, mechanism, initial_response].compact
57
- command_proc.call(*args, &block)
93
+ command_proc.call(*args, &continuations_handler)
58
94
  end
59
95
 
96
+ ##
97
+ # method: host
98
+ # The hostname to which the client connected.
99
+ def_delegator :client, :host
100
+
101
+ ##
102
+ # method: port
103
+ # The destination port to which the client connected.
104
+ def_delegator :client, :port
105
+
60
106
  # Returns an array of server responses errors raised by run_command.
61
107
  # Exceptions in this array won't drop the connection.
62
108
  def response_errors; [] end
63
109
 
64
- # Drop the connection gracefully.
65
- def drop_connection; client.drop_connection end
110
+ ##
111
+ # method: drop_connection
112
+ # Drop the connection gracefully, sending a "LOGOUT" command as needed.
113
+ def_delegator :client, :drop_connection
114
+
115
+ ##
116
+ # method: drop_connection!
117
+ # Drop the connection abruptly, closing the socket without logging out.
118
+ def_delegator :client, :drop_connection!
66
119
 
67
- # Drop the connection abruptly.
68
- def drop_connection!; client.drop_connection! end
69
120
  end
70
121
  end
71
122
  end
@@ -20,7 +20,7 @@ class Net::IMAP::SASL::CramMD5Authenticator
20
20
  warn_deprecation: true,
21
21
  **)
22
22
  if warn_deprecation
23
- warn "WARNING: CRAM-MD5 mechanism is deprecated." # TODO: recommend SCRAM
23
+ warn "WARNING: CRAM-MD5 mechanism is deprecated.", category: :deprecated
24
24
  end
25
25
  require "digest/md5"
26
26
  @user = authcid || username || user