net-imap 0.4.17 → 0.5.2
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.
- checksums.yaml +4 -4
- data/Gemfile +7 -1
- data/README.md +7 -3
- data/docs/styles.css +70 -14
- data/lib/net/imap/authenticators.rb +2 -2
- data/lib/net/imap/command_data.rb +32 -51
- data/lib/net/imap/config.rb +36 -10
- data/lib/net/imap/data_encoding.rb +3 -3
- data/lib/net/imap/data_lite.rb +225 -0
- data/lib/net/imap/deprecated_client_options.rb +6 -3
- data/lib/net/imap/errors.rb +6 -0
- data/lib/net/imap/esearch_result.rb +140 -0
- data/lib/net/imap/response_data.rb +7 -93
- data/lib/net/imap/response_parser/parser_utils.rb +5 -0
- data/lib/net/imap/response_parser.rb +77 -18
- 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 +538 -150
- data/net-imap.gemspec +1 -1
- data/rakelib/string_prep_tables_generator.rb +2 -0
- metadata +6 -4
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
# An "extended search" response (+ESEARCH+). ESearchResult should be
|
6
|
+
# returned (instead of SearchResult) by IMAP#search, IMAP#uid_search,
|
7
|
+
# IMAP#sort, and IMAP#uid_sort under any of the following conditions:
|
8
|
+
#
|
9
|
+
# * Return options were specified for IMAP#search or IMAP#uid_search.
|
10
|
+
# The server must support a search extension which allows
|
11
|
+
# RFC4466[https://www.rfc-editor.org/rfc/rfc4466.html] +return+ options,
|
12
|
+
# such as +ESEARCH+, +PARTIAL+, or +IMAP4rev2+.
|
13
|
+
# * Return options were specified for IMAP#sort or IMAP#uid_sort.
|
14
|
+
# The server must support the +ESORT+ extension
|
15
|
+
# {[RFC5267]}[https://www.rfc-editor.org/rfc/rfc5267.html#section-3].
|
16
|
+
#
|
17
|
+
# *NOTE:* IMAP#search and IMAP#uid_search do not support +ESORT+ yet.
|
18
|
+
# * The server supports +IMAP4rev2+ but _not_ +IMAP4rev1+, or +IMAP4rev2+
|
19
|
+
# has been enabled. +IMAP4rev2+ requires +ESEARCH+ results.
|
20
|
+
#
|
21
|
+
# Note that some servers may claim to support a search extension which
|
22
|
+
# requires an +ESEARCH+ result, such as +PARTIAL+, but still only return a
|
23
|
+
# +SEARCH+ result when +return+ options are specified.
|
24
|
+
#
|
25
|
+
# Some search extensions may result in the server sending ESearchResult
|
26
|
+
# responses after the initiating command has completed. Use
|
27
|
+
# IMAP#add_response_handler to handle these responses.
|
28
|
+
class ESearchResult < Data.define(:tag, :uid, :data)
|
29
|
+
def initialize(tag: nil, uid: nil, data: nil)
|
30
|
+
tag => String | nil; tag = -tag if tag
|
31
|
+
uid => true | false | nil; uid = !!uid
|
32
|
+
data => Array | nil; data ||= []; data.freeze
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
# :call-seq: to_a -> Array of integers
|
37
|
+
#
|
38
|
+
# When #all contains a SequenceSet of message sequence
|
39
|
+
# numbers or UIDs, +to_a+ returns that set as an array of integers.
|
40
|
+
#
|
41
|
+
# When #all is +nil+, either because the server
|
42
|
+
# returned no results or because +ALL+ was not included in
|
43
|
+
# the IMAP#search +RETURN+ options, #to_a returns an empty array.
|
44
|
+
#
|
45
|
+
# Note that SearchResult also implements +to_a+, so it can be used without
|
46
|
+
# checking if the server returned +SEARCH+ or +ESEARCH+ data.
|
47
|
+
def to_a; all&.numbers || [] end
|
48
|
+
|
49
|
+
##
|
50
|
+
# attr_reader: tag
|
51
|
+
#
|
52
|
+
# The tag string for the command that caused this response to be returned.
|
53
|
+
#
|
54
|
+
# When +nil+, this response was not caused by a particular command.
|
55
|
+
|
56
|
+
##
|
57
|
+
# attr_reader: uid
|
58
|
+
#
|
59
|
+
# Indicates whether #data in this response refers to UIDs (when +true+) or
|
60
|
+
# to message sequence numbers (when +false+).
|
61
|
+
|
62
|
+
##
|
63
|
+
alias uid? uid
|
64
|
+
|
65
|
+
##
|
66
|
+
# attr_reader: data
|
67
|
+
#
|
68
|
+
# Search return data, as an array of <tt>[name, value]</tt> pairs. Most
|
69
|
+
# return data corresponds to a search +return+ option with the same name.
|
70
|
+
#
|
71
|
+
# Note that some return data names may be used more than once per result.
|
72
|
+
#
|
73
|
+
# This data can be more simply retrieved by #min, #max, #all, #count,
|
74
|
+
# #modseq, and other methods.
|
75
|
+
|
76
|
+
# :call-seq: min -> integer or nil
|
77
|
+
#
|
78
|
+
# The lowest message number/UID that satisfies the SEARCH criteria.
|
79
|
+
#
|
80
|
+
# Returns +nil+ when the associated search command has no results, or when
|
81
|
+
# the +MIN+ return option wasn't specified.
|
82
|
+
#
|
83
|
+
# Requires +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.1] or
|
84
|
+
# +IMAP4rev2+ {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.4].
|
85
|
+
def min; data.assoc("MIN")&.last end
|
86
|
+
|
87
|
+
# :call-seq: max -> integer or nil
|
88
|
+
#
|
89
|
+
# The highest message number/UID that satisfies the SEARCH criteria.
|
90
|
+
#
|
91
|
+
# Returns +nil+ when the associated search command has no results, or when
|
92
|
+
# the +MAX+ return option wasn't specified.
|
93
|
+
#
|
94
|
+
# Requires +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.1] or
|
95
|
+
# +IMAP4rev2+ {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.4].
|
96
|
+
def max; data.assoc("MAX")&.last end
|
97
|
+
|
98
|
+
# :call-seq: all -> sequence set or nil
|
99
|
+
#
|
100
|
+
# A SequenceSet containing all message sequence numbers or UIDs that
|
101
|
+
# satisfy the SEARCH criteria.
|
102
|
+
#
|
103
|
+
# Returns +nil+ when the associated search command has no results, or when
|
104
|
+
# the +ALL+ return option was not specified but other return options were.
|
105
|
+
#
|
106
|
+
# Requires +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.1] or
|
107
|
+
# +IMAP4rev2+ {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.4].
|
108
|
+
#
|
109
|
+
# See also: #to_a
|
110
|
+
def all; data.assoc("ALL")&.last end
|
111
|
+
|
112
|
+
# :call-seq: count -> integer or nil
|
113
|
+
#
|
114
|
+
# Returns the number of messages that satisfy the SEARCH criteria.
|
115
|
+
#
|
116
|
+
# Returns +nil+ when the associated search command has no results, or when
|
117
|
+
# the +COUNT+ return option wasn't specified.
|
118
|
+
#
|
119
|
+
# Requires +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.1] or
|
120
|
+
# +IMAP4rev2+ {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.4].
|
121
|
+
def count; data.assoc("COUNT")&.last end
|
122
|
+
|
123
|
+
# :call-seq: modseq -> integer or nil
|
124
|
+
#
|
125
|
+
# The highest +mod-sequence+ of all messages being returned.
|
126
|
+
#
|
127
|
+
# Returns +nil+ when the associated search command has no results, or when
|
128
|
+
# the +MODSEQ+ search criterion wasn't specified.
|
129
|
+
#
|
130
|
+
# Note that there is no search +return+ option for +MODSEQ+. It will be
|
131
|
+
# returned whenever the +CONDSTORE+ extension has been enabled. Using the
|
132
|
+
# +MODSEQ+ search criteria will implicitly enable +CONDSTORE+.
|
133
|
+
#
|
134
|
+
# Requires +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]
|
135
|
+
# and +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.2].
|
136
|
+
def modseq; data.assoc("MODSEQ")&.last end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module Net
|
4
4
|
class IMAP < Protocol
|
5
|
+
autoload :ESearchResult, "#{__dir__}/esearch_result"
|
5
6
|
autoload :FetchData, "#{__dir__}/fetch_data"
|
6
7
|
autoload :SearchResult, "#{__dir__}/search_result"
|
7
8
|
autoload :SequenceSet, "#{__dir__}/sequence_set"
|
@@ -939,7 +940,8 @@ module Net
|
|
939
940
|
# for something else?
|
940
941
|
#++
|
941
942
|
def media_subtype
|
942
|
-
warn("media_subtype is obsolete, use subtype instead.\n",
|
943
|
+
warn("media_subtype is obsolete, use subtype instead.\n",
|
944
|
+
uplevel: 1, category: :deprecated)
|
943
945
|
return subtype
|
944
946
|
end
|
945
947
|
end
|
@@ -984,7 +986,8 @@ module Net
|
|
984
986
|
# generate a warning message to +stderr+, then return
|
985
987
|
# the value of +subtype+.
|
986
988
|
def media_subtype
|
987
|
-
warn("media_subtype is obsolete, use subtype instead.\n",
|
989
|
+
warn("media_subtype is obsolete, use subtype instead.\n",
|
990
|
+
uplevel: 1, category: :deprecated)
|
988
991
|
return subtype
|
989
992
|
end
|
990
993
|
end
|
@@ -1040,77 +1043,6 @@ module Net
|
|
1040
1043
|
end
|
1041
1044
|
end
|
1042
1045
|
|
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
1046
|
# Net::IMAP::BodyTypeMultipart represents body structures of messages and
|
1115
1047
|
# message parts, when <tt>Content-Type</tt> is <tt>multipart/*</tt>.
|
1116
1048
|
class BodyTypeMultipart < Struct.new(:media_type, :subtype,
|
@@ -1182,29 +1114,11 @@ module Net
|
|
1182
1114
|
# generate a warning message to +stderr+, then return
|
1183
1115
|
# the value of +subtype+.
|
1184
1116
|
def media_subtype
|
1185
|
-
warn("media_subtype is obsolete, use subtype instead.\n",
|
1117
|
+
warn("media_subtype is obsolete, use subtype instead.\n",
|
1118
|
+
uplevel: 1, category: :deprecated)
|
1186
1119
|
return subtype
|
1187
1120
|
end
|
1188
1121
|
end
|
1189
1122
|
|
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
1123
|
end
|
1210
1124
|
end
|
@@ -769,7 +769,6 @@ module Net
|
|
769
769
|
def response_data__ignored; response_data__unhandled(IgnoredResponse) end
|
770
770
|
alias response_data__noop response_data__ignored
|
771
771
|
|
772
|
-
alias esearch_response response_data__unhandled
|
773
772
|
alias expunged_resp response_data__unhandled
|
774
773
|
alias uidfetch_resp response_data__unhandled
|
775
774
|
alias listrights_data response_data__unhandled
|
@@ -1317,31 +1316,19 @@ module Net
|
|
1317
1316
|
# header-fld-name = astring
|
1318
1317
|
#
|
1319
1318
|
# NOTE: Previously, Net::IMAP recreated the raw original source string.
|
1320
|
-
# Now, it
|
1321
|
-
#
|
1322
|
-
#
|
1323
|
-
# standard header field names are valid atoms:
|
1319
|
+
# Now, it returns the decoded astring value. Although this is technically
|
1320
|
+
# incompatible, it should almost never make a difference: all standard
|
1321
|
+
# header field names are valid atoms:
|
1324
1322
|
#
|
1325
1323
|
# https://www.iana.org/assignments/message-headers/message-headers.xhtml
|
1326
1324
|
#
|
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:
|
1325
|
+
# See also RFC5233:
|
1333
1326
|
# optional-field = field-name ":" unstructured CRLF
|
1334
1327
|
# field-name = 1*ftext
|
1335
1328
|
# ftext = %d33-57 / ; Printable US-ASCII
|
1336
1329
|
# %d59-126 ; characters not including
|
1337
1330
|
# ; ":".
|
1338
|
-
|
1339
|
-
assert_no_lookahead
|
1340
|
-
start = @pos
|
1341
|
-
astring
|
1342
|
-
end_pos = @token ? @pos - 1 : @pos
|
1343
|
-
@str[start...end_pos]
|
1344
|
-
end
|
1331
|
+
alias header_fld_name astring
|
1345
1332
|
|
1346
1333
|
# mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list /
|
1347
1334
|
# "LSUB" SP mailbox-list / "SEARCH" *(SP nz-number) /
|
@@ -1480,6 +1467,78 @@ module Net
|
|
1480
1467
|
end
|
1481
1468
|
alias sort_data mailbox_data__search
|
1482
1469
|
|
1470
|
+
# esearch-response = "ESEARCH" [search-correlator] [SP "UID"]
|
1471
|
+
# *(SP search-return-data)
|
1472
|
+
# ;; Note that SEARCH and ESEARCH responses
|
1473
|
+
# ;; SHOULD be mutually exclusive,
|
1474
|
+
# ;; i.e., only one of the response types
|
1475
|
+
# ;; should be
|
1476
|
+
# ;; returned as a result of a command.
|
1477
|
+
# esearch-response = "ESEARCH" [search-correlator] [SP "UID"]
|
1478
|
+
# *(SP search-return-data)
|
1479
|
+
# ; ESEARCH response replaces SEARCH response
|
1480
|
+
# ; from IMAP4rev1.
|
1481
|
+
# search-correlator = SP "(" "TAG" SP tag-string ")"
|
1482
|
+
def esearch_response
|
1483
|
+
name = label("ESEARCH")
|
1484
|
+
tag = search_correlator if peek_str?(" (")
|
1485
|
+
uid = peek_re?(/\G UID\b/i) && (SP!; label("UID"); true)
|
1486
|
+
data = []
|
1487
|
+
data << search_return_data while SP?
|
1488
|
+
esearch = ESearchResult.new(tag, uid, data)
|
1489
|
+
UntaggedResponse.new(name, esearch, @str)
|
1490
|
+
end
|
1491
|
+
|
1492
|
+
# From RFC4731 (ESEARCH):
|
1493
|
+
# search-return-data = "MIN" SP nz-number /
|
1494
|
+
# "MAX" SP nz-number /
|
1495
|
+
# "ALL" SP sequence-set /
|
1496
|
+
# "COUNT" SP number /
|
1497
|
+
# search-ret-data-ext
|
1498
|
+
# ; All return data items conform to
|
1499
|
+
# ; search-ret-data-ext syntax.
|
1500
|
+
# search-ret-data-ext = search-modifier-name SP search-return-value
|
1501
|
+
# search-modifier-name = tagged-ext-label
|
1502
|
+
# search-return-value = tagged-ext-val
|
1503
|
+
#
|
1504
|
+
# From RFC4731 (ESEARCH):
|
1505
|
+
# search-return-data =/ "MODSEQ" SP mod-sequence-value
|
1506
|
+
#
|
1507
|
+
def search_return_data
|
1508
|
+
label = search_modifier_name; SP!
|
1509
|
+
value =
|
1510
|
+
case label
|
1511
|
+
when "MIN" then nz_number
|
1512
|
+
when "MAX" then nz_number
|
1513
|
+
when "ALL" then sequence_set
|
1514
|
+
when "COUNT" then number
|
1515
|
+
when "MODSEQ" then mod_sequence_value # RFC7162: CONDSTORE
|
1516
|
+
else search_return_value
|
1517
|
+
end
|
1518
|
+
[label, value]
|
1519
|
+
end
|
1520
|
+
|
1521
|
+
# search-modifier-name = tagged-ext-label
|
1522
|
+
alias search_modifier_name tagged_ext_label
|
1523
|
+
|
1524
|
+
# search-return-value = tagged-ext-val
|
1525
|
+
# ; Data for the returned search option.
|
1526
|
+
# ; A single "nz-number"/"number"/"number64" value
|
1527
|
+
# ; can be returned as an atom (i.e., without
|
1528
|
+
# ; quoting). A sequence-set can be returned
|
1529
|
+
# ; as an atom as well.
|
1530
|
+
def search_return_value; ExtensionData.new(tagged_ext_val) end
|
1531
|
+
|
1532
|
+
# search-correlator = SP "(" "TAG" SP tag-string ")"
|
1533
|
+
def search_correlator
|
1534
|
+
SP!; lpar; label("TAG"); SP!; tag = tag_string; rpar
|
1535
|
+
tag
|
1536
|
+
end
|
1537
|
+
|
1538
|
+
# tag-string = astring
|
1539
|
+
# ; <tag> represented as <astring>
|
1540
|
+
alias tag_string astring
|
1541
|
+
|
1483
1542
|
# RFC5256: THREAD
|
1484
1543
|
# thread-data = "THREAD" [SP 1*thread-list]
|
1485
1544
|
def thread_data
|
@@ -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
|