net-imap 0.4.16 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb20811050c485f7fcde01bb24dbe455026b45cc463efe39353a3f186312a5d5
4
- data.tar.gz: 1290933be2c606cb299c75acc7ba59aa3ecdbcad245e776981eb2eb49d3289f3
3
+ metadata.gz: 2c89d97e842bce303df112c3fbc0f048f20a2ff1dbf3b85bb2314ea0d2e207c5
4
+ data.tar.gz: 11d6ec196e1e768f61a17b0ce7f385a11eb03e3e92af8ab327ba9a90699ee940
5
5
  SHA512:
6
- metadata.gz: 857dfd2863abedf53c6adf874a8d6883ea93de91cb01bd0c3ec60ca2279f86e1ae41ddedaccd1329490dddb6dabdce59cddf22699c0792085a5fba2a76184647
7
- data.tar.gz: 0766bbf7514aa1c293b18575c47bf02f5eff1c62b69c8cda432aba35d72572607d60588e45ab78a25e5df7f8d7df2fe5c90d965f580c8178171554151ab0c4a0
6
+ metadata.gz: ca70e20c3c70f4ca57822f70936546adb3129dd7c0771ef2d054b0356ed4f6a994b7453803cc06bd25ebb37dc0ed10f60b5541bc56749dee38a6126264344599
7
+ data.tar.gz: 2921a79dbdab7bd0476d883c18db50150388d587c711270faac99ad4505574a23e6532fe61a4fb48f51c8bddea6641d96f81d9dcbe245b4d4f4542fcc7418566
data/Gemfile CHANGED
@@ -13,4 +13,10 @@ gem "rdoc"
13
13
  gem "test-unit"
14
14
  gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core"
15
15
 
16
- gem "benchmark-driver"
16
+ gem "benchmark-driver", require: false
17
+
18
+ group :test do
19
+ gem "simplecov", require: false
20
+ gem "simplecov-html", require: false
21
+ gem "simplecov-json", require: false
22
+ end
@@ -9,7 +9,7 @@ module Net::IMAP::Authenticators
9
9
  "%s.%s is deprecated. Use %s.%s instead." % [
10
10
  Net::IMAP, __method__, Net::IMAP::SASL, __method__
11
11
  ],
12
- uplevel: 1
12
+ uplevel: 1, category: :deprecated
13
13
  )
14
14
  Net::IMAP::SASL.add_authenticator(...)
15
15
  end
@@ -20,7 +20,7 @@ module Net::IMAP::Authenticators
20
20
  "%s.%s is deprecated. Use %s.%s instead." % [
21
21
  Net::IMAP, __method__, Net::IMAP::SASL, __method__
22
22
  ],
23
- uplevel: 1
23
+ uplevel: 1, category: :deprecated
24
24
  )
25
25
  Net::IMAP::SASL.authenticator(...)
26
26
  end
@@ -179,6 +179,7 @@ module Net
179
179
  end
180
180
  end
181
181
 
182
+ # *DEPRECATED*. Replaced by SequenceSet.
182
183
  class MessageSet # :nodoc:
183
184
  def send_data(imap, tag)
184
185
  imap.__send__(:put_string, format_internal(@data))
@@ -192,6 +193,16 @@ module Net
192
193
 
193
194
  def initialize(data)
194
195
  @data = data
196
+ warn("DEPRECATED: #{MessageSet} should be replaced with #{SequenceSet}.",
197
+ uplevel: 1, category: :deprecated)
198
+ begin
199
+ # to ensure the input works with SequenceSet, too
200
+ SequenceSet.new(data)
201
+ rescue
202
+ warn "MessageSet input is incompatible with SequenceSet: [%s] %s" % [
203
+ $!.class, $!.message
204
+ ]
205
+ end
195
206
  end
196
207
 
197
208
  def format_internal(data)
@@ -7,10 +7,10 @@ require_relative "config/attr_type_coercion"
7
7
  module Net
8
8
  class IMAP
9
9
 
10
- # Net::IMAP::Config stores configuration options for Net::IMAP clients.
11
- # The global configuration can be seen at either Net::IMAP.config or
12
- # Net::IMAP::Config.global, and the client-specific configuration can be
13
- # seen at Net::IMAP#config.
10
+ # Net::IMAP::Config <em>(available since +v0.4.13+)</em> stores
11
+ # configuration options for Net::IMAP clients. The global configuration can
12
+ # be seen at either Net::IMAP.config or Net::IMAP::Config.global, and the
13
+ # client-specific configuration can be seen at Net::IMAP#config.
14
14
  #
15
15
  # When creating a new client, all unhandled keyword arguments to
16
16
  # Net::IMAP.new are delegated to Config.new. Every client has its own
@@ -128,7 +128,7 @@ module Net
128
128
  # The global config object. Also available from Net::IMAP.config.
129
129
  def self.global; @global if defined?(@global) end
130
130
 
131
- # A hash of hard-coded configurations, indexed by version number.
131
+ # A hash of hard-coded configurations, indexed by version number or name.
132
132
  def self.version_defaults; @version_defaults end
133
133
  @version_defaults = {}
134
134
 
@@ -172,9 +172,16 @@ module Net
172
172
  include AttrInheritance
173
173
  include AttrTypeCoercion
174
174
 
175
- # The debug mode (boolean)
175
+ # The debug mode (boolean). The default value is +false+.
176
176
  #
177
- # The default value is +false+.
177
+ # When #debug is +true+:
178
+ # * Data sent to and received from the server will be logged.
179
+ # * ResponseParser will print warnings with extra detail for parse
180
+ # errors. _This may include recoverable errors._
181
+ # * ResponseParser makes extra assertions.
182
+ #
183
+ # *NOTE:* Versioned default configs inherit #debug from Config.global, and
184
+ # #load_defaults will not override #debug.
178
185
  attr_accessor :debug, type: :boolean
179
186
 
180
187
  # method: debug?
@@ -200,34 +207,84 @@ module Net
200
207
  # The default value is +5+ seconds.
201
208
  attr_accessor :idle_response_timeout, type: Integer
202
209
 
203
- # :markup: markdown
204
- #
205
210
  # Whether to use the +SASL-IR+ extension when the server and \SASL
206
- # mechanism both support it.
211
+ # mechanism both support it. Can be overridden by the +sasl_ir+ keyword
212
+ # parameter to Net::IMAP#authenticate.
213
+ #
214
+ # <em>(Support for +SASL-IR+ was added in +v0.4.0+.)</em>
215
+ #
216
+ # ==== Valid options
207
217
  #
208
- # See Net::IMAP#authenticate.
218
+ # [+false+ <em>(original behavior, before support was added)</em>]
219
+ # Do not use +SASL-IR+, even when it is supported by the server and the
220
+ # mechanism.
209
221
  #
210
- # | Starting with version | The default value is |
211
- # |-----------------------|------------------------------------------|
212
- # | _original_ | +false+ <em>(extension unsupported)</em> |
213
- # | v0.4 | +true+ <em>(support added)</em> |
222
+ # [+true+ <em>(default since +v0.4+)</em>]
223
+ # Use +SASL-IR+ when it is supported by the server and the mechanism.
214
224
  attr_accessor :sasl_ir, type: :boolean
215
225
 
216
- # :markup: markdown
226
+ # Controls the behavior of Net::IMAP#login when the +LOGINDISABLED+
227
+ # capability is present. When enforced, Net::IMAP will raise a
228
+ # LoginDisabledError when that capability is present.
229
+ #
230
+ # <em>(Support for +LOGINDISABLED+ was added in +v0.5.0+.)</em>
231
+ #
232
+ # ==== Valid options
233
+ #
234
+ # [+false+ <em>(original behavior, before support was added)</em>]
235
+ # Send the +LOGIN+ command without checking for +LOGINDISABLED+.
236
+ #
237
+ # [+:when_capabilities_cached+]
238
+ # Enforce the requirement when Net::IMAP#capabilities_cached? is true,
239
+ # but do not send a +CAPABILITY+ command to discover the capabilities.
240
+ #
241
+ # [+true+ <em>(default since +v0.5+)</em>]
242
+ # Only send the +LOGIN+ command if the +LOGINDISABLED+ capability is not
243
+ # present. When capabilities are unknown, Net::IMAP will automatically
244
+ # send a +CAPABILITY+ command first before sending +LOGIN+.
245
+ #
246
+ attr_accessor :enforce_logindisabled, type: [
247
+ false, :when_capabilities_cached, true
248
+ ]
249
+
250
+ # Controls the behavior of Net::IMAP#responses when called without any
251
+ # arguments (+type+ or +block+).
252
+ #
253
+ # ==== Valid options
217
254
  #
218
- # Controls the behavior of Net::IMAP#responses when called without a
219
- # block. Valid options are `:warn`, `:raise`, or
220
- # `:silence_deprecation_warning`.
255
+ # [+:silence_deprecation_warning+ <em>(original behavior)</em>]
256
+ # Returns the mutable responses hash (without any warnings).
257
+ # <em>This is not thread-safe.</em>
221
258
  #
222
- # | Starting with version | The default value is |
223
- # |-------------------------|--------------------------------|
224
- # | v0.4.13 | +:silence_deprecation_warning+ |
225
- # | v0.5 <em>(planned)</em> | +:warn+ |
226
- # | _eventually_ | +:raise+ |
259
+ # [+:warn+ <em>(default since +v0.5+)</em>]
260
+ # Prints a warning and returns the mutable responses hash.
261
+ # <em>This is not thread-safe.</em>
262
+ #
263
+ # [+:frozen_dup+ <em>(planned default for +v0.6+)</em>]
264
+ # Returns a frozen copy of the unhandled responses hash, with frozen
265
+ # array values.
266
+ #
267
+ # Note that calling IMAP#responses with a +type+ and without a block is
268
+ # not configurable and always behaves like +:frozen_dup+.
269
+ #
270
+ # <em>(+:frozen_dup+ config option was added in +v0.4.17+)</em>
271
+ #
272
+ # [+:raise+]
273
+ # Raise an ArgumentError with the deprecation warning.
274
+ #
275
+ # Note: #responses_without_args is an alias for #responses_without_block.
227
276
  attr_accessor :responses_without_block, type: [
228
- :silence_deprecation_warning, :warn, :raise,
277
+ :silence_deprecation_warning, :warn, :frozen_dup, :raise,
229
278
  ]
230
279
 
280
+ alias responses_without_args responses_without_block # :nodoc:
281
+ alias responses_without_args= responses_without_block= # :nodoc:
282
+
283
+ ##
284
+ # :attr_accessor: responses_without_args
285
+ #
286
+ # Alias for responses_without_block
287
+
231
288
  # Creates a new config object and initialize its attribute with +attrs+.
232
289
  #
233
290
  # If +parent+ is not given, the global config is used by default.
@@ -306,32 +363,36 @@ module Net
306
363
  open_timeout: 30,
307
364
  idle_response_timeout: 5,
308
365
  sasl_ir: true,
309
- responses_without_block: :silence_deprecation_warning,
366
+ enforce_logindisabled: true,
367
+ responses_without_block: :warn,
310
368
  ).freeze
311
369
 
312
370
  @global = default.new
313
371
 
314
- version_defaults[0.4] = Config[default.send(:defaults_hash)]
372
+ version_defaults[:default] = Config[default.send(:defaults_hash)]
373
+ version_defaults[:current] = Config[:default]
315
374
 
316
- version_defaults[0] = Config[0.4].dup.update(
375
+ version_defaults[0] = Config[:current].dup.update(
317
376
  sasl_ir: false,
377
+ responses_without_block: :silence_deprecation_warning,
378
+ enforce_logindisabled: false,
318
379
  ).freeze
319
380
  version_defaults[0.0] = Config[0]
320
381
  version_defaults[0.1] = Config[0]
321
382
  version_defaults[0.2] = Config[0]
322
383
  version_defaults[0.3] = Config[0]
323
384
 
324
- version_defaults[0.5] = Config[0.4].dup.update(
325
- responses_without_block: :warn,
385
+ version_defaults[0.4] = Config[0.3].dup.update(
386
+ sasl_ir: true,
326
387
  ).freeze
327
388
 
328
- version_defaults[:default] = Config[0.4]
329
- version_defaults[:current] = Config[0.4]
330
- version_defaults[:next] = Config[0.5]
389
+ version_defaults[0.5] = Config[:current]
331
390
 
332
- version_defaults[:future] = Config[0.5].dup.update(
333
- responses_without_block: :raise,
391
+ version_defaults[0.6] = Config[0.5].dup.update(
392
+ responses_without_block: :frozen_dup,
334
393
  ).freeze
394
+ version_defaults[:next] = Config[0.6]
395
+ version_defaults[:future] = Config[:next]
335
396
 
336
397
  version_defaults.freeze
337
398
  end
@@ -186,7 +186,7 @@ module Net
186
186
 
187
187
  # Ensure argument is 'number' or raise DataFormatError
188
188
  def ensure_number(num)
189
- return if valid_number?(num)
189
+ return num if valid_number?(num)
190
190
 
191
191
  msg = "number must be unsigned 32-bit integer: #{num}"
192
192
  raise DataFormatError, msg
@@ -194,7 +194,7 @@ module Net
194
194
 
195
195
  # Ensure argument is 'nz_number' or raise DataFormatError
196
196
  def ensure_nz_number(num)
197
- return if valid_nz_number?(num)
197
+ return num if valid_nz_number?(num)
198
198
 
199
199
  msg = "nz_number must be non-zero unsigned 32-bit integer: #{num}"
200
200
  raise DataFormatError, msg
@@ -202,7 +202,7 @@ module Net
202
202
 
203
203
  # Ensure argument is 'mod_sequence_value' or raise DataFormatError
204
204
  def ensure_mod_sequence_value(num)
205
- return if valid_mod_sequence_value?(num)
205
+ return num if valid_mod_sequence_value?(num)
206
206
 
207
207
  msg = "mod_sequence_value must be unsigned 64-bit integer: #{num}"
208
208
  raise DataFormatError, msg
@@ -83,10 +83,12 @@ module Net
83
83
  elsif deprecated.empty?
84
84
  super host, port: port_or_options
85
85
  elsif deprecated.shift
86
- warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1
86
+ warn("DEPRECATED: Call Net::IMAP.new with keyword options",
87
+ uplevel: 1, category: :deprecated)
87
88
  super host, port: port_or_options, ssl: create_ssl_params(*deprecated)
88
89
  else
89
- warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1
90
+ warn("DEPRECATED: Call Net::IMAP.new with keyword options",
91
+ uplevel: 1, category: :deprecated)
90
92
  super host, port: port_or_options, ssl: false
91
93
  end
92
94
  end
@@ -113,7 +115,8 @@ module Net
113
115
  elsif deprecated.first.respond_to?(:to_hash)
114
116
  super(**Hash.try_convert(deprecated.first))
115
117
  else
116
- warn "DEPRECATED: Call Net::IMAP#starttls with keyword options", uplevel: 1
118
+ warn("DEPRECATED: Call Net::IMAP#starttls with keyword options",
119
+ uplevel: 1, category: :deprecated)
117
120
  super(**create_ssl_params(*deprecated))
118
121
  end
119
122
  end
@@ -7,6 +7,12 @@ module Net
7
7
  class Error < StandardError
8
8
  end
9
9
 
10
+ class LoginDisabledError < Error
11
+ def initialize(msg = "Remote server has disabled the LOGIN command", ...)
12
+ super
13
+ end
14
+ end
15
+
10
16
  # Error raised when data is in the incorrect format.
11
17
  class DataFormatError < Error
12
18
  end
@@ -939,7 +939,8 @@ module Net
939
939
  # for something else?
940
940
  #++
941
941
  def media_subtype
942
- 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)
943
944
  return subtype
944
945
  end
945
946
  end
@@ -984,7 +985,8 @@ module Net
984
985
  # generate a warning message to +stderr+, then return
985
986
  # the value of +subtype+.
986
987
  def media_subtype
987
- 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)
988
990
  return subtype
989
991
  end
990
992
  end
@@ -1040,77 +1042,6 @@ module Net
1040
1042
  end
1041
1043
  end
1042
1044
 
1043
- # BodyTypeAttachment is not used and will be removed in an upcoming release.
1044
- #
1045
- # === Bug Analysis
1046
- #
1047
- # \IMAP body structures are parenthesized lists and assign their fields
1048
- # positionally, so missing fields change the interpretation of all
1049
- # following fields. Additionally, different body types have a different
1050
- # number of required fields, followed by optional "extension" fields.
1051
- #
1052
- # BodyTypeAttachment was previously returned when a "message/rfc822" part,
1053
- # which should be sent as <tt>body-type-msg</tt> with ten required fields,
1054
- # was actually sent as a <tt>body-type-basic</tt> with _seven_ required
1055
- # fields.
1056
- #
1057
- # basic => type, subtype, param, id, desc, enc, octets, md5=nil, dsp=nil, lang=nil, loc=nil, *ext
1058
- # msg => type, subtype, param, id, desc, enc, octets, envelope, body, lines, md5=nil, ...
1059
- #
1060
- # Normally, +envelope+ and +md5+ are incompatible, but Net::IMAP leniently
1061
- # allowed buggy servers to send +NIL+ for +envelope+. As a result, when a
1062
- # server sent a <tt>message/rfc822</tt> part with +NIL+ for +md5+ and a
1063
- # non-<tt>NIL</tt> +dsp+, Net::IMAP misinterpreted the
1064
- # <tt>Content-Disposition</tt> as if it were a strange body type. In all
1065
- # reported cases, the <tt>Content-Disposition</tt> was "attachment", so
1066
- # BodyTypeAttachment was created as the workaround.
1067
- #
1068
- # === Current behavior
1069
- #
1070
- # When interpreted strictly, +envelope+ and +md5+ are incompatible. So the
1071
- # current parsing algorithm peeks ahead after it has received the seventh
1072
- # body field. If the next token is not the start of an +envelope+, we assume
1073
- # the server has incorrectly sent us a <tt>body-type-basic</tt> and return
1074
- # BodyTypeBasic. As a result, what was previously BodyTypeMessage#body =>
1075
- # BodyTypeAttachment is now BodyTypeBasic#disposition => ContentDisposition.
1076
- #
1077
- class BodyTypeAttachment < Struct.new(:dsp_type, :_unused_, :param)
1078
- # *invalid for BodyTypeAttachment*
1079
- def media_type
1080
- warn(<<~WARN, uplevel: 1)
1081
- BodyTypeAttachment#media_type is obsolete. Use dsp_type instead.
1082
- WARN
1083
- dsp_type
1084
- end
1085
-
1086
- # *invalid for BodyTypeAttachment*
1087
- def subtype
1088
- warn("BodyTypeAttachment#subtype is obsolete.\n", uplevel: 1)
1089
- nil
1090
- end
1091
-
1092
- ##
1093
- # method: dsp_type
1094
- # :call-seq: dsp_type -> string
1095
- #
1096
- # Returns the content disposition type, as defined by
1097
- # [DISPOSITION[https://tools.ietf.org/html/rfc2183]].
1098
-
1099
- ##
1100
- # method: param
1101
- # :call-seq: param -> hash
1102
- #
1103
- # Returns a hash representing parameters of the Content-Disposition
1104
- # field, as defined by [DISPOSITION[https://tools.ietf.org/html/rfc2183]].
1105
-
1106
- ##
1107
- def multipart?
1108
- return false
1109
- end
1110
- end
1111
-
1112
- deprecate_constant :BodyTypeAttachment
1113
-
1114
1045
  # Net::IMAP::BodyTypeMultipart represents body structures of messages and
1115
1046
  # message parts, when <tt>Content-Type</tt> is <tt>multipart/*</tt>.
1116
1047
  class BodyTypeMultipart < Struct.new(:media_type, :subtype,
@@ -1182,29 +1113,11 @@ module Net
1182
1113
  # generate a warning message to +stderr+, then return
1183
1114
  # the value of +subtype+.
1184
1115
  def media_subtype
1185
- 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)
1186
1118
  return subtype
1187
1119
  end
1188
1120
  end
1189
1121
 
1190
- # === Obsolete
1191
- # BodyTypeExtension is not used and will be removed in an upcoming release.
1192
- #
1193
- # >>>
1194
- # BodyTypeExtension was (incorrectly) used for <tt>message/*</tt> parts
1195
- # (besides <tt>message/rfc822</tt>, which correctly uses BodyTypeMessage).
1196
- #
1197
- # Net::IMAP now (correctly) parses all message types (other than
1198
- # <tt>message/rfc822</tt> or <tt>message/global</tt>) as BodyTypeBasic.
1199
- class BodyTypeExtension < Struct.new(:media_type, :subtype,
1200
- :params, :content_id,
1201
- :description, :encoding, :size)
1202
- def multipart?
1203
- return false
1204
- end
1205
- end
1206
-
1207
- deprecate_constant :BodyTypeExtension
1208
-
1209
1122
  end
1210
1123
  end
@@ -1317,31 +1317,19 @@ module Net
1317
1317
  # header-fld-name = astring
1318
1318
  #
1319
1319
  # NOTE: Previously, Net::IMAP recreated the raw original source string.
1320
- # Now, it grabs the raw encoded value using @str and @pos. A future
1321
- # version may simply return the decoded astring value. Although that is
1322
- # technically incompatible, it should almost never make a difference: all
1323
- # 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:
1324
1323
  #
1325
1324
  # https://www.iana.org/assignments/message-headers/message-headers.xhtml
1326
1325
  #
1327
- # Although RFC3501 allows any astring, RFC5322-valid header names are one
1328
- # or more of the printable US-ASCII characters, except SP and colon. So
1329
- # empty string isn't valid, and literals aren't needed and should not be
1330
- # used. This is explicitly unchanged by [I18N-HDRS] (RFC6532).
1331
- #
1332
- # RFC5233:
1326
+ # See also RFC5233:
1333
1327
  # optional-field = field-name ":" unstructured CRLF
1334
1328
  # field-name = 1*ftext
1335
1329
  # ftext = %d33-57 / ; Printable US-ASCII
1336
1330
  # %d59-126 ; characters not including
1337
1331
  # ; ":".
1338
- def header_fld_name
1339
- assert_no_lookahead
1340
- start = @pos
1341
- astring
1342
- end_pos = @token ? @pos - 1 : @pos
1343
- @str[start...end_pos]
1344
- end
1332
+ alias header_fld_name astring
1345
1333
 
1346
1334
  # mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list /
1347
1335
  # "LSUB" SP mailbox-list / "SEARCH" *(SP nz-number) /
@@ -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