net-imap 0.4.17 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +7 -1
- data/lib/net/imap/authenticators.rb +2 -2
- data/lib/net/imap/command_data.rb +11 -0
- data/lib/net/imap/config.rb +36 -10
- data/lib/net/imap/data_encoding.rb +3 -3
- data/lib/net/imap/deprecated_client_options.rb +6 -3
- data/lib/net/imap/errors.rb +6 -0
- data/lib/net/imap/response_data.rb +6 -93
- data/lib/net/imap/response_parser.rb +5 -17
- data/lib/net/imap/sasl/authentication_exchange.rb +52 -20
- data/lib/net/imap/sasl/authenticators.rb +8 -4
- data/lib/net/imap/sasl/client_adapter.rb +77 -26
- data/lib/net/imap/sasl/cram_md5_authenticator.rb +1 -1
- data/lib/net/imap/sasl/digest_md5_authenticator.rb +213 -51
- data/lib/net/imap/sasl/login_authenticator.rb +2 -1
- data/lib/net/imap/sasl/protocol_adapters.rb +60 -4
- data/lib/net/imap/sasl.rb +6 -3
- data/lib/net/imap/sasl_adapter.rb +0 -1
- data/lib/net/imap/sequence_set.rb +18 -23
- data/lib/net/imap.rb +285 -108
- data/net-imap.gemspec +1 -1
- data/rakelib/string_prep_tables_generator.rb +2 -0
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf511b8683c6931cf9ec2d3aa4a55ef634a2cc0e670590a4142d9895594b9191
|
4
|
+
data.tar.gz: db4ca0b7230553258e0d81a8a5b5cbed4273e30245200db661145ba430af20d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b61a04c6992df7d6ffcf0e5396723966c3f3a5a1c6a596caf413c63d355100c8d2c28096a2261045d52ada4d8e995bba1c4f12864bf65fe1355c331df236eb85
|
7
|
+
data.tar.gz: 0bc68ec15c5d53f0f437eab5318bf8cdd428dcdc650a98f66e961658ed361b125e866db2d41954e50ce66de5ec143d108e9135f554bd240ff0d7057724d16416
|
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)
|
data/lib/net/imap/config.rb
CHANGED
@@ -223,6 +223,29 @@ module Net
|
|
223
223
|
# Use +SASL-IR+ when it is supported by the server and the mechanism.
|
224
224
|
attr_accessor :sasl_ir, type: :boolean
|
225
225
|
|
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
|
+
]
|
226
249
|
|
227
250
|
# Controls the behavior of Net::IMAP#responses when called without any
|
228
251
|
# arguments (+type+ or +block+).
|
@@ -340,33 +363,36 @@ module Net
|
|
340
363
|
open_timeout: 30,
|
341
364
|
idle_response_timeout: 5,
|
342
365
|
sasl_ir: true,
|
343
|
-
|
366
|
+
enforce_logindisabled: true,
|
367
|
+
responses_without_block: :warn,
|
344
368
|
).freeze
|
345
369
|
|
346
370
|
@global = default.new
|
347
371
|
|
348
|
-
version_defaults[
|
372
|
+
version_defaults[:default] = Config[default.send(:defaults_hash)]
|
373
|
+
version_defaults[:current] = Config[:default]
|
349
374
|
|
350
|
-
version_defaults[0] = Config[
|
375
|
+
version_defaults[0] = Config[:current].dup.update(
|
351
376
|
sasl_ir: false,
|
377
|
+
responses_without_block: :silence_deprecation_warning,
|
378
|
+
enforce_logindisabled: false,
|
352
379
|
).freeze
|
353
380
|
version_defaults[0.0] = Config[0]
|
354
381
|
version_defaults[0.1] = Config[0]
|
355
382
|
version_defaults[0.2] = Config[0]
|
356
383
|
version_defaults[0.3] = Config[0]
|
357
384
|
|
358
|
-
version_defaults[0.
|
359
|
-
|
385
|
+
version_defaults[0.4] = Config[0.3].dup.update(
|
386
|
+
sasl_ir: true,
|
360
387
|
).freeze
|
361
388
|
|
362
|
-
version_defaults[
|
363
|
-
version_defaults[:current] = Config[0.4]
|
364
|
-
version_defaults[:next] = Config[0.5]
|
389
|
+
version_defaults[0.5] = Config[:current]
|
365
390
|
|
366
|
-
version_defaults[0.6]
|
391
|
+
version_defaults[0.6] = Config[0.5].dup.update(
|
367
392
|
responses_without_block: :frozen_dup,
|
368
393
|
).freeze
|
369
|
-
version_defaults[:
|
394
|
+
version_defaults[:next] = Config[0.6]
|
395
|
+
version_defaults[:future] = Config[:next]
|
370
396
|
|
371
397
|
version_defaults.freeze
|
372
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
|
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
|
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
|
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
|
data/lib/net/imap/errors.rb
CHANGED
@@ -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",
|
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",
|
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",
|
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
|
1321
|
-
#
|
1322
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
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
|
-
#
|
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
|
-
#
|
29
|
-
# .authenticate
|
28
|
+
# MyClient::SASLAdapter.new(self).authenticate(...)
|
30
29
|
# end
|
31
30
|
#
|
32
|
-
#
|
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
|
-
#
|
38
|
-
#
|
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
|
-
#
|
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 =
|
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 =
|
86
|
+
key = Authenticators.normalize_name(name)
|
83
87
|
@authenticators.delete(key)
|
84
88
|
end
|
85
89
|
|
86
90
|
def mechanism?(name)
|
87
|
-
key =
|
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 =
|
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
|