net-imap 0.3.10 → 0.4.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.
- checksums.yaml +4 -4
- data/.github/workflows/pages.yml +46 -0
- data/.github/workflows/test.yml +5 -12
- data/Gemfile +2 -1
- data/README.md +15 -4
- data/Rakefile +0 -7
- data/benchmarks/generate_parser_benchmarks +52 -0
- data/benchmarks/parser.yml +578 -0
- data/benchmarks/stringprep.yml +1 -1
- data/lib/net/imap/authenticators.rb +26 -57
- data/lib/net/imap/command_data.rb +13 -6
- data/lib/net/imap/deprecated_client_options.rb +139 -0
- data/lib/net/imap/errors.rb +0 -54
- data/lib/net/imap/response_data.rb +46 -41
- data/lib/net/imap/response_parser/parser_utils.rb +230 -0
- data/lib/net/imap/response_parser.rb +667 -649
- data/lib/net/imap/sasl/anonymous_authenticator.rb +68 -0
- data/lib/net/imap/sasl/authenticators.rb +112 -0
- data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +15 -9
- data/lib/net/imap/{authenticators/digest_md5.rb → sasl/digest_md5_authenticator.rb} +74 -21
- data/lib/net/imap/sasl/external_authenticator.rb +62 -0
- data/lib/net/imap/sasl/gs2_header.rb +80 -0
- data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +19 -14
- data/lib/net/imap/sasl/oauthbearer_authenticator.rb +164 -0
- data/lib/net/imap/sasl/plain_authenticator.rb +93 -0
- data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
- data/lib/net/imap/sasl/scram_authenticator.rb +278 -0
- data/lib/net/imap/sasl/stringprep.rb +6 -66
- data/lib/net/imap/sasl/xoauth2_authenticator.rb +88 -0
- data/lib/net/imap/sasl.rb +139 -44
- data/lib/net/imap/stringprep/nameprep.rb +70 -0
- data/lib/net/imap/stringprep/saslprep.rb +69 -0
- data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
- data/lib/net/imap/stringprep/tables.rb +146 -0
- data/lib/net/imap/stringprep/trace.rb +85 -0
- data/lib/net/imap/stringprep.rb +159 -0
- data/lib/net/imap.rb +987 -719
- data/net-imap.gemspec +1 -1
- data/rakelib/saslprep.rake +4 -4
- data/rakelib/string_prep_tables_generator.rb +82 -60
- metadata +30 -13
- data/lib/net/imap/authenticators/plain.rb +0 -41
- data/lib/net/imap/authenticators/xoauth2.rb +0 -20
- data/lib/net/imap/response_reader.rb +0 -75
- data/lib/net/imap/sasl/saslprep.rb +0 -55
- data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
- data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
data/lib/net/imap.rb
CHANGED
|
@@ -24,11 +24,9 @@ end
|
|
|
24
24
|
module Net
|
|
25
25
|
|
|
26
26
|
# Net::IMAP implements Internet Message Access Protocol (\IMAP) client
|
|
27
|
-
# functionality. The protocol is described
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
# TODO: and [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
|
|
31
|
-
#++
|
|
27
|
+
# functionality. The protocol is described
|
|
28
|
+
# in {IMAP4rev1 [RFC3501]}[https://tools.ietf.org/html/rfc3501]
|
|
29
|
+
# and {IMAP4rev2 [RFC9051]}[https://tools.ietf.org/html/rfc9051].
|
|
32
30
|
#
|
|
33
31
|
# == \IMAP Overview
|
|
34
32
|
#
|
|
@@ -45,16 +43,10 @@ module Net
|
|
|
45
43
|
# To work on the messages within a mailbox, the client must
|
|
46
44
|
# first select that mailbox, using either #select or #examine
|
|
47
45
|
# (for read-only access). Once the client has successfully
|
|
48
|
-
# selected a mailbox, they enter the
|
|
46
|
+
# selected a mailbox, they enter the "_selected_" state, and that
|
|
49
47
|
# mailbox becomes the _current_ mailbox, on which mail-item
|
|
50
48
|
# related commands implicitly operate.
|
|
51
49
|
#
|
|
52
|
-
# === Connection state
|
|
53
|
-
#
|
|
54
|
-
# Once an IMAP connection is established, the connection is in one of four
|
|
55
|
-
# states: <tt>not authenticated</tt>, +authenticated+, +selected+, and
|
|
56
|
-
# +logout+. Most commands are valid only in certain states.
|
|
57
|
-
#
|
|
58
50
|
# === Sequence numbers and UIDs
|
|
59
51
|
#
|
|
60
52
|
# Messages have two sorts of identifiers: message sequence
|
|
@@ -83,31 +75,22 @@ module Net
|
|
|
83
75
|
# UIDs have to be reassigned. An \IMAP client thus cannot
|
|
84
76
|
# rearrange message orders.
|
|
85
77
|
#
|
|
86
|
-
# ===
|
|
87
|
-
#
|
|
88
|
-
# Net::IMAP <em>does not modify its behavior</em> according to server
|
|
89
|
-
# #capability. Users of the class must check for required capabilities before
|
|
90
|
-
# issuing commands. Special care should be taken to follow all #capability
|
|
91
|
-
# requirements for #starttls, #login, and #authenticate.
|
|
92
|
-
#
|
|
93
|
-
# See the #capability method for more information.
|
|
94
|
-
#
|
|
95
|
-
# == Examples of Usage
|
|
78
|
+
# === Examples of Usage
|
|
96
79
|
#
|
|
97
|
-
#
|
|
80
|
+
# ==== List sender and subject of all recent messages in the default mailbox
|
|
98
81
|
#
|
|
99
82
|
# imap = Net::IMAP.new('mail.example.com')
|
|
100
|
-
# imap.authenticate('
|
|
83
|
+
# imap.authenticate('PLAIN', 'joe_user', 'joes_password')
|
|
101
84
|
# imap.examine('INBOX')
|
|
102
85
|
# imap.search(["RECENT"]).each do |message_id|
|
|
103
86
|
# envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
|
|
104
87
|
# puts "#{envelope.from[0].name}: \t#{envelope.subject}"
|
|
105
88
|
# end
|
|
106
89
|
#
|
|
107
|
-
#
|
|
90
|
+
# ==== Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
|
|
108
91
|
#
|
|
109
92
|
# imap = Net::IMAP.new('mail.example.com')
|
|
110
|
-
# imap.authenticate('
|
|
93
|
+
# imap.authenticate('PLAIN', 'joe_user', 'joes_password')
|
|
111
94
|
# imap.select('Mail/sent-mail')
|
|
112
95
|
# if not imap.list('Mail/', 'sent-apr03')
|
|
113
96
|
# imap.create('Mail/sent-apr03')
|
|
@@ -118,12 +101,96 @@ module Net
|
|
|
118
101
|
# end
|
|
119
102
|
# imap.expunge
|
|
120
103
|
#
|
|
104
|
+
# == Capabilities
|
|
105
|
+
#
|
|
106
|
+
# Most Net::IMAP methods do not _currently_ modify their behaviour according
|
|
107
|
+
# to the server's advertised #capabilities. Users of this class must check
|
|
108
|
+
# that the server is capable of extension commands or command arguments before
|
|
109
|
+
# sending them. Special care should be taken to follow the #capabilities
|
|
110
|
+
# requirements for #starttls, #login, and #authenticate.
|
|
111
|
+
#
|
|
112
|
+
# See #capable?, #auth_capable?, #capabilities, #auth_mechanisms to discover
|
|
113
|
+
# server capabilities. For relevant capability requirements, see the
|
|
114
|
+
# documentation on each \IMAP command.
|
|
115
|
+
#
|
|
116
|
+
# imap = Net::IMAP.new("mail.example.com")
|
|
117
|
+
# imap.capable?(:IMAP4rev1) or raise "Not an IMAP4rev1 server"
|
|
118
|
+
# imap.capable?(:starttls) or raise "Cannot start TLS"
|
|
119
|
+
# imap.starttls
|
|
120
|
+
#
|
|
121
|
+
# if imap.auth_capable?("PLAIN")
|
|
122
|
+
# imap.authenticate "PLAIN", username, password
|
|
123
|
+
# elsif !imap.capability?("LOGINDISABLED")
|
|
124
|
+
# imap.login username, password
|
|
125
|
+
# else
|
|
126
|
+
# raise "No acceptable authentication mechanisms"
|
|
127
|
+
# end
|
|
128
|
+
#
|
|
129
|
+
# # Support for "UTF8=ACCEPT" implies support for "ENABLE"
|
|
130
|
+
# imap.enable :utf8 if imap.auth_capable?("UTF8=ACCEPT")
|
|
131
|
+
#
|
|
132
|
+
# namespaces = imap.namespace if imap.capable?(:namespace)
|
|
133
|
+
# mbox_prefix = namespaces&.personal&.first&.prefix || ""
|
|
134
|
+
# mbox_delim = namespaces&.personal&.first&.delim || "/"
|
|
135
|
+
# mbox_path = prefix + %w[path to my mailbox].join(delim)
|
|
136
|
+
# imap.create mbox_path
|
|
137
|
+
#
|
|
138
|
+
# === Basic IMAP4rev1 capabilities
|
|
139
|
+
#
|
|
140
|
+
# IMAP4rev1 servers must advertise +IMAP4rev1+ in their capabilities list.
|
|
141
|
+
# IMAP4rev1 servers must _implement_ the +STARTTLS+, <tt>AUTH=PLAIN</tt>,
|
|
142
|
+
# and +LOGINDISABLED+ capabilities. See #starttls, #login, and #authenticate
|
|
143
|
+
# for the implications of these capabilities.
|
|
144
|
+
#
|
|
145
|
+
# === Caching +CAPABILITY+ responses
|
|
146
|
+
#
|
|
147
|
+
# Net::IMAP automatically stores and discards capability data according to the
|
|
148
|
+
# the requirements and recommendations in
|
|
149
|
+
# {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
|
|
150
|
+
# {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
|
|
151
|
+
# {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
|
|
152
|
+
# Use #capable?, #auth_capable?, or #capabilities to use this cache and avoid
|
|
153
|
+
# sending the #capability command unnecessarily.
|
|
154
|
+
#
|
|
155
|
+
# The server may advertise its initial capabilities using the +CAPABILITY+
|
|
156
|
+
# ResponseCode in a +PREAUTH+ or +OK+ #greeting. When TLS has started
|
|
157
|
+
# (#starttls) and after authentication (#login or #authenticate), the server's
|
|
158
|
+
# capabilities may change and cached capabilities are discarded. The server
|
|
159
|
+
# may send updated capabilities with an +OK+ TaggedResponse to #login or
|
|
160
|
+
# #authenticate, and these will be cached by Net::IMAP. But the
|
|
161
|
+
# TaggedResponse to #starttls MUST be ignored--it is sent before TLS starts
|
|
162
|
+
# and is unprotected.
|
|
163
|
+
#
|
|
164
|
+
# When storing capability values to variables, be careful that they are
|
|
165
|
+
# discarded or reset appropriately, especially following #starttls.
|
|
166
|
+
#
|
|
167
|
+
# === Using IMAP4rev1 extensions
|
|
168
|
+
#
|
|
169
|
+
# See the {IANA IMAP4 capabilities
|
|
170
|
+
# registry}[http://www.iana.org/assignments/imap4-capabilities] for a list of
|
|
171
|
+
# all standard capabilities, and their reference RFCs.
|
|
172
|
+
#
|
|
173
|
+
# IMAP4rev1 servers must not activate behavior that is incompatible with the
|
|
174
|
+
# base specification until an explicit client action invokes a capability,
|
|
175
|
+
# e.g. sending a command or command argument specific to that capability.
|
|
176
|
+
# Servers may send data with backward compatible behavior, such as response
|
|
177
|
+
# codes or mailbox attributes, at any time without client action.
|
|
178
|
+
#
|
|
179
|
+
# Invoking capabilities which are unknown to Net::IMAP may cause unexpected
|
|
180
|
+
# behavior and errors. For example, ResponseParseError is raised when
|
|
181
|
+
# unknown response syntax is received. Invoking commands or command
|
|
182
|
+
# parameters that are unsupported by the server may raise NoResponseError,
|
|
183
|
+
# BadResponseError, or cause other unexpected behavior.
|
|
184
|
+
#
|
|
185
|
+
# Some capabilities must be explicitly activated using the #enable command.
|
|
186
|
+
# See #enable for details.
|
|
187
|
+
#
|
|
121
188
|
# == Thread Safety
|
|
122
189
|
#
|
|
123
190
|
# Net::IMAP supports concurrent threads. For example,
|
|
124
191
|
#
|
|
125
192
|
# imap = Net::IMAP.new("imap.foo.net", "imap2")
|
|
126
|
-
# imap.authenticate("
|
|
193
|
+
# imap.authenticate("scram-md5", "bar", "password")
|
|
127
194
|
# imap.select("inbox")
|
|
128
195
|
# fetch_thread = Thread.start { imap.fetch(1..-1, "UID") }
|
|
129
196
|
# search_result = imap.search(["BODY", "hello"])
|
|
@@ -132,41 +199,6 @@ module Net
|
|
|
132
199
|
#
|
|
133
200
|
# This script invokes the FETCH command and the SEARCH command concurrently.
|
|
134
201
|
#
|
|
135
|
-
# When running multiple commands, care must be taken to avoid ambiguity. For
|
|
136
|
-
# example, SEARCH responses are ambiguous about which command they are
|
|
137
|
-
# responding to, so search commands should not run simultaneously, unless the
|
|
138
|
-
# server supports +ESEARCH+ {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731] or
|
|
139
|
-
# IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]. See {RFC9051
|
|
140
|
-
# §5.5}[https://www.rfc-editor.org/rfc/rfc9051.html#section-5.5] for
|
|
141
|
-
# other examples of command sequences which should not be pipelined.
|
|
142
|
-
#
|
|
143
|
-
# == Unbounded memory use
|
|
144
|
-
#
|
|
145
|
-
# Net::IMAP reads server responses in a separate receiver thread per client.
|
|
146
|
-
# Unhandled response data is saved to #responses, and response_handlers run
|
|
147
|
-
# inside the receiver thread. See the list of methods for {handling server
|
|
148
|
-
# responses}[rdoc-ref:Net::IMAP@Handling+server+responses], below.
|
|
149
|
-
#
|
|
150
|
-
# Because the receiver thread continuously reads and saves new responses, some
|
|
151
|
-
# scenarios must be careful to avoid unbounded memory use:
|
|
152
|
-
#
|
|
153
|
-
# * Commands such as #list or #fetch can have an enormous number of responses.
|
|
154
|
-
# * Commands such as #fetch can result in an enormous size per response.
|
|
155
|
-
# * Long-lived connections will gradually accumulate unsolicited server
|
|
156
|
-
# responses, especially +EXISTS+, +FETCH+, and +EXPUNGE+ responses.
|
|
157
|
-
# * A buggy or untrusted server could send inappropriate responses, which
|
|
158
|
-
# could be very numerous, very large, and very rapid.
|
|
159
|
-
#
|
|
160
|
-
# Use paginated or limited versions of commands whenever possible.
|
|
161
|
-
#
|
|
162
|
-
# Use #max_response_size to impose a limit on incoming server responses
|
|
163
|
-
# as they are being read. <em>This is especially important for untrusted
|
|
164
|
-
# servers.</em>
|
|
165
|
-
#
|
|
166
|
-
# Use #add_response_handler to handle responses after each one is received.
|
|
167
|
-
# Use the +response_handlers+ argument to ::new to assign response handlers
|
|
168
|
-
# before the receiver thread is started.
|
|
169
|
-
#
|
|
170
202
|
# == Errors
|
|
171
203
|
#
|
|
172
204
|
# An \IMAP server can send three different types of responses to indicate
|
|
@@ -214,24 +246,54 @@ module Net
|
|
|
214
246
|
# == What's here?
|
|
215
247
|
#
|
|
216
248
|
# * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods]
|
|
217
|
-
# * {
|
|
218
|
-
# * {...for any state}[rdoc-ref:Net::IMAP@IMAP+commands+for+any+state]
|
|
219
|
-
# * {...for the "not authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Not+Authenticated-22+state]
|
|
220
|
-
# * {...for the "authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Authenticated-22+state]
|
|
221
|
-
# * {...for the "selected" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Selected-22+state]
|
|
222
|
-
# * {...for the "logout" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Logout-22+state]
|
|
223
|
-
# * {Supported IMAP extensions}[rdoc-ref:Net::IMAP@Supported+IMAP+extensions]
|
|
249
|
+
# * {Server capabilities}[rdoc-ref:Net::IMAP@Server+capabilities]
|
|
224
250
|
# * {Handling server responses}[rdoc-ref:Net::IMAP@Handling+server+responses]
|
|
251
|
+
# * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
|
|
252
|
+
# * {for any state}[rdoc-ref:Net::IMAP@Any+state]
|
|
253
|
+
# * {for the "not authenticated" state}[rdoc-ref:Net::IMAP@Not+Authenticated+state]
|
|
254
|
+
# * {for the "authenticated" state}[rdoc-ref:Net::IMAP@Authenticated+state]
|
|
255
|
+
# * {for the "selected" state}[rdoc-ref:Net::IMAP@Selected+state]
|
|
256
|
+
# * {for the "logout" state}[rdoc-ref:Net::IMAP@Logout+state]
|
|
257
|
+
# * {IMAP extension support}[rdoc-ref:Net::IMAP@IMAP+extension+support]
|
|
225
258
|
#
|
|
226
259
|
# === Connection control methods
|
|
227
260
|
#
|
|
228
|
-
# - Net::IMAP.new:
|
|
229
|
-
# successful server greeting before
|
|
261
|
+
# - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
|
|
262
|
+
# waits for a successful server greeting before the method returns.
|
|
230
263
|
# - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
|
|
231
|
-
# - #logout: Tells the server to end the session.
|
|
264
|
+
# - #logout: Tells the server to end the session. Enters the "_logout_" state.
|
|
232
265
|
# - #disconnect: Disconnects the connection (without sending #logout first).
|
|
233
266
|
# - #disconnected?: True if the connection has been closed.
|
|
234
267
|
#
|
|
268
|
+
# === Server capabilities
|
|
269
|
+
#
|
|
270
|
+
# - #capable?: Returns whether the server supports a given capability.
|
|
271
|
+
# - #capabilities: Returns the server's capabilities as an array of strings.
|
|
272
|
+
# - #auth_capable?: Returns whether the server advertises support for a given
|
|
273
|
+
# SASL mechanism, for use with #authenticate.
|
|
274
|
+
# - #auth_mechanisms: Returns the #authenticate SASL mechanisms which
|
|
275
|
+
# the server claims to support as an array of strings.
|
|
276
|
+
# - #clear_cached_capabilities: Clears cached capabilities.
|
|
277
|
+
#
|
|
278
|
+
# <em>The capabilities cache is automatically cleared after completing
|
|
279
|
+
# #starttls, #login, or #authenticate.</em>
|
|
280
|
+
# - #capability: Sends the +CAPABILITY+ command and returns the #capabilities.
|
|
281
|
+
#
|
|
282
|
+
# <em>In general, #capable? should be used rather than explicitly sending a
|
|
283
|
+
# +CAPABILITY+ command to the server.</em>
|
|
284
|
+
#
|
|
285
|
+
# === Handling server responses
|
|
286
|
+
#
|
|
287
|
+
# - #greeting: The server's initial untagged response, which can indicate a
|
|
288
|
+
# pre-authenticated connection.
|
|
289
|
+
# - #responses: Yields unhandled UntaggedResponse#data and <em>non-+nil+</em>
|
|
290
|
+
# ResponseCode#data.
|
|
291
|
+
# - #clear_responses: Deletes unhandled data from #responses and returns it.
|
|
292
|
+
# - #add_response_handler: Add a block to be called inside the receiver thread
|
|
293
|
+
# with every server response.
|
|
294
|
+
# - #response_handlers: Returns the list of response handlers.
|
|
295
|
+
# - #remove_response_handler: Remove a previously added response handler.
|
|
296
|
+
#
|
|
235
297
|
# === Core \IMAP commands
|
|
236
298
|
#
|
|
237
299
|
# The following commands are defined either by
|
|
@@ -240,70 +302,50 @@ module Net
|
|
|
240
302
|
# [IDLE[https://tools.ietf.org/html/rfc2177]],
|
|
241
303
|
# [NAMESPACE[https://tools.ietf.org/html/rfc2342]],
|
|
242
304
|
# [UNSELECT[https://tools.ietf.org/html/rfc3691]],
|
|
243
|
-
|
|
244
|
-
# TODO: [ENABLE[https://tools.ietf.org/html/rfc5161]],
|
|
245
|
-
# TODO: [LIST-EXTENDED[https://tools.ietf.org/html/rfc5258]],
|
|
246
|
-
# TODO: [LIST-STATUS[https://tools.ietf.org/html/rfc5819]],
|
|
247
|
-
#++
|
|
305
|
+
# [ENABLE[https://tools.ietf.org/html/rfc5161]],
|
|
248
306
|
# [MOVE[https://tools.ietf.org/html/rfc6851]].
|
|
249
307
|
# These extensions are widely supported by modern IMAP4rev1 servers and have
|
|
250
308
|
# all been integrated into [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
|
|
251
|
-
# <em
|
|
252
|
-
#
|
|
253
|
-
|
|
254
|
-
# TODO: When IMAP4rev2 is supported, add the following to the each of the
|
|
255
|
-
# appropriate commands below.
|
|
256
|
-
# Note:: CHECK has been removed from IMAP4rev2.
|
|
257
|
-
# Note:: LSUB is obsoleted by +LIST-EXTENDED and has been removed from IMAP4rev2.
|
|
258
|
-
# <em>Some arguments require the +LIST-EXTENDED+ or +IMAP4rev2+ capability.</em>
|
|
259
|
-
# <em>Requires either the +ENABLE+ or +IMAP4rev2+ capability.</em>
|
|
260
|
-
# <em>Requires either the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
|
|
261
|
-
# <em>Requires either the +IDLE+ or +IMAP4rev2+ capability.</em>
|
|
262
|
-
# <em>Requires either the +UNSELECT+ or +IMAP4rev2+ capability.</em>
|
|
263
|
-
# <em>Requires either the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
|
|
264
|
-
# <em>Requires either the +MOVE+ or +IMAP4rev2+ capability.</em>
|
|
265
|
-
#++
|
|
266
|
-
#
|
|
267
|
-
# ==== \IMAP commands for any state
|
|
309
|
+
# <em>*NOTE:* Net::IMAP doesn't support IMAP4rev2 yet.</em>
|
|
310
|
+
#
|
|
311
|
+
# ==== Any state
|
|
268
312
|
#
|
|
269
313
|
# - #capability: Returns the server's capabilities as an array of strings.
|
|
270
314
|
#
|
|
271
|
-
# <em>
|
|
272
|
-
#
|
|
315
|
+
# <em>In general, #capable? should be used rather than explicitly sending a
|
|
316
|
+
# +CAPABILITY+ command to the server.</em>
|
|
273
317
|
# - #noop: Allows the server to send unsolicited untagged #responses.
|
|
274
|
-
# - #logout: Tells the server to end the session. Enters the
|
|
318
|
+
# - #logout: Tells the server to end the session. Enters the "_logout_" state.
|
|
275
319
|
#
|
|
276
|
-
# ====
|
|
320
|
+
# ==== Not Authenticated state
|
|
277
321
|
#
|
|
278
322
|
# In addition to the commands for any state, the following commands are valid
|
|
279
|
-
# in the
|
|
323
|
+
# in the "<em>not authenticated</em>" state:
|
|
280
324
|
#
|
|
281
325
|
# - #starttls: Upgrades a clear-text connection to use TLS.
|
|
282
326
|
#
|
|
283
327
|
# <em>Requires the +STARTTLS+ capability.</em>
|
|
284
|
-
# - #authenticate: Identifies the client to the server using the given
|
|
285
|
-
# mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
|
286
|
-
# and credentials. Enters the
|
|
328
|
+
# - #authenticate: Identifies the client to the server using the given
|
|
329
|
+
# {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
|
330
|
+
# and credentials. Enters the "_authenticated_" state.
|
|
287
331
|
#
|
|
288
|
-
# <em>
|
|
289
|
-
#
|
|
332
|
+
# <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
|
|
333
|
+
# supported mechanisms.</em>
|
|
290
334
|
# - #login: Identifies the client to the server using a plain text password.
|
|
291
|
-
# Using #authenticate is preferred. Enters the
|
|
335
|
+
# Using #authenticate is generally preferred. Enters the "_authenticated_"
|
|
336
|
+
# state.
|
|
292
337
|
#
|
|
293
338
|
# <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
|
|
294
339
|
#
|
|
295
|
-
# ====
|
|
340
|
+
# ==== Authenticated state
|
|
296
341
|
#
|
|
297
342
|
# In addition to the commands for any state, the following commands are valid
|
|
298
|
-
# in the
|
|
343
|
+
# in the "_authenticated_" state:
|
|
299
344
|
#
|
|
300
|
-
|
|
301
|
-
#
|
|
302
|
-
#
|
|
303
|
-
#
|
|
304
|
-
#++
|
|
305
|
-
# - #select: Open a mailbox and enter the +selected+ state.
|
|
306
|
-
# - #examine: Open a mailbox read-only, and enter the +selected+ state.
|
|
345
|
+
# - #enable: Enables backwards incompatible server extensions.
|
|
346
|
+
# <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
|
|
347
|
+
# - #select: Open a mailbox and enter the "_selected_" state.
|
|
348
|
+
# - #examine: Open a mailbox read-only, and enter the "_selected_" state.
|
|
307
349
|
# - #create: Creates a new mailbox.
|
|
308
350
|
# - #delete: Permanently remove a mailbox.
|
|
309
351
|
# - #rename: Change the name of a mailbox.
|
|
@@ -311,37 +353,31 @@ module Net
|
|
|
311
353
|
# - #unsubscribe: Removes a mailbox from the "subscribed" set.
|
|
312
354
|
# - #list: Returns names and attributes of mailboxes matching a given pattern.
|
|
313
355
|
# - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
|
|
314
|
-
#
|
|
315
|
-
# <em>Requires the +NAMESPACE+ capability.</em>
|
|
356
|
+
# <em>Requires the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
|
|
316
357
|
# - #status: Returns mailbox information, e.g. message count, unseen message
|
|
317
358
|
# count, +UIDVALIDITY+ and +UIDNEXT+.
|
|
318
359
|
# - #append: Appends a message to the end of a mailbox.
|
|
319
360
|
# - #idle: Allows the server to send updates to the client, without the client
|
|
320
361
|
# needing to poll using #noop.
|
|
362
|
+
# <em>Requires the +IDLE+ or +IMAP4rev2+ capability.</em>
|
|
363
|
+
# - *Obsolete* #lsub: <em>Replaced by <tt>LIST-EXTENDED</tt> and removed from
|
|
364
|
+
# +IMAP4rev2+.</em> Lists mailboxes in the "subscribed" set.
|
|
321
365
|
#
|
|
322
|
-
# <em
|
|
323
|
-
# - #lsub: Lists mailboxes the user has declared "active" or "subscribed".
|
|
324
|
-
#--
|
|
325
|
-
# <em>Replaced by</em> <tt>LIST-EXTENDED</tt> <em>and removed from</em>
|
|
326
|
-
# +IMAP4rev2+. <em>However, Net::IMAP hasn't implemented</em>
|
|
327
|
-
# <tt>LIST-EXTENDED</tt> _yet_.
|
|
328
|
-
#++
|
|
366
|
+
# <em>*Note:* Net::IMAP hasn't implemented <tt>LIST-EXTENDED</tt> yet.</em>
|
|
329
367
|
#
|
|
330
|
-
# ====
|
|
368
|
+
# ==== Selected state
|
|
331
369
|
#
|
|
332
|
-
# In addition to the commands for any state and the
|
|
333
|
-
# commands, the following commands are valid in the
|
|
370
|
+
# In addition to the commands for any state and the "_authenticated_"
|
|
371
|
+
# commands, the following commands are valid in the "_selected_" state:
|
|
334
372
|
#
|
|
335
|
-
# - #close: Closes the mailbox and returns to the
|
|
373
|
+
# - #close: Closes the mailbox and returns to the "_authenticated_" state,
|
|
336
374
|
# expunging deleted messages, unless the mailbox was opened as read-only.
|
|
337
|
-
# - #unselect: Closes the mailbox and returns to the
|
|
375
|
+
# - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
|
|
338
376
|
# without expunging any messages.
|
|
339
|
-
#
|
|
340
|
-
# <em>Requires the +UNSELECT+ capability.</em>
|
|
377
|
+
# <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
|
|
341
378
|
# - #expunge: Permanently removes messages which have the Deleted flag set.
|
|
342
|
-
# - #uid_expunge: Restricts
|
|
343
|
-
#
|
|
344
|
-
# <em>Requires the +UIDPLUS+ capability.</em>
|
|
379
|
+
# - #uid_expunge: Restricts expunge to only remove the specified UIDs.
|
|
380
|
+
# <em>Requires the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
|
|
345
381
|
# - #search, #uid_search: Returns sequence numbers or UIDs of messages that
|
|
346
382
|
# match the given searching criteria.
|
|
347
383
|
# - #fetch, #uid_fetch: Returns data associated with a set of messages,
|
|
@@ -351,45 +387,35 @@ module Net
|
|
|
351
387
|
# specified destination mailbox.
|
|
352
388
|
# - #move, #uid_move: Moves the specified messages to the end of the
|
|
353
389
|
# specified destination mailbox, expunging them from the current mailbox.
|
|
390
|
+
# <em>Requires the +MOVE+ or +IMAP4rev2+ capability.</em>
|
|
391
|
+
# - #check: <em>*Obsolete:* removed from +IMAP4rev2+.</em>
|
|
392
|
+
# Can be replaced with #noop or #idle.
|
|
354
393
|
#
|
|
355
|
-
#
|
|
356
|
-
# - #check: Mostly obsolete. Can be replaced with #noop or #idle.
|
|
357
|
-
#--
|
|
358
|
-
# <em>Removed from IMAP4rev2.</em>
|
|
359
|
-
#++
|
|
394
|
+
# ==== Logout state
|
|
360
395
|
#
|
|
361
|
-
#
|
|
362
|
-
#
|
|
363
|
-
# No \IMAP commands are valid in the +logout+ state. If the socket is still
|
|
396
|
+
# No \IMAP commands are valid in the "_logout_" state. If the socket is still
|
|
364
397
|
# open, Net::IMAP will close it after receiving server confirmation.
|
|
365
398
|
# Exceptions will be raised by \IMAP commands that have already started and
|
|
366
399
|
# are waiting for a response, as well as any that are called after logout.
|
|
367
400
|
#
|
|
368
|
-
# ===
|
|
401
|
+
# === \IMAP extension support
|
|
369
402
|
#
|
|
370
403
|
# ==== RFC9051: +IMAP4rev2+
|
|
371
404
|
#
|
|
372
|
-
# Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is
|
|
373
|
-
# yet
|
|
374
|
-
#
|
|
375
|
-
|
|
376
|
-
#
|
|
377
|
-
#
|
|
378
|
-
#
|
|
379
|
-
#
|
|
380
|
-
#
|
|
381
|
-
#
|
|
382
|
-
#
|
|
383
|
-
#
|
|
384
|
-
#
|
|
385
|
-
#
|
|
386
|
-
# implicitly supported, but we can do better: Response codes: RFC5530, etc
|
|
387
|
-
# implicitly supported, but we can do better: <tt>STATUS=SIZE</tt>
|
|
388
|
-
# implicitly supported, but we can do better: <tt>STATUS DELETED</tt>
|
|
389
|
-
#++
|
|
390
|
-
# Commands for these extensions are included with the {Core IMAP
|
|
391
|
-
# commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above. Other supported
|
|
392
|
-
# extensons are listed below.
|
|
405
|
+
# Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is not supported
|
|
406
|
+
# yet, Net::IMAP supports several extensions that have been folded into it:
|
|
407
|
+
# +ENABLE+, +IDLE+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, and +UNSELECT+.
|
|
408
|
+
# Commands for these extensions are listed with the {Core IMAP
|
|
409
|
+
# commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above.
|
|
410
|
+
#
|
|
411
|
+
# >>>
|
|
412
|
+
# <em>The following are folded into +IMAP4rev2+ but are currently
|
|
413
|
+
# unsupported or incompletely supported by</em> Net::IMAP<em>: RFC4466
|
|
414
|
+
# extensions, +ESEARCH+, +SEARCHRES+, +LIST-EXTENDED+,
|
|
415
|
+
# +LIST-STATUS+, +LITERAL-+, +BINARY+ fetch, and +SPECIAL-USE+. The
|
|
416
|
+
# following extensions are implicitly supported, but will be updated with
|
|
417
|
+
# more direct support: RFC5530 response codes, <tt>STATUS=SIZE</tt>, and
|
|
418
|
+
# <tt>STATUS=DELETED</tt>.</em>
|
|
393
419
|
#
|
|
394
420
|
# ==== RFC2087: +QUOTA+
|
|
395
421
|
# - #getquota: returns the resource usage and limits for a quota root
|
|
@@ -398,92 +424,47 @@ module Net
|
|
|
398
424
|
# - #setquota: sets the resource limits for a given quota root.
|
|
399
425
|
#
|
|
400
426
|
# ==== RFC2177: +IDLE+
|
|
401
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]
|
|
402
|
-
#
|
|
427
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
|
428
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
|
403
429
|
# - #idle: Allows the server to send updates to the client, without the client
|
|
404
430
|
# needing to poll using #noop.
|
|
405
431
|
#
|
|
406
432
|
# ==== RFC2342: +NAMESPACE+
|
|
407
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]
|
|
408
|
-
#
|
|
433
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
|
434
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
|
409
435
|
# - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
|
|
410
436
|
#
|
|
411
437
|
# ==== RFC2971: +ID+
|
|
412
438
|
# - #id: exchanges client and server implementation information.
|
|
413
439
|
#
|
|
414
|
-
#--
|
|
415
|
-
# ==== RFC3502: +MULTIAPPEND+
|
|
416
|
-
# TODO...
|
|
417
|
-
#++
|
|
418
|
-
#
|
|
419
|
-
#--
|
|
420
|
-
# ==== RFC3516: +BINARY+
|
|
421
|
-
# TODO...
|
|
422
|
-
#++
|
|
423
|
-
#
|
|
424
440
|
# ==== RFC3691: +UNSELECT+
|
|
425
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]
|
|
426
|
-
#
|
|
427
|
-
# - #unselect: Closes the mailbox and returns to the
|
|
441
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
|
442
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
|
443
|
+
# - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
|
|
428
444
|
# without expunging any messages.
|
|
429
445
|
#
|
|
430
446
|
# ==== RFC4314: +ACL+
|
|
431
447
|
# - #getacl: lists the authenticated user's access rights to a mailbox.
|
|
432
448
|
# - #setacl: sets the access rights for a user on a mailbox
|
|
433
|
-
|
|
434
|
-
#
|
|
435
|
-
#++
|
|
436
|
-
# - *_Note:_* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
|
|
449
|
+
# >>>
|
|
450
|
+
# *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
|
|
437
451
|
#
|
|
438
452
|
# ==== RFC4315: +UIDPLUS+
|
|
439
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]
|
|
440
|
-
#
|
|
453
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
|
454
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
|
441
455
|
# - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
|
|
442
456
|
# - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode
|
|
443
457
|
# - Updates #append with the +APPENDUID+ ResponseCode
|
|
444
458
|
# - Updates #copy, #move with the +COPYUID+ ResponseCode
|
|
445
459
|
#
|
|
446
|
-
#--
|
|
447
|
-
# ==== RFC4466: Collected Extensions to IMAP4 ABNF
|
|
448
|
-
# TODO...
|
|
449
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this RFC updates
|
|
450
|
-
# the protocol to enable new optional parameters to many commands: #select,
|
|
451
|
-
# #examine, #create, #rename, #fetch, #uid_fetch, #store, #uid_store, #search,
|
|
452
|
-
# #uid_search, and #append. However, specific parameters are not defined.
|
|
453
|
-
# Extensions to these commands use this syntax whenever possible. Net::IMAP
|
|
454
|
-
# may be partially compatible with extensions to these commands, even without
|
|
455
|
-
# any explicit support.
|
|
456
|
-
#++
|
|
457
|
-
#
|
|
458
|
-
#--
|
|
459
|
-
# ==== RFC4731 +ESEARCH+
|
|
460
|
-
# TODO...
|
|
461
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
|
|
462
|
-
# - Updates #search, #uid_search to accept result options: +MIN+, +MAX+,
|
|
463
|
-
# +ALL+, +COUNT+, and to return ExtendedSearchData.
|
|
464
|
-
#++
|
|
465
|
-
#
|
|
466
|
-
#--
|
|
467
460
|
# ==== RFC4959: +SASL-IR+
|
|
468
|
-
# TODO...
|
|
469
461
|
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
|
|
470
|
-
# - Updates #authenticate to
|
|
471
|
-
#++
|
|
462
|
+
# - Updates #authenticate with the option to send an initial response.
|
|
472
463
|
#
|
|
473
|
-
|
|
474
|
-
#
|
|
475
|
-
#
|
|
476
|
-
|
|
477
|
-
#
|
|
478
|
-
#--
|
|
479
|
-
# ==== RFC5182 +SEARCHRES+
|
|
480
|
-
# TODO...
|
|
481
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
|
|
482
|
-
# - Updates #search, #uid_search with the +SAVE+ result option.
|
|
483
|
-
# - Updates #copy, #uid_copy, #fetch, #uid_fetch, #move, #uid_move, #search,
|
|
484
|
-
# #uid_search, #store, #uid_store, and #uid_expunge with ability to
|
|
485
|
-
# reference the saved result of a previous #search or #uid_search command.
|
|
486
|
-
#++
|
|
464
|
+
# ==== RFC5161: +ENABLE+
|
|
465
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
|
466
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
|
467
|
+
# - #enable: Enables backwards incompatible server extensions.
|
|
487
468
|
#
|
|
488
469
|
# ==== RFC5256: +SORT+
|
|
489
470
|
# - #sort, #uid_sort: An alternate version of #search or #uid_search which
|
|
@@ -493,75 +474,20 @@ module Net
|
|
|
493
474
|
# which arranges the results into ordered groups or threads according to a
|
|
494
475
|
# chosen algorithm.
|
|
495
476
|
#
|
|
496
|
-
#--
|
|
497
|
-
# ==== RFC5258 +LIST-EXTENDED+
|
|
498
|
-
# TODO...
|
|
499
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this updates the
|
|
500
|
-
# protocol with new optional parameters to the #list command, adding a few of
|
|
501
|
-
# its own. Net::IMAP may be forward-compatible with future #list extensions,
|
|
502
|
-
# even without any explicit support.
|
|
503
|
-
# - Updates #list to accept selection options: +SUBSCRIBED+, +REMOTE+, and
|
|
504
|
-
# +RECURSIVEMATCH+, and return options: +SUBSCRIBED+ and +CHILDREN+.
|
|
505
|
-
#++
|
|
506
|
-
#
|
|
507
|
-
#--
|
|
508
|
-
# ==== RFC5819 +LIST-STATUS+
|
|
509
|
-
# TODO...
|
|
510
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
|
|
511
|
-
# - Updates #list with +STATUS+ return option.
|
|
512
|
-
#++
|
|
513
|
-
#
|
|
514
477
|
# ==== +XLIST+ (non-standard, deprecated)
|
|
515
478
|
# - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses.
|
|
516
479
|
#
|
|
517
|
-
#--
|
|
518
|
-
# ==== RFC6154 +SPECIAL-USE+
|
|
519
|
-
# TODO...
|
|
520
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
|
|
521
|
-
# - Updates #list with the +SPECIAL-USE+ selection and return options.
|
|
522
|
-
#++
|
|
523
|
-
#
|
|
524
480
|
# ==== RFC6851: +MOVE+
|
|
525
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]
|
|
526
|
-
#
|
|
481
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
|
482
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
|
527
483
|
# - #move, #uid_move: Moves the specified messages to the end of the
|
|
528
484
|
# specified destination mailbox, expunging them from the current mailbox.
|
|
529
485
|
#
|
|
530
|
-
|
|
531
|
-
# ==== RFC6855: UTF8=ACCEPT
|
|
532
|
-
# TODO...
|
|
533
|
-
# ==== RFC6855: UTF8=ONLY
|
|
534
|
-
# TODO...
|
|
535
|
-
#++
|
|
536
|
-
#
|
|
537
|
-
#--
|
|
538
|
-
# ==== RFC7888: <tt>LITERAL+</tt>, +LITERAL-+
|
|
539
|
-
# TODO...
|
|
540
|
-
# ==== RFC7162: +QRESYNC+
|
|
541
|
-
# TODO...
|
|
542
|
-
# ==== RFC7162: +CONDSTORE+
|
|
543
|
-
# TODO...
|
|
544
|
-
# ==== RFC8474: +OBJECTID+
|
|
545
|
-
# TODO...
|
|
546
|
-
# ==== RFC9208: +QUOTA+
|
|
547
|
-
# TODO...
|
|
548
|
-
#++
|
|
549
|
-
#
|
|
550
|
-
# === Handling server responses
|
|
551
|
-
#
|
|
552
|
-
# - #greeting: The server's initial untagged response, which can indicate a
|
|
553
|
-
# pre-authenticated connection.
|
|
554
|
-
# - #responses: A hash with arrays of unhandled <em>non-+nil+</em>
|
|
555
|
-
# UntaggedResponse and ResponseCode +#data+, keyed by +#name+.
|
|
556
|
-
# - #add_response_handler: Add a block to be called inside the receiver thread
|
|
557
|
-
# with every server response.
|
|
558
|
-
# - #remove_response_handler: Remove a previously added response handler.
|
|
486
|
+
# ==== RFC6855: <tt>UTF8=ACCEPT</tt>, <tt>UTF8=ONLY</tt>
|
|
559
487
|
#
|
|
488
|
+
# - See #enable for information about support for UTF-8 string encoding.
|
|
560
489
|
#
|
|
561
490
|
# == References
|
|
562
|
-
#--
|
|
563
|
-
# TODO: Consider moving references list to REFERENCES.md or REFERENCES.rdoc.
|
|
564
|
-
#++
|
|
565
491
|
#
|
|
566
492
|
# [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
|
|
567
493
|
# Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
|
|
@@ -662,27 +588,21 @@ module Net
|
|
|
662
588
|
# RFC 1864, DOI 10.17487/RFC1864, October 1995,
|
|
663
589
|
# <https://www.rfc-editor.org/info/rfc1864>.
|
|
664
590
|
#
|
|
665
|
-
|
|
666
|
-
#
|
|
667
|
-
#
|
|
668
|
-
#
|
|
669
|
-
#
|
|
670
|
-
# profile for Internet Message Access Protocol (IMAP)",
|
|
671
|
-
# RFC 3503, DOI 10.17487/RFC3503, March 2003,
|
|
672
|
-
# <https://www.rfc-editor.org/info/rfc3503>.
|
|
673
|
-
#++
|
|
591
|
+
# [RFC3503[https://tools.ietf.org/html/rfc3503]]::
|
|
592
|
+
# Melnikov, A., "Message Disposition Notification (MDN)
|
|
593
|
+
# profile for Internet Message Access Protocol (IMAP)",
|
|
594
|
+
# RFC 3503, DOI 10.17487/RFC3503, March 2003,
|
|
595
|
+
# <https://www.rfc-editor.org/info/rfc3503>.
|
|
674
596
|
#
|
|
675
|
-
# ===
|
|
597
|
+
# === \IMAP Extensions
|
|
676
598
|
#
|
|
677
|
-
# [QUOTA[https://tools.ietf.org/html/rfc2087]]::
|
|
678
|
-
# Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
|
|
679
|
-
# January 1997, <https://www.rfc-editor.org/info/rfc2087>.
|
|
680
|
-
#--
|
|
681
|
-
# TODO: test compatibility with updated QUOTA extension:
|
|
682
599
|
# [QUOTA[https://tools.ietf.org/html/rfc9208]]::
|
|
683
600
|
# Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
|
|
684
601
|
# March 2022, <https://www.rfc-editor.org/info/rfc9208>.
|
|
685
|
-
|
|
602
|
+
#
|
|
603
|
+
# <em>Note: obsoletes</em>
|
|
604
|
+
# RFC-2087[https://tools.ietf.org/html/rfc2087]<em> (January 1997)</em>.
|
|
605
|
+
# <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
|
|
686
606
|
# [IDLE[https://tools.ietf.org/html/rfc2177]]::
|
|
687
607
|
# Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
|
|
688
608
|
# June 1997, <https://www.rfc-editor.org/info/rfc2177>.
|
|
@@ -715,33 +635,43 @@ module Net
|
|
|
715
635
|
# Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
|
|
716
636
|
# (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
|
|
717
637
|
# <https://www.rfc-editor.org/info/rfc6851>.
|
|
638
|
+
# [UTF8=ACCEPT[https://tools.ietf.org/html/rfc6855]]::
|
|
639
|
+
# [UTF8=ONLY[https://tools.ietf.org/html/rfc6855]]::
|
|
640
|
+
# Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed.,
|
|
641
|
+
# "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013,
|
|
642
|
+
# <https://www.rfc-editor.org/info/rfc6855>.
|
|
718
643
|
#
|
|
719
644
|
# === IANA registries
|
|
720
|
-
#
|
|
721
645
|
# * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
|
|
722
646
|
# * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
|
|
723
647
|
# * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
|
|
724
648
|
# * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
|
|
725
649
|
# * {IMAP Threading Algorithms}[https://www.iana.org/assignments/imap-threading-algorithms/imap-threading-algorithms.xhtml]
|
|
726
|
-
#--
|
|
727
|
-
# * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
|
|
728
|
-
# * [{LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
|
|
729
|
-
# * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
|
|
730
|
-
# * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
|
|
731
|
-
# * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
|
|
732
|
-
# * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
|
|
733
|
-
#++
|
|
734
650
|
# * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
|
735
651
|
# * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]:
|
|
736
652
|
# +imap+: tcp/143, +imaps+: tcp/993
|
|
737
653
|
# * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
|
|
738
654
|
# +imap+
|
|
739
655
|
# * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
|
|
656
|
+
# ===== For currently unsupported features:
|
|
657
|
+
# * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
|
|
658
|
+
# * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
|
|
659
|
+
# * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
|
|
660
|
+
# * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
|
|
661
|
+
# * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
|
|
662
|
+
# * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
|
|
740
663
|
#
|
|
741
664
|
class IMAP < Protocol
|
|
742
|
-
VERSION = "0.
|
|
665
|
+
VERSION = "0.4.0"
|
|
743
666
|
|
|
744
|
-
|
|
667
|
+
# Aliases for supported capabilities, to be used with the #enable command.
|
|
668
|
+
ENABLE_ALIASES = {
|
|
669
|
+
utf8: "UTF8=ACCEPT",
|
|
670
|
+
"UTF8=ONLY" => "UTF8=ACCEPT",
|
|
671
|
+
}.freeze
|
|
672
|
+
|
|
673
|
+
autoload :SASL, File.expand_path("imap/sasl", __dir__)
|
|
674
|
+
autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
|
|
745
675
|
|
|
746
676
|
include MonitorMixin
|
|
747
677
|
if defined?(OpenSSL::SSL)
|
|
@@ -749,69 +679,6 @@ module Net
|
|
|
749
679
|
include SSL
|
|
750
680
|
end
|
|
751
681
|
|
|
752
|
-
# Returns the initial greeting the server, an UntaggedResponse.
|
|
753
|
-
attr_reader :greeting
|
|
754
|
-
|
|
755
|
-
# Returns a hash with arrays of unhandled <em>non-+nil+</em>
|
|
756
|
-
# UntaggedResponse#data keyed by UntaggedResponse#name, and
|
|
757
|
-
# ResponseCode#data keyed by ResponseCode#name.
|
|
758
|
-
#
|
|
759
|
-
# For example:
|
|
760
|
-
#
|
|
761
|
-
# imap.select("inbox")
|
|
762
|
-
# p imap.responses["EXISTS"][-1]
|
|
763
|
-
# #=> 2
|
|
764
|
-
# p imap.responses["UIDVALIDITY"][-1]
|
|
765
|
-
# #=> 968263756
|
|
766
|
-
attr_reader :responses
|
|
767
|
-
|
|
768
|
-
# Returns all response handlers.
|
|
769
|
-
attr_reader :response_handlers
|
|
770
|
-
|
|
771
|
-
# Seconds to wait until a connection is opened.
|
|
772
|
-
# If the IMAP object cannot open a connection within this time,
|
|
773
|
-
# it raises a Net::OpenTimeout exception. The default value is 30 seconds.
|
|
774
|
-
attr_reader :open_timeout
|
|
775
|
-
|
|
776
|
-
# Seconds to wait until an IDLE response is received.
|
|
777
|
-
attr_reader :idle_response_timeout
|
|
778
|
-
|
|
779
|
-
# The maximum allowed server response size. When +nil+, there is no limit
|
|
780
|
-
# on response size.
|
|
781
|
-
#
|
|
782
|
-
# The default value is _unlimited_ (after +v0.5.8+, the default is 512 MiB).
|
|
783
|
-
# A _much_ lower value should be used with untrusted servers (for example,
|
|
784
|
-
# when connecting to a user-provided hostname). When using a lower limit,
|
|
785
|
-
# message bodies should be fetched in chunks rather than all at once.
|
|
786
|
-
#
|
|
787
|
-
# <em>Please Note:</em> this only limits the size per response. It does
|
|
788
|
-
# not prevent a flood of individual responses and it does not limit how
|
|
789
|
-
# many unhandled responses may be stored on the responses hash. See
|
|
790
|
-
# Net::IMAP@Unbounded+memory+use.
|
|
791
|
-
#
|
|
792
|
-
# Socket reads are limited to the maximum remaining bytes for the current
|
|
793
|
-
# response: max_response_size minus the bytes that have already been read.
|
|
794
|
-
# When the limit is reached, or reading a +literal+ _would_ go over the
|
|
795
|
-
# limit, ResponseTooLargeError is raised and the connection is closed.
|
|
796
|
-
# See also #socket_read_limit.
|
|
797
|
-
#
|
|
798
|
-
# Note that changes will not take effect immediately, because the receiver
|
|
799
|
-
# thread may already be waiting for the next response using the previous
|
|
800
|
-
# value. Net::IMAP#noop can force a response and enforce the new setting
|
|
801
|
-
# immediately.
|
|
802
|
-
#
|
|
803
|
-
# ==== Versioned Defaults
|
|
804
|
-
#
|
|
805
|
-
# Net::IMAP#max_response_size <em>was added in +v0.2.5+ and +v0.3.9+ as an
|
|
806
|
-
# attr_accessor, and in +v0.4.20+ and +v0.5.7+ as a delegator to a config
|
|
807
|
-
# attribute.</em>
|
|
808
|
-
#
|
|
809
|
-
# * original: +nil+ <em>(no limit)</em>
|
|
810
|
-
# * +0.5+: 512 MiB
|
|
811
|
-
attr_accessor :max_response_size
|
|
812
|
-
|
|
813
|
-
attr_accessor :client_thread # :nodoc:
|
|
814
|
-
|
|
815
682
|
# Returns the debug mode.
|
|
816
683
|
def self.debug
|
|
817
684
|
return @@debug
|
|
@@ -838,9 +705,175 @@ module Net
|
|
|
838
705
|
alias default_ssl_port default_tls_port
|
|
839
706
|
end
|
|
840
707
|
|
|
708
|
+
# Returns the initial greeting the server, an UntaggedResponse.
|
|
709
|
+
attr_reader :greeting
|
|
710
|
+
|
|
711
|
+
# Seconds to wait until a connection is opened.
|
|
712
|
+
# If the IMAP object cannot open a connection within this time,
|
|
713
|
+
# it raises a Net::OpenTimeout exception. The default value is 30 seconds.
|
|
714
|
+
attr_reader :open_timeout
|
|
715
|
+
|
|
716
|
+
# Seconds to wait until an IDLE response is received.
|
|
717
|
+
attr_reader :idle_response_timeout
|
|
718
|
+
|
|
719
|
+
# The hostname this client connected to
|
|
720
|
+
attr_reader :host
|
|
721
|
+
|
|
722
|
+
# The port this client connected to
|
|
723
|
+
attr_reader :port
|
|
724
|
+
|
|
725
|
+
# Returns the
|
|
726
|
+
# {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]
|
|
727
|
+
# used by the SSLSocket when TLS is attempted, even when the TLS handshake
|
|
728
|
+
# is unsuccessful. The context object will be frozen.
|
|
729
|
+
#
|
|
730
|
+
# Returns +nil+ for a plaintext connection.
|
|
731
|
+
attr_reader :ssl_ctx
|
|
732
|
+
|
|
733
|
+
# Returns the parameters that were sent to #ssl_ctx
|
|
734
|
+
# {set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]
|
|
735
|
+
# when the connection tries to use TLS (even when unsuccessful).
|
|
736
|
+
#
|
|
737
|
+
# Returns +false+ for a plaintext connection.
|
|
738
|
+
attr_reader :ssl_ctx_params
|
|
739
|
+
|
|
740
|
+
# Creates a new Net::IMAP object and connects it to the specified
|
|
741
|
+
# +host+.
|
|
742
|
+
#
|
|
743
|
+
# ==== Options
|
|
744
|
+
#
|
|
745
|
+
# Accepts the following options:
|
|
746
|
+
#
|
|
747
|
+
# [port]
|
|
748
|
+
# Port number. Defaults to 993 when +ssl+ is truthy, and 143 otherwise.
|
|
749
|
+
#
|
|
750
|
+
# [ssl]
|
|
751
|
+
# If +true+, the connection will use TLS with the default params set by
|
|
752
|
+
# {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params].
|
|
753
|
+
# If +ssl+ is a hash, it's passed to
|
|
754
|
+
# {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
|
|
755
|
+
# the keys are names of attribute assignment methods on
|
|
756
|
+
# SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
|
|
757
|
+
#
|
|
758
|
+
# [open_timeout]
|
|
759
|
+
# Seconds to wait until a connection is opened
|
|
760
|
+
# [idle_response_timeout]
|
|
761
|
+
# Seconds to wait until an IDLE response is received
|
|
762
|
+
#
|
|
763
|
+
# See DeprecatedClientOptions.new for deprecated arguments.
|
|
764
|
+
#
|
|
765
|
+
# ==== Examples
|
|
766
|
+
#
|
|
767
|
+
# Connect to cleartext port 143 at mail.example.com and recieve the server greeting:
|
|
768
|
+
# imap = Net::IMAP.new('mail.example.com', ssl: false) # => #<Net::IMAP:0x00007f79b0872bd0>
|
|
769
|
+
# imap.port => 143
|
|
770
|
+
# imap.tls_verified? => false
|
|
771
|
+
# imap.greeting => name: ("OK" | "PREAUTH") => status
|
|
772
|
+
# status # => "OK"
|
|
773
|
+
# # The client is connected in the "Not Authenticated" state.
|
|
774
|
+
#
|
|
775
|
+
# Connect with TLS to port 993
|
|
776
|
+
# imap = Net::IMAP.new('mail.example.com', ssl: true) # => #<Net::IMAP:0x00007f79b0872bd0>
|
|
777
|
+
# imap.port => 993
|
|
778
|
+
# imap.tls_verified? => true
|
|
779
|
+
# imap.greeting => name: (/OK/i | /PREAUTH/i) => status
|
|
780
|
+
# case status
|
|
781
|
+
# in /OK/i
|
|
782
|
+
# # The client is connected in the "Not Authenticated" state.
|
|
783
|
+
# imap.authenticate("PLAIN", "joe_user", "joes_password")
|
|
784
|
+
# in /PREAUTH/i
|
|
785
|
+
# # The client is connected in the "Authenticated" state.
|
|
786
|
+
# end
|
|
787
|
+
#
|
|
788
|
+
# Connect with prior authentication, for example using an SSL certificate:
|
|
789
|
+
# ssl_ctx_params = {
|
|
790
|
+
# cert: OpenSSL::X509::Certificate.new(File.read("client.crt")),
|
|
791
|
+
# key: OpenSSL::PKey::EC.new(File.read('client.key')),
|
|
792
|
+
# extra_chain_cert: [
|
|
793
|
+
# OpenSSL::X509::Certificate.new(File.read("intermediate.crt")),
|
|
794
|
+
# ],
|
|
795
|
+
# }
|
|
796
|
+
# imap = Net::IMAP.new('mail.example.com', ssl: ssl_ctx_params)
|
|
797
|
+
# imap.port => 993
|
|
798
|
+
# imap.tls_verified? => true
|
|
799
|
+
# imap.greeting => name: "PREAUTH"
|
|
800
|
+
# # The client is connected in the "Authenticated" state.
|
|
801
|
+
#
|
|
802
|
+
# ==== Exceptions
|
|
803
|
+
#
|
|
804
|
+
# The most common errors are:
|
|
805
|
+
#
|
|
806
|
+
# [Errno::ECONNREFUSED]
|
|
807
|
+
# Connection refused by +host+ or an intervening firewall.
|
|
808
|
+
# [Errno::ETIMEDOUT]
|
|
809
|
+
# Connection timed out (possibly due to packets being dropped by an
|
|
810
|
+
# intervening firewall).
|
|
811
|
+
# [Errno::ENETUNREACH]
|
|
812
|
+
# There is no route to that network.
|
|
813
|
+
# [SocketError]
|
|
814
|
+
# Hostname not known or other socket error.
|
|
815
|
+
# [Net::IMAP::ByeResponseError]
|
|
816
|
+
# Connected to the host successfully, but it immediately said goodbye.
|
|
817
|
+
#
|
|
818
|
+
def initialize(host, port: nil, ssl: nil,
|
|
819
|
+
open_timeout: 30, idle_response_timeout: 5)
|
|
820
|
+
super()
|
|
821
|
+
# Config options
|
|
822
|
+
@host = host
|
|
823
|
+
@port = port || (ssl ? SSL_PORT : PORT)
|
|
824
|
+
@open_timeout = Integer(open_timeout)
|
|
825
|
+
@idle_response_timeout = Integer(idle_response_timeout)
|
|
826
|
+
@ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
|
|
827
|
+
|
|
828
|
+
# Basic Client State
|
|
829
|
+
@utf8_strings = false
|
|
830
|
+
@debug_output_bol = true
|
|
831
|
+
@exception = nil
|
|
832
|
+
@greeting = nil
|
|
833
|
+
@capabilities = nil
|
|
834
|
+
|
|
835
|
+
# Client Protocol Reciever
|
|
836
|
+
@parser = ResponseParser.new
|
|
837
|
+
@responses = Hash.new {|h, k| h[k] = [] }
|
|
838
|
+
@response_handlers = []
|
|
839
|
+
@receiver_thread = nil
|
|
840
|
+
@receiver_thread_exception = nil
|
|
841
|
+
@receiver_thread_terminating = false
|
|
842
|
+
|
|
843
|
+
# Client Protocol Sender (including state for currently running commands)
|
|
844
|
+
@tag_prefix = "RUBY"
|
|
845
|
+
@tagno = 0
|
|
846
|
+
@tagged_responses = {}
|
|
847
|
+
@tagged_response_arrival = new_cond
|
|
848
|
+
@continued_command_tag = nil
|
|
849
|
+
@continuation_request_arrival = new_cond
|
|
850
|
+
@continuation_request_exception = nil
|
|
851
|
+
@idle_done_cond = nil
|
|
852
|
+
@logout_command_tag = nil
|
|
853
|
+
|
|
854
|
+
# Connection
|
|
855
|
+
@tls_verified = false
|
|
856
|
+
@sock = tcp_socket(@host, @port)
|
|
857
|
+
start_tls_session if ssl_ctx
|
|
858
|
+
start_imap_connection
|
|
859
|
+
|
|
860
|
+
# DEPRECATED: to remove in next version
|
|
861
|
+
@client_thread = Thread.current
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
# Returns true after the TLS negotiation has completed and the remote
|
|
865
|
+
# hostname has been verified. Returns false when TLS has been established
|
|
866
|
+
# but peer verification was disabled.
|
|
867
|
+
def tls_verified?; @tls_verified end
|
|
868
|
+
|
|
869
|
+
def client_thread # :nodoc:
|
|
870
|
+
warn "Net::IMAP#client_thread is deprecated and will be removed soon."
|
|
871
|
+
@client_thread
|
|
872
|
+
end
|
|
873
|
+
|
|
841
874
|
# Disconnects from the server.
|
|
842
875
|
#
|
|
843
|
-
# Related: #logout
|
|
876
|
+
# Related: #logout, #logout!
|
|
844
877
|
def disconnect
|
|
845
878
|
return if disconnected?
|
|
846
879
|
begin
|
|
@@ -870,62 +903,123 @@ module Net
|
|
|
870
903
|
return @sock.closed?
|
|
871
904
|
end
|
|
872
905
|
|
|
873
|
-
#
|
|
874
|
-
#
|
|
875
|
-
#
|
|
906
|
+
# Returns whether the server supports a given +capability+. When available,
|
|
907
|
+
# cached #capabilities are used without sending a new #capability command to
|
|
908
|
+
# the server.
|
|
876
909
|
#
|
|
877
|
-
#
|
|
878
|
-
#
|
|
879
|
-
# of all standard capabilities, and their reference RFCs.
|
|
910
|
+
# <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
|
|
911
|
+
# behaviour according to the server's advertised #capabilities.</em>
|
|
880
912
|
#
|
|
881
|
-
#
|
|
882
|
-
# <em>*Note* that Net::IMAP does not currently modify its
|
|
883
|
-
# behaviour according to the capabilities of the server;
|
|
884
|
-
# it is up to the user of the class to ensure that
|
|
885
|
-
# a certain capability is supported by a server before
|
|
886
|
-
# using it.</em>
|
|
913
|
+
# See Net::IMAP@Capabilities for more about \IMAP capabilities.
|
|
887
914
|
#
|
|
888
|
-
#
|
|
889
|
-
|
|
915
|
+
# Related: #auth_capable?, #capabilities, #capability, #enable
|
|
916
|
+
def capable?(capability) capabilities.include? capability.to_s.upcase end
|
|
917
|
+
alias capability? capable?
|
|
918
|
+
|
|
919
|
+
# Returns the server capabilities. When available, cached capabilities are
|
|
920
|
+
# used without sending a new #capability command to the server.
|
|
890
921
|
#
|
|
891
|
-
#
|
|
922
|
+
# To ensure a case-insensitive comparison, #capable? can be used instead.
|
|
892
923
|
#
|
|
893
|
-
#
|
|
894
|
-
#
|
|
895
|
-
# <tt>AUTH=PLAIN</tt>, and +LOGINDISABLED+ capabilities, and clients must
|
|
896
|
-
# respect their presence or absence. See the capabilites requirements on
|
|
897
|
-
# #starttls, #login, and #authenticate.
|
|
924
|
+
# <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
|
|
925
|
+
# behaviour according to the server's advertised #capabilities.</em>
|
|
898
926
|
#
|
|
899
|
-
#
|
|
927
|
+
# See Net::IMAP@Capabilities for more about \IMAP capabilities.
|
|
900
928
|
#
|
|
901
|
-
#
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
929
|
+
# Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable
|
|
930
|
+
def capabilities
|
|
931
|
+
@capabilities || capability
|
|
932
|
+
end
|
|
933
|
+
|
|
934
|
+
# Returns the #authenticate mechanisms that the server claims to support.
|
|
935
|
+
# These are derived from the #capabilities with an <tt>AUTH=</tt> prefix.
|
|
936
|
+
#
|
|
937
|
+
# This may be different when the connection is cleartext or using TLS. Most
|
|
938
|
+
# servers will drop all <tt>AUTH=</tt> mechanisms from #capabilities after
|
|
939
|
+
# the connection has authenticated.
|
|
940
|
+
#
|
|
941
|
+
# imap = Net::IMAP.new(hostname, ssl: false)
|
|
942
|
+
# imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"]
|
|
943
|
+
# imap.auth_mechanisms # => []
|
|
944
|
+
#
|
|
945
|
+
# imap.starttls
|
|
946
|
+
# imap.capabilities # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2",
|
|
947
|
+
# # "AUTH=OAUTHBEARER"]
|
|
948
|
+
# imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"]
|
|
949
|
+
#
|
|
950
|
+
# imap.authenticate("XOAUTH2", username, oauth2_access_token)
|
|
951
|
+
# imap.auth_mechanisms # => []
|
|
952
|
+
#
|
|
953
|
+
# Related: #authenticate, #auth_capable?, #capabilities
|
|
954
|
+
def auth_mechanisms
|
|
955
|
+
capabilities
|
|
956
|
+
.grep(/\AAUTH=/i)
|
|
957
|
+
.map { _1.delete_prefix("AUTH=") }
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
# Returns whether the server supports a given SASL +mechanism+ for use with
|
|
961
|
+
# the #authenticate command. The +mechanism+ is supported when
|
|
962
|
+
# #capabilities includes <tt>"AUTH=#{mechanism.to_s.upcase}"</tt>. When
|
|
963
|
+
# available, cached capabilities are used without sending a new #capability
|
|
964
|
+
# command to the server.
|
|
965
|
+
#
|
|
966
|
+
# imap.capable? "AUTH=PLAIN" # => true
|
|
967
|
+
# imap.auth_capable? "PLAIN" # => true
|
|
968
|
+
# imap.auth_capable? "blurdybloop" # => false
|
|
906
969
|
#
|
|
907
|
-
#
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
970
|
+
# Related: #authenticate, #auth_mechanisms, #capable?, #capabilities
|
|
971
|
+
def auth_capable?(mechanism)
|
|
972
|
+
capable? "AUTH=#{mechanism}"
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
# Returns whether capabilities have been cached. When true, #capable? and
|
|
976
|
+
# #capabilities don't require sending a #capability command to the server.
|
|
977
|
+
#
|
|
978
|
+
# See Net::IMAP@Capabilities for more about \IMAP capabilities.
|
|
912
979
|
#
|
|
913
|
-
#
|
|
980
|
+
# Related: #capable?, #capability, #clear_cached_capabilities
|
|
981
|
+
def capabilities_cached?
|
|
982
|
+
!!@capabilities
|
|
983
|
+
end
|
|
984
|
+
|
|
985
|
+
# Clears capabilities that have been remembered by the Net::IMAP client.
|
|
986
|
+
# This forces a #capability command to be sent the next time a #capabilities
|
|
987
|
+
# query method is called.
|
|
914
988
|
#
|
|
915
|
-
#
|
|
916
|
-
#
|
|
917
|
-
#
|
|
918
|
-
# #add_response_handler.
|
|
989
|
+
# Net::IMAP automatically discards its cached capabilities when they can
|
|
990
|
+
# change. Explicitly calling this _should_ be unnecessary for well-behaved
|
|
991
|
+
# servers.
|
|
919
992
|
#
|
|
920
|
-
#
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
993
|
+
# Related: #capable?, #capability, #capabilities_cached?
|
|
994
|
+
def clear_cached_capabilities
|
|
995
|
+
synchronize do
|
|
996
|
+
clear_responses("CAPABILITY")
|
|
997
|
+
@capabilities = nil
|
|
998
|
+
end
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
# Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
|
|
1002
|
+
# and returns an array of capabilities that are supported by the server.
|
|
1003
|
+
# The result is stored for use by #capable? and #capabilities.
|
|
924
1004
|
#
|
|
1005
|
+
# <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
|
|
1006
|
+
# behaviour according to the server's advertised #capabilities.</em>
|
|
1007
|
+
#
|
|
1008
|
+
# Net::IMAP automatically stores and discards capability data according to
|
|
1009
|
+
# the requirements and recommendations in
|
|
1010
|
+
# {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
|
|
1011
|
+
# {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
|
|
1012
|
+
# {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
|
|
1013
|
+
# Use #capable?, #auth_capable?, or #capabilities to this cache and avoid
|
|
1014
|
+
# sending the #capability command unnecessarily.
|
|
1015
|
+
#
|
|
1016
|
+
# See Net::IMAP@Capabilities for more about \IMAP capabilities.
|
|
1017
|
+
#
|
|
1018
|
+
# Related: #capable?, #auth_capable?, #capability, #enable
|
|
925
1019
|
def capability
|
|
926
1020
|
synchronize do
|
|
927
1021
|
send_command("CAPABILITY")
|
|
928
|
-
|
|
1022
|
+
@capabilities = clear_responses("CAPABILITY").last.freeze
|
|
929
1023
|
end
|
|
930
1024
|
end
|
|
931
1025
|
|
|
@@ -936,8 +1030,7 @@ module Net
|
|
|
936
1030
|
# Note that the user should first check if the server supports the ID
|
|
937
1031
|
# capability. For example:
|
|
938
1032
|
#
|
|
939
|
-
#
|
|
940
|
-
# if capabilities.include?("ID")
|
|
1033
|
+
# if capable?(:ID)
|
|
941
1034
|
# id = imap.id(
|
|
942
1035
|
# name: "my IMAP client (ruby)",
|
|
943
1036
|
# version: MyIMAP::VERSION,
|
|
@@ -955,7 +1048,7 @@ module Net
|
|
|
955
1048
|
def id(client_id=nil)
|
|
956
1049
|
synchronize do
|
|
957
1050
|
send_command("ID", ClientID.new(client_id))
|
|
958
|
-
|
|
1051
|
+
clear_responses("ID").last
|
|
959
1052
|
end
|
|
960
1053
|
end
|
|
961
1054
|
|
|
@@ -979,15 +1072,43 @@ module Net
|
|
|
979
1072
|
# to inform the command to inform the server that the client is done with
|
|
980
1073
|
# the connection.
|
|
981
1074
|
#
|
|
982
|
-
# Related: #disconnect
|
|
1075
|
+
# Related: #disconnect, #logout!
|
|
983
1076
|
def logout
|
|
984
1077
|
send_command("LOGOUT")
|
|
985
1078
|
end
|
|
986
1079
|
|
|
1080
|
+
# Calls #logout then, after receiving the TaggedResponse for the +LOGOUT+,
|
|
1081
|
+
# calls #disconnect. Returns the TaggedResponse from +LOGOUT+. Returns
|
|
1082
|
+
# +nil+ when the client is already disconnected, in contrast to #logout
|
|
1083
|
+
# which raises an exception.
|
|
1084
|
+
#
|
|
1085
|
+
# If #logout raises a StandardError, a warning will be printed but the
|
|
1086
|
+
# exception will not be re-raised.
|
|
1087
|
+
#
|
|
1088
|
+
# This is useful in situations where the connection must be dropped, for
|
|
1089
|
+
# example for security or after tests. If logout errors need to be handled,
|
|
1090
|
+
# use #logout and #disconnect instead.
|
|
1091
|
+
#
|
|
1092
|
+
# Related: #logout, #disconnect
|
|
1093
|
+
def logout!
|
|
1094
|
+
logout unless disconnected?
|
|
1095
|
+
rescue => ex
|
|
1096
|
+
warn "%s during <Net::IMAP %s:%s> logout!: %s" % [
|
|
1097
|
+
ex.class, host, port, ex
|
|
1098
|
+
]
|
|
1099
|
+
ensure
|
|
1100
|
+
disconnect
|
|
1101
|
+
end
|
|
1102
|
+
|
|
987
1103
|
# Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1]
|
|
988
1104
|
# to start a TLS session.
|
|
989
1105
|
#
|
|
990
|
-
# Any +options+ are forwarded to
|
|
1106
|
+
# Any +options+ are forwarded directly to
|
|
1107
|
+
# {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
|
|
1108
|
+
# the keys are names of attribute assignment methods on
|
|
1109
|
+
# SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
|
|
1110
|
+
#
|
|
1111
|
+
# See DeprecatedClientOptions#starttls for deprecated arguments.
|
|
991
1112
|
#
|
|
992
1113
|
# This method returns after TLS negotiation and hostname verification are
|
|
993
1114
|
# both successful. Any error indicates that the connection has not been
|
|
@@ -997,149 +1118,156 @@ module Net
|
|
|
997
1118
|
# >>>
|
|
998
1119
|
# Any #response_handlers added before STARTTLS should be aware that the
|
|
999
1120
|
# TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation.
|
|
1000
|
-
# TLS
|
|
1121
|
+
# TLS starts immediately _after_ that response. Any response code sent
|
|
1122
|
+
# with the response (e.g. CAPABILITY) is insecure and cannot be trusted.
|
|
1001
1123
|
#
|
|
1002
1124
|
# Related: Net::IMAP.new, #login, #authenticate
|
|
1003
1125
|
#
|
|
1004
1126
|
# ===== Capability
|
|
1005
|
-
#
|
|
1006
|
-
#
|
|
1127
|
+
# Clients should not call #starttls unless the server advertises the
|
|
1128
|
+
# +STARTTLS+ capability.
|
|
1007
1129
|
#
|
|
1008
1130
|
# Server capabilities may change after #starttls, #login, and #authenticate.
|
|
1009
|
-
# Cached capabilities
|
|
1010
|
-
#
|
|
1011
|
-
# The TaggedResponse to #starttls is sent clear-text, so the server <em>must
|
|
1012
|
-
# *not*</em> send capabilities in the #starttls response and clients <em>must
|
|
1013
|
-
# not</em> use them if they are sent. Servers will generally send an
|
|
1014
|
-
# unsolicited untagged response immeditely _after_ #starttls completes.
|
|
1131
|
+
# Cached #capabilities will be cleared when this method completes.
|
|
1015
1132
|
#
|
|
1016
|
-
def starttls(options
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
ok = send_command("STARTTLS") do |resp|
|
|
1133
|
+
def starttls(**options)
|
|
1134
|
+
@ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
|
|
1135
|
+
send_command("STARTTLS") do |resp|
|
|
1020
1136
|
if resp.kind_of?(TaggedResponse) && resp.name == "OK"
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
certs = options.to_str
|
|
1025
|
-
options = create_ssl_params(certs, verify)
|
|
1026
|
-
rescue NoMethodError
|
|
1027
|
-
end
|
|
1028
|
-
start_tls_session(options)
|
|
1137
|
+
clear_cached_capabilities
|
|
1138
|
+
clear_responses
|
|
1139
|
+
start_tls_session
|
|
1029
1140
|
end
|
|
1030
|
-
rescue Exception => error
|
|
1031
|
-
raise # note that the error backtrace is in the receiver_thread
|
|
1032
|
-
end
|
|
1033
|
-
if error
|
|
1034
|
-
disconnect
|
|
1035
|
-
raise error
|
|
1036
1141
|
end
|
|
1037
|
-
unless handled
|
|
1038
|
-
disconnect
|
|
1039
|
-
raise InvalidResponseError,
|
|
1040
|
-
"STARTTLS handler was bypassed, although server responded %p" % [
|
|
1041
|
-
ok.raw_data.chomp
|
|
1042
|
-
]
|
|
1043
|
-
end
|
|
1044
|
-
ok
|
|
1045
1142
|
end
|
|
1046
1143
|
|
|
1047
1144
|
# :call-seq:
|
|
1048
|
-
# authenticate(mechanism,
|
|
1049
|
-
# authenticate(mech, *creds, **props) {|prop, auth| val } -> ok_resp
|
|
1050
|
-
# authenticate(mechanism, authnid, credentials, authzid=nil) -> ok_resp
|
|
1051
|
-
# authenticate(mechanism, **properties) -> ok_resp
|
|
1052
|
-
# authenticate(mechanism) {|propname, authctx| prop_value } -> ok_resp
|
|
1145
|
+
# authenticate(mechanism, *, sasl_ir: true, **, &) -> ok_resp
|
|
1053
1146
|
#
|
|
1054
1147
|
# Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
|
|
1055
1148
|
# to authenticate the client. If successful, the connection enters the
|
|
1056
1149
|
# "_authenticated_" state.
|
|
1057
1150
|
#
|
|
1058
1151
|
# +mechanism+ is the name of the \SASL authentication mechanism to be used.
|
|
1059
|
-
# All other arguments are forwarded to the authenticator for the requested
|
|
1060
|
-
# mechanism. The listed call signatures are suggestions. <em>The
|
|
1061
|
-
# documentation for each individual mechanism must be consulted for its
|
|
1062
|
-
# specific parameters.</em>
|
|
1063
1152
|
#
|
|
1064
|
-
#
|
|
1153
|
+
# +sasl_ir+ allows or disallows sending an "initial response" (see the
|
|
1154
|
+
# +SASL-IR+ capability, below).
|
|
1065
1155
|
#
|
|
1066
|
-
#
|
|
1156
|
+
# All other arguments are forwarded to the registered SASL authenticator for
|
|
1157
|
+
# the requested mechanism. <em>The documentation for each individual
|
|
1158
|
+
# mechanism must be consulted for its specific parameters.</em>
|
|
1067
1159
|
#
|
|
1068
|
-
#
|
|
1160
|
+
# Related: #login, #starttls, #auth_capable?, #auth_mechanisms
|
|
1069
1161
|
#
|
|
1070
|
-
#
|
|
1071
|
-
# Login using clear-text username and password.
|
|
1162
|
+
# ==== Mechanisms
|
|
1072
1163
|
#
|
|
1073
|
-
#
|
|
1074
|
-
#
|
|
1075
|
-
# Non-standard and obsoleted by +OAUTHBEARER+, but widely
|
|
1076
|
-
# supported.
|
|
1164
|
+
# Each mechanism has different properties and requirements. Please consult
|
|
1165
|
+
# the documentation for the specific mechanisms you are using:
|
|
1077
1166
|
#
|
|
1078
|
-
#
|
|
1079
|
-
#
|
|
1080
|
-
#
|
|
1167
|
+
# +ANONYMOUS+::
|
|
1168
|
+
# See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
|
|
1169
|
+
#
|
|
1170
|
+
# Allows the user to gain access to public services or resources without
|
|
1171
|
+
# authenticating or disclosing an identity.
|
|
1172
|
+
#
|
|
1173
|
+
# +EXTERNAL+::
|
|
1174
|
+
# See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
|
|
1175
|
+
#
|
|
1176
|
+
# Authenticates using already established credentials, such as a TLS
|
|
1177
|
+
# certificate or IPsec.
|
|
1081
1178
|
#
|
|
1082
|
-
#
|
|
1179
|
+
# +OAUTHBEARER+::
|
|
1180
|
+
# See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator].
|
|
1083
1181
|
#
|
|
1084
|
-
#
|
|
1182
|
+
# Login using an OAuth2 Bearer token. This is the standard mechanism
|
|
1183
|
+
# for using OAuth2 with \SASL, but it is not yet deployed as widely as
|
|
1184
|
+
# +XOAUTH2+.
|
|
1085
1185
|
#
|
|
1086
|
-
#
|
|
1186
|
+
# +PLAIN+::
|
|
1187
|
+
# See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator].
|
|
1087
1188
|
#
|
|
1088
|
-
#
|
|
1189
|
+
# Login using clear-text username and password.
|
|
1089
1190
|
#
|
|
1090
|
-
#
|
|
1091
|
-
#
|
|
1191
|
+
# +SCRAM-SHA-1+::
|
|
1192
|
+
# +SCRAM-SHA-256+::
|
|
1193
|
+
# See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator].
|
|
1194
|
+
#
|
|
1195
|
+
# Login by username and password. The password is not sent to the
|
|
1196
|
+
# server but is used in a salted challenge/response exchange.
|
|
1197
|
+
# +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
|
|
1198
|
+
# Net::IMAP::SASL. New authenticators can easily be added for any other
|
|
1199
|
+
# <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
|
|
1200
|
+
# OpenSSL::Digest.
|
|
1201
|
+
#
|
|
1202
|
+
# +XOAUTH2+::
|
|
1203
|
+
# See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator].
|
|
1204
|
+
#
|
|
1205
|
+
# Login using a username and an OAuth2 access token. Non-standard and
|
|
1206
|
+
# obsoleted by +OAUTHBEARER+, but widely supported.
|
|
1207
|
+
#
|
|
1208
|
+
# See the {SASL mechanism
|
|
1092
1209
|
# registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
|
1093
|
-
# for
|
|
1210
|
+
# for a list of all SASL mechanisms and their specifications. To register
|
|
1211
|
+
# new authenticators, see Authenticators.
|
|
1094
1212
|
#
|
|
1095
|
-
# =====
|
|
1213
|
+
# ===== Deprecated mechanisms
|
|
1096
1214
|
#
|
|
1097
|
-
#
|
|
1098
|
-
#
|
|
1215
|
+
# <em>Obsolete mechanisms should be avoided, but are still available for
|
|
1216
|
+
# backwards compatibility. See</em> Net::IMAP::SASL@Deprecated+mechanisms.
|
|
1217
|
+
# <em>Using a deprecated mechanism will print a warning.</em>
|
|
1099
1218
|
#
|
|
1100
|
-
#
|
|
1101
|
-
#
|
|
1102
|
-
#
|
|
1103
|
-
#
|
|
1104
|
-
#
|
|
1105
|
-
#
|
|
1106
|
-
#
|
|
1107
|
-
#
|
|
1108
|
-
#
|
|
1109
|
-
#
|
|
1110
|
-
#
|
|
1111
|
-
# creds = {
|
|
1112
|
-
# authcid: username,
|
|
1113
|
-
# password: proc { password ||= ui.prompt_for_password },
|
|
1114
|
-
# oauth2_token: proc { accesstok ||= kms.fresh_access_token },
|
|
1115
|
-
# }
|
|
1116
|
-
# capa = imap.capability
|
|
1117
|
-
# if capa.include? "AUTH=OAUTHBEARER"
|
|
1118
|
-
# imap.authenticate "OAUTHBEARER", **creds # authcid, oauth2_token
|
|
1119
|
-
# elsif capa.include? "AUTH=XOAUTH2"
|
|
1120
|
-
# imap.authenticate "XOAUTH2", **creds # authcid, oauth2_token
|
|
1121
|
-
# elsif capa.include? "AUTH=SCRAM-SHA-256"
|
|
1122
|
-
# imap.authenticate "SCRAM-SHA-256", **creds # authcid, password
|
|
1123
|
-
# elsif capa.include? "AUTH=PLAIN"
|
|
1124
|
-
# imap.authenticate "PLAIN", **creds # authcid, password
|
|
1125
|
-
# elsif capa.include? "AUTH=DIGEST-MD5"
|
|
1126
|
-
# imap.authenticate "DIGEST-MD5", **creds # authcid, password
|
|
1127
|
-
# elsif capa.include? "LOGINDISABLED"
|
|
1128
|
-
# raise "the server has disabled login"
|
|
1129
|
-
# else
|
|
1219
|
+
# ==== Capabilities
|
|
1220
|
+
#
|
|
1221
|
+
# <tt>"AUTH=#{mechanism}"</tt> capabilities indicate server support for
|
|
1222
|
+
# mechanisms. Use #auth_capable? or #auth_mechanisms to check for support
|
|
1223
|
+
# before using a particular mechanism.
|
|
1224
|
+
#
|
|
1225
|
+
# if imap.auth_capable? "XOAUTH2"
|
|
1226
|
+
# imap.authenticate "XOAUTH2", username, oauth2_access_token
|
|
1227
|
+
# elsif imap.auth_capable? "PLAIN"
|
|
1228
|
+
# imap.authenticate "PLAIN", username, password
|
|
1229
|
+
# elsif !imap.capability? "LOGINDISABLED"
|
|
1130
1230
|
# imap.login username, password
|
|
1231
|
+
# else
|
|
1232
|
+
# raise "No acceptable authentication mechanism is available"
|
|
1131
1233
|
# end
|
|
1132
1234
|
#
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1235
|
+
# Although servers should list all supported \SASL mechanisms, they may
|
|
1236
|
+
# allow authentication with an unlisted +mechanism+.
|
|
1237
|
+
#
|
|
1238
|
+
# If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported
|
|
1239
|
+
# and the appropriate <tt>"AUTH=#{mechanism}"</tt> capability is present,
|
|
1240
|
+
# an "initial response" may be sent as an argument to the +AUTHENTICATE+
|
|
1241
|
+
# command, saving a round-trip. The SASL exchange allows for server
|
|
1242
|
+
# challenges and client responses, but many mechanisms expect the client to
|
|
1243
|
+
# "respond" first. The initial response will only be sent for
|
|
1244
|
+
# "client-first" mechanisms.
|
|
1245
|
+
#
|
|
1246
|
+
# Server capabilities may change after #starttls, #login, and #authenticate.
|
|
1247
|
+
# Previously cached #capabilities will be cleared when this method
|
|
1248
|
+
# completes. If the TaggedResponse to #authenticate includes updated
|
|
1249
|
+
# capabilities, they will be cached.
|
|
1250
|
+
def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback)
|
|
1251
|
+
mechanism = mechanism.to_s.tr("_", "-").upcase
|
|
1252
|
+
authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
|
|
1253
|
+
cmdargs = ["AUTHENTICATE", mechanism]
|
|
1254
|
+
if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
|
|
1255
|
+
authenticator.respond_to?(:initial_response?) &&
|
|
1256
|
+
authenticator.initial_response?
|
|
1257
|
+
response = authenticator.process(nil)
|
|
1258
|
+
cmdargs << (response.empty? ? "=" : [response].pack("m0"))
|
|
1259
|
+
end
|
|
1260
|
+
result = send_command_with_continuations(*cmdargs) {|data|
|
|
1261
|
+
challenge = data.unpack1("m")
|
|
1262
|
+
response = authenticator.process challenge
|
|
1263
|
+
[response].pack("m0")
|
|
1264
|
+
}
|
|
1265
|
+
if authenticator.respond_to?(:done?) && !authenticator.done?
|
|
1266
|
+
logout!
|
|
1267
|
+
raise SASL::AuthenticationIncomplete, result
|
|
1142
1268
|
end
|
|
1269
|
+
@capabilities = capabilities_from_resp_code result
|
|
1270
|
+
result
|
|
1143
1271
|
end
|
|
1144
1272
|
|
|
1145
1273
|
# Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
|
|
@@ -1147,16 +1275,25 @@ module Net
|
|
|
1147
1275
|
# this +user+. If successful, the connection enters the "_authenticated_"
|
|
1148
1276
|
# state.
|
|
1149
1277
|
#
|
|
1150
|
-
# Using #authenticate
|
|
1151
|
-
#
|
|
1278
|
+
# Using #authenticate {should be
|
|
1279
|
+
# preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command]
|
|
1280
|
+
# over #login. The LOGIN command is not the same as #authenticate with the
|
|
1281
|
+
# "LOGIN" +mechanism+.
|
|
1152
1282
|
#
|
|
1153
1283
|
# A Net::IMAP::NoResponseError is raised if authentication fails.
|
|
1154
1284
|
#
|
|
1155
1285
|
# Related: #authenticate, #starttls
|
|
1156
1286
|
#
|
|
1157
|
-
#
|
|
1158
|
-
#
|
|
1159
|
-
#
|
|
1287
|
+
# ===== Capabilities
|
|
1288
|
+
#
|
|
1289
|
+
# An IMAP client MUST NOT call #login when the server advertises the
|
|
1290
|
+
# +LOGINDISABLED+ capability.
|
|
1291
|
+
#
|
|
1292
|
+
# if imap.capability? "LOGINDISABLED"
|
|
1293
|
+
# raise "Remote server has disabled the login command"
|
|
1294
|
+
# else
|
|
1295
|
+
# imap.login username, password
|
|
1296
|
+
# end
|
|
1160
1297
|
#
|
|
1161
1298
|
# Server capabilities may change after #starttls, #login, and #authenticate.
|
|
1162
1299
|
# Cached capabilities _must_ be invalidated after this method completes.
|
|
@@ -1165,17 +1302,18 @@ module Net
|
|
|
1165
1302
|
#
|
|
1166
1303
|
def login(user, password)
|
|
1167
1304
|
send_command("LOGIN", user, password)
|
|
1305
|
+
.tap { @capabilities = capabilities_from_resp_code _1 }
|
|
1168
1306
|
end
|
|
1169
1307
|
|
|
1170
1308
|
# Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
|
|
1171
1309
|
# to select a +mailbox+ so that messages in the +mailbox+ can be accessed.
|
|
1172
1310
|
#
|
|
1173
1311
|
# After you have selected a mailbox, you may retrieve the number of items in
|
|
1174
|
-
# that mailbox from <tt>imap.responses
|
|
1175
|
-
# recent messages from <tt>imap.responses
|
|
1176
|
-
# these values can change if new messages arrive during a session
|
|
1177
|
-
# existing messages are expunged; see #add_response_handler for a
|
|
1178
|
-
# detect these events.
|
|
1312
|
+
# that mailbox from <tt>imap.responses("EXISTS", &:last)</tt>, and the
|
|
1313
|
+
# number of recent messages from <tt>imap.responses("RECENT", &:last)</tt>.
|
|
1314
|
+
# Note that these values can change if new messages arrive during a session
|
|
1315
|
+
# or when existing messages are expunged; see #add_response_handler for a
|
|
1316
|
+
# way to detect these events.
|
|
1179
1317
|
#
|
|
1180
1318
|
# A Net::IMAP::NoResponseError is raised if the mailbox does not
|
|
1181
1319
|
# exist or is for some reason non-selectable.
|
|
@@ -1306,7 +1444,7 @@ module Net
|
|
|
1306
1444
|
def list(refname, mailbox)
|
|
1307
1445
|
synchronize do
|
|
1308
1446
|
send_command("LIST", refname, mailbox)
|
|
1309
|
-
|
|
1447
|
+
clear_responses("LIST")
|
|
1310
1448
|
end
|
|
1311
1449
|
end
|
|
1312
1450
|
|
|
@@ -1344,8 +1482,7 @@ module Net
|
|
|
1344
1482
|
#
|
|
1345
1483
|
# ===== For example:
|
|
1346
1484
|
#
|
|
1347
|
-
#
|
|
1348
|
-
# if capabilities.include?("NAMESPACE")
|
|
1485
|
+
# if capable?("NAMESPACE")
|
|
1349
1486
|
# namespaces = imap.namespace
|
|
1350
1487
|
# if namespace = namespaces.personal.first
|
|
1351
1488
|
# prefix = namespace.prefix # e.g. "" or "INBOX."
|
|
@@ -1364,7 +1501,7 @@ module Net
|
|
|
1364
1501
|
def namespace
|
|
1365
1502
|
synchronize do
|
|
1366
1503
|
send_command("NAMESPACE")
|
|
1367
|
-
|
|
1504
|
+
clear_responses("NAMESPACE").last
|
|
1368
1505
|
end
|
|
1369
1506
|
end
|
|
1370
1507
|
|
|
@@ -1408,7 +1545,7 @@ module Net
|
|
|
1408
1545
|
def xlist(refname, mailbox)
|
|
1409
1546
|
synchronize do
|
|
1410
1547
|
send_command("XLIST", refname, mailbox)
|
|
1411
|
-
|
|
1548
|
+
clear_responses("XLIST")
|
|
1412
1549
|
end
|
|
1413
1550
|
end
|
|
1414
1551
|
|
|
@@ -1427,8 +1564,8 @@ module Net
|
|
|
1427
1564
|
synchronize do
|
|
1428
1565
|
send_command("GETQUOTAROOT", mailbox)
|
|
1429
1566
|
result = []
|
|
1430
|
-
result.concat(
|
|
1431
|
-
result.concat(
|
|
1567
|
+
result.concat(clear_responses("QUOTAROOT"))
|
|
1568
|
+
result.concat(clear_responses("QUOTA"))
|
|
1432
1569
|
return result
|
|
1433
1570
|
end
|
|
1434
1571
|
end
|
|
@@ -1447,7 +1584,7 @@ module Net
|
|
|
1447
1584
|
def getquota(mailbox)
|
|
1448
1585
|
synchronize do
|
|
1449
1586
|
send_command("GETQUOTA", mailbox)
|
|
1450
|
-
|
|
1587
|
+
clear_responses("QUOTA")
|
|
1451
1588
|
end
|
|
1452
1589
|
end
|
|
1453
1590
|
|
|
@@ -1503,7 +1640,7 @@ module Net
|
|
|
1503
1640
|
def getacl(mailbox)
|
|
1504
1641
|
synchronize do
|
|
1505
1642
|
send_command("GETACL", mailbox)
|
|
1506
|
-
|
|
1643
|
+
clear_responses("ACL").last
|
|
1507
1644
|
end
|
|
1508
1645
|
end
|
|
1509
1646
|
|
|
@@ -1518,7 +1655,7 @@ module Net
|
|
|
1518
1655
|
def lsub(refname, mailbox)
|
|
1519
1656
|
synchronize do
|
|
1520
1657
|
send_command("LSUB", refname, mailbox)
|
|
1521
|
-
|
|
1658
|
+
clear_responses("LSUB")
|
|
1522
1659
|
end
|
|
1523
1660
|
end
|
|
1524
1661
|
|
|
@@ -1542,7 +1679,7 @@ module Net
|
|
|
1542
1679
|
def status(mailbox, attr)
|
|
1543
1680
|
synchronize do
|
|
1544
1681
|
send_command("STATUS", mailbox, attr)
|
|
1545
|
-
|
|
1682
|
+
clear_responses("STATUS").last&.attr
|
|
1546
1683
|
end
|
|
1547
1684
|
end
|
|
1548
1685
|
|
|
@@ -1631,7 +1768,7 @@ module Net
|
|
|
1631
1768
|
def expunge
|
|
1632
1769
|
synchronize do
|
|
1633
1770
|
send_command("EXPUNGE")
|
|
1634
|
-
|
|
1771
|
+
clear_responses("EXPUNGE")
|
|
1635
1772
|
end
|
|
1636
1773
|
end
|
|
1637
1774
|
|
|
@@ -1663,7 +1800,7 @@ module Net
|
|
|
1663
1800
|
def uid_expunge(uid_set)
|
|
1664
1801
|
synchronize do
|
|
1665
1802
|
send_command("UID EXPUNGE", MessageSet.new(uid_set))
|
|
1666
|
-
|
|
1803
|
+
clear_responses("EXPUNGE")
|
|
1667
1804
|
end
|
|
1668
1805
|
end
|
|
1669
1806
|
|
|
@@ -1682,7 +1819,7 @@ module Net
|
|
|
1682
1819
|
# or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
|
|
1683
1820
|
# in addition to documentation for
|
|
1684
1821
|
# any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
|
|
1685
|
-
# reported by #
|
|
1822
|
+
# reported by #capabilities which may define additional search filters, e.g:
|
|
1686
1823
|
# +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
|
|
1687
1824
|
# +SAVEDATE+. The following are some common search criteria:
|
|
1688
1825
|
#
|
|
@@ -1977,6 +2114,87 @@ module Net
|
|
|
1977
2114
|
return thread_internal("UID THREAD", algorithm, search_keys, charset)
|
|
1978
2115
|
end
|
|
1979
2116
|
|
|
2117
|
+
# Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1]
|
|
2118
|
+
# {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1]
|
|
2119
|
+
# to enable the specified server +capabilities+. Each capability may be an
|
|
2120
|
+
# array, string, or symbol. Returns a list of the capabilities that were
|
|
2121
|
+
# enabled.
|
|
2122
|
+
#
|
|
2123
|
+
# The +ENABLE+ command is only valid in the _authenticated_ state, before
|
|
2124
|
+
# any mailbox is selected.
|
|
2125
|
+
#
|
|
2126
|
+
# Related: #capable?, #capabilities, #capability
|
|
2127
|
+
#
|
|
2128
|
+
# ===== Capabilities
|
|
2129
|
+
#
|
|
2130
|
+
# The server's capabilities must include
|
|
2131
|
+
# +ENABLE+ [RFC5161[https://tools.ietf.org/html/rfc5161]]
|
|
2132
|
+
# or +IMAP4REV2+ [RFC9051[https://tools.ietf.org/html/rfc9051]].
|
|
2133
|
+
#
|
|
2134
|
+
# Additionally, the server capabilities must include a capability matching
|
|
2135
|
+
# each enabled extension (usually the same name as the enabled extension).
|
|
2136
|
+
# The following capabilities may be enabled:
|
|
2137
|
+
#
|
|
2138
|
+
# [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
|
|
2139
|
+
#
|
|
2140
|
+
# In a future release, <tt>enable(:utf8)</tt> will enable either
|
|
2141
|
+
# <tt>"UTF8=ACCEPT"</tt> or <tt>"IMAP4rev2"</tt>, depending on server
|
|
2142
|
+
# capabilities.
|
|
2143
|
+
#
|
|
2144
|
+
# [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
|
|
2145
|
+
#
|
|
2146
|
+
# The server's capabilities must include <tt>UTF8=ACCEPT</tt> _or_
|
|
2147
|
+
# <tt>UTF8=ONLY</tt>.
|
|
2148
|
+
#
|
|
2149
|
+
# This allows the server to send strings encoded as UTF-8 which might
|
|
2150
|
+
# otherwise need to use a 7-bit encoding, such as {modified
|
|
2151
|
+
# UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for
|
|
2152
|
+
# message headers.
|
|
2153
|
+
#
|
|
2154
|
+
# *Note:* <em>A future update may set string encodings slightly
|
|
2155
|
+
# differently</em>, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8"
|
|
2156
|
+
# when it is. Currently, the encoding of strings sent as "quoted" or
|
|
2157
|
+
# "text" will _always_ be "UTF-8", even when only ASCII characters are
|
|
2158
|
+
# used (e.g. "Subject: Agenda") And currently, string "literals" sent
|
|
2159
|
+
# by the server will always have an "ASCII-8BIT" (binary)
|
|
2160
|
+
# encoding, even if they generally contain UTF-8 data, if they are
|
|
2161
|
+
# text at all.
|
|
2162
|
+
#
|
|
2163
|
+
# [<tt>"UTF8=ONLY"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
|
|
2164
|
+
#
|
|
2165
|
+
# A server that reports the <tt>UTF8=ONLY</tt> capability _requires_ that
|
|
2166
|
+
# the client <tt>enable("UTF8=ACCEPT")</tt> before any mailboxes may be
|
|
2167
|
+
# selected. For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
|
|
2168
|
+
# <tt>enable("UTF8=ACCEPT")</tt>.
|
|
2169
|
+
#
|
|
2170
|
+
# ===== Unsupported capabilities
|
|
2171
|
+
#
|
|
2172
|
+
# *Note:* Some extensions that use ENABLE permit the server to send syntax
|
|
2173
|
+
# that Net::IMAP cannot parse, which may raise an exception and disconnect.
|
|
2174
|
+
# Some extensions may work, but the support may be incomplete, untested, or
|
|
2175
|
+
# experimental.
|
|
2176
|
+
#
|
|
2177
|
+
# Until a capability is documented here as supported, enabling it may result
|
|
2178
|
+
# in undocumented behavior and a future release may update with incompatible
|
|
2179
|
+
# behavior <em>without warning or deprecation</em>.
|
|
2180
|
+
#
|
|
2181
|
+
# <em>Caution is advised.</em>
|
|
2182
|
+
#
|
|
2183
|
+
def enable(*capabilities)
|
|
2184
|
+
capabilities = capabilities
|
|
2185
|
+
.flatten
|
|
2186
|
+
.map {|e| ENABLE_ALIASES[e] || e }
|
|
2187
|
+
.uniq
|
|
2188
|
+
.join(' ')
|
|
2189
|
+
synchronize do
|
|
2190
|
+
send_command("ENABLE #{capabilities}")
|
|
2191
|
+
result = clear_responses("ENABLED").last
|
|
2192
|
+
@utf8_strings ||= result.include? "UTF8=ACCEPT"
|
|
2193
|
+
@utf8_strings ||= result.include? "IMAP4REV2"
|
|
2194
|
+
result
|
|
2195
|
+
end
|
|
2196
|
+
end
|
|
2197
|
+
|
|
1980
2198
|
# Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3]
|
|
1981
2199
|
# {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13]
|
|
1982
2200
|
# that waits for notifications of new or expunged messages. Yields
|
|
@@ -2041,6 +2259,104 @@ module Net
|
|
|
2041
2259
|
end
|
|
2042
2260
|
end
|
|
2043
2261
|
|
|
2262
|
+
# :call-seq:
|
|
2263
|
+
# responses {|hash| ...} -> block result
|
|
2264
|
+
# responses(type) {|array| ...} -> block result
|
|
2265
|
+
#
|
|
2266
|
+
# Yields unhandled responses and returns the result of the block.
|
|
2267
|
+
#
|
|
2268
|
+
# Unhandled responses are stored in a hash, with arrays of
|
|
2269
|
+
# <em>non-+nil+</em> UntaggedResponse#data keyed by UntaggedResponse#name
|
|
2270
|
+
# and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
|
|
2271
|
+
# yield the entire responses hash. Call with +type+ to yield only the array
|
|
2272
|
+
# of responses for that type.
|
|
2273
|
+
#
|
|
2274
|
+
# For example:
|
|
2275
|
+
#
|
|
2276
|
+
# imap.select("inbox")
|
|
2277
|
+
# p imap.responses("EXISTS", &:last)
|
|
2278
|
+
# #=> 2
|
|
2279
|
+
# p imap.responses("UIDVALIDITY", &:last)
|
|
2280
|
+
# #=> 968263756
|
|
2281
|
+
#
|
|
2282
|
+
# >>>
|
|
2283
|
+
# *Note:* Access to the responses hash is synchronized for thread-safety.
|
|
2284
|
+
# The receiver thread and response_handlers cannot process new responses
|
|
2285
|
+
# until the block completes. Accessing either the response hash or its
|
|
2286
|
+
# response type arrays outside of the block is unsafe.
|
|
2287
|
+
#
|
|
2288
|
+
# Calling without a block is unsafe and deprecated. Future releases will
|
|
2289
|
+
# raise ArgumentError unless a block is given.
|
|
2290
|
+
#
|
|
2291
|
+
# Previously unhandled responses are automatically cleared before entering a
|
|
2292
|
+
# mailbox with #select or #examine. Long-lived connections can receive many
|
|
2293
|
+
# unhandled server responses, which must be pruned or they will continually
|
|
2294
|
+
# consume more memory. Update or clear the responses hash or arrays inside
|
|
2295
|
+
# the block, or use #clear_responses.
|
|
2296
|
+
#
|
|
2297
|
+
# Only non-+nil+ data is stored. Many important response codes have no data
|
|
2298
|
+
# of their own, but are used as "tags" on the ResponseText object they are
|
|
2299
|
+
# attached to. ResponseText will be accessible by its response types:
|
|
2300
|
+
# "+OK+", "+NO+", "+BAD+", "+BYE+", or "+PREAUTH+".
|
|
2301
|
+
#
|
|
2302
|
+
# TaggedResponse#data is not saved to #responses, nor is any
|
|
2303
|
+
# ResponseCode#data on tagged responses. Although some command methods do
|
|
2304
|
+
# return the TaggedResponse directly, #add_response_handler must be used to
|
|
2305
|
+
# handle all response codes.
|
|
2306
|
+
#
|
|
2307
|
+
# Related: #clear_responses, #response_handlers, #greeting
|
|
2308
|
+
def responses(type = nil)
|
|
2309
|
+
if block_given?
|
|
2310
|
+
synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
|
|
2311
|
+
elsif type
|
|
2312
|
+
raise ArgumentError, "Pass a block or use #clear_responses"
|
|
2313
|
+
else
|
|
2314
|
+
# warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1)
|
|
2315
|
+
@responses
|
|
2316
|
+
end
|
|
2317
|
+
end
|
|
2318
|
+
|
|
2319
|
+
# :call-seq:
|
|
2320
|
+
# clear_responses -> hash
|
|
2321
|
+
# clear_responses(type) -> array
|
|
2322
|
+
#
|
|
2323
|
+
# Clears and returns the unhandled #responses hash or the unhandled
|
|
2324
|
+
# responses array for a single response +type+.
|
|
2325
|
+
#
|
|
2326
|
+
# Clearing responses is synchronized with other threads. The lock is
|
|
2327
|
+
# released before returning.
|
|
2328
|
+
#
|
|
2329
|
+
# Related: #responses, #response_handlers
|
|
2330
|
+
def clear_responses(type = nil)
|
|
2331
|
+
synchronize {
|
|
2332
|
+
if type
|
|
2333
|
+
@responses.delete(type) || []
|
|
2334
|
+
else
|
|
2335
|
+
@responses.dup.transform_values(&:freeze)
|
|
2336
|
+
.tap { _1.default = [].freeze }
|
|
2337
|
+
.tap { @responses.clear }
|
|
2338
|
+
end
|
|
2339
|
+
}
|
|
2340
|
+
.freeze
|
|
2341
|
+
end
|
|
2342
|
+
|
|
2343
|
+
# Returns all response handlers, including those that are added internally
|
|
2344
|
+
# by commands. Each response handler will be called with every new
|
|
2345
|
+
# UntaggedResponse, TaggedResponse, and ContinuationRequest.
|
|
2346
|
+
#
|
|
2347
|
+
# Response handlers are called with a mutex inside the receiver thread. New
|
|
2348
|
+
# responses cannot be processed and commands from other threads must wait
|
|
2349
|
+
# until all response_handlers return. An exception will shut-down the
|
|
2350
|
+
# receiver thread and close the connection.
|
|
2351
|
+
#
|
|
2352
|
+
# For thread-safety, the returned array is a frozen copy of the internal
|
|
2353
|
+
# array.
|
|
2354
|
+
#
|
|
2355
|
+
# Related: #add_response_handler, #remove_response_handler
|
|
2356
|
+
def response_handlers
|
|
2357
|
+
synchronize { @response_handlers.clone.freeze }
|
|
2358
|
+
end
|
|
2359
|
+
|
|
2044
2360
|
# Adds a response handler. For example, to detect when
|
|
2045
2361
|
# the server sends a new EXISTS response (which normally
|
|
2046
2362
|
# indicates new messages being added to the mailbox),
|
|
@@ -2053,19 +2369,21 @@ module Net
|
|
|
2053
2369
|
# end
|
|
2054
2370
|
# }
|
|
2055
2371
|
#
|
|
2056
|
-
# Response handlers can also be added when the client is created before the
|
|
2057
|
-
# receiver thread is started, by the +response_handlers+ argument to ::new.
|
|
2058
|
-
# This ensures every server response is handled, including the #greeting.
|
|
2059
|
-
#
|
|
2060
2372
|
# Related: #remove_response_handler, #response_handlers
|
|
2061
2373
|
def add_response_handler(handler = nil, &block)
|
|
2062
2374
|
raise ArgumentError, "two Procs are passed" if handler && block
|
|
2063
|
-
|
|
2375
|
+
synchronize do
|
|
2376
|
+
@response_handlers.push(block || handler)
|
|
2377
|
+
end
|
|
2064
2378
|
end
|
|
2065
2379
|
|
|
2066
2380
|
# Removes the response handler.
|
|
2381
|
+
#
|
|
2382
|
+
# Related: #add_response_handler, #response_handlers
|
|
2067
2383
|
def remove_response_handler(handler)
|
|
2068
|
-
|
|
2384
|
+
synchronize do
|
|
2385
|
+
@response_handlers.delete(handler)
|
|
2386
|
+
end
|
|
2069
2387
|
end
|
|
2070
2388
|
|
|
2071
2389
|
private
|
|
@@ -2076,104 +2394,29 @@ module Net
|
|
|
2076
2394
|
|
|
2077
2395
|
@@debug = false
|
|
2078
2396
|
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
# The available options are:
|
|
2088
|
-
#
|
|
2089
|
-
# port:: Port number (default value is 143 for imap, or 993 for imaps)
|
|
2090
|
-
# ssl:: If +options[:ssl]+ is true, then an attempt will be made
|
|
2091
|
-
# to use SSL (now TLS) to connect to the server.
|
|
2092
|
-
# If +options[:ssl]+ is a hash, it's passed to
|
|
2093
|
-
# OpenSSL::SSL::SSLContext#set_params as parameters.
|
|
2094
|
-
# open_timeout:: Seconds to wait until a connection is opened
|
|
2095
|
-
# idle_response_timeout:: Seconds to wait until an IDLE response is received
|
|
2096
|
-
# response_handlers:: A list of response handlers to be added before the
|
|
2097
|
-
# receiver thread is started. This ensures every server
|
|
2098
|
-
# response is handled, including the #greeting. Note
|
|
2099
|
-
# that the greeting is handled in the current thread,
|
|
2100
|
-
# but all other responses are handled in the receiver
|
|
2101
|
-
# thread.
|
|
2102
|
-
# max_response_size:: See #max_response_size.
|
|
2103
|
-
#
|
|
2104
|
-
# The most common errors are:
|
|
2105
|
-
#
|
|
2106
|
-
# Errno::ECONNREFUSED:: Connection refused by +host+ or an intervening
|
|
2107
|
-
# firewall.
|
|
2108
|
-
# Errno::ETIMEDOUT:: Connection timed out (possibly due to packets
|
|
2109
|
-
# being dropped by an intervening firewall).
|
|
2110
|
-
# Errno::ENETUNREACH:: There is no route to that network.
|
|
2111
|
-
# SocketError:: Hostname not known or other socket error.
|
|
2112
|
-
# Net::IMAP::ByeResponseError:: The connected to the host was successful, but
|
|
2113
|
-
# it immediately said goodbye.
|
|
2114
|
-
def initialize(host, port_or_options = {},
|
|
2115
|
-
usessl = false, certs = nil, verify = true)
|
|
2116
|
-
super()
|
|
2117
|
-
@host = host
|
|
2118
|
-
begin
|
|
2119
|
-
options = port_or_options.to_hash
|
|
2120
|
-
rescue NoMethodError
|
|
2121
|
-
# for backward compatibility
|
|
2122
|
-
options = {}
|
|
2123
|
-
options[:port] = port_or_options
|
|
2124
|
-
if usessl
|
|
2125
|
-
options[:ssl] = create_ssl_params(certs, verify)
|
|
2126
|
-
end
|
|
2127
|
-
end
|
|
2128
|
-
@port = options[:port] || (options[:ssl] ? SSL_PORT : PORT)
|
|
2129
|
-
@tag_prefix = "RUBY"
|
|
2130
|
-
@tagno = 0
|
|
2131
|
-
@open_timeout = options[:open_timeout] || 30
|
|
2132
|
-
@idle_response_timeout = options[:idle_response_timeout] || 5
|
|
2133
|
-
@max_response_size = options[:max_response_size]
|
|
2134
|
-
@parser = ResponseParser.new
|
|
2135
|
-
@sock = tcp_socket(@host, @port)
|
|
2136
|
-
@reader = ResponseReader.new(self, @sock)
|
|
2137
|
-
begin
|
|
2138
|
-
if options[:ssl]
|
|
2139
|
-
start_tls_session(options[:ssl])
|
|
2140
|
-
@usessl = true
|
|
2141
|
-
else
|
|
2142
|
-
@usessl = false
|
|
2143
|
-
end
|
|
2144
|
-
@responses = Hash.new([].freeze)
|
|
2145
|
-
@tagged_responses = {}
|
|
2146
|
-
@response_handlers = []
|
|
2147
|
-
options[:response_handlers]&.each do |h| add_response_handler(h) end
|
|
2148
|
-
@tagged_response_arrival = new_cond
|
|
2149
|
-
@continued_command_tag = nil
|
|
2150
|
-
@continuation_request_arrival = new_cond
|
|
2151
|
-
@continuation_request_exception = nil
|
|
2152
|
-
@idle_done_cond = nil
|
|
2153
|
-
@logout_command_tag = nil
|
|
2154
|
-
@debug_output_bol = true
|
|
2155
|
-
@exception = nil
|
|
2156
|
-
|
|
2157
|
-
@greeting = get_response
|
|
2158
|
-
if @greeting.nil?
|
|
2159
|
-
raise Error, "connection closed"
|
|
2160
|
-
end
|
|
2161
|
-
if @greeting.name == "BYE"
|
|
2162
|
-
raise ByeResponseError, @greeting
|
|
2163
|
-
end
|
|
2164
|
-
@response_handlers.each do |handler| handler.call(@greeting) end
|
|
2397
|
+
def start_imap_connection
|
|
2398
|
+
@greeting = get_server_greeting
|
|
2399
|
+
@capabilities = capabilities_from_resp_code @greeting
|
|
2400
|
+
@receiver_thread = start_receiver_thread
|
|
2401
|
+
rescue Exception
|
|
2402
|
+
@sock.close
|
|
2403
|
+
raise
|
|
2404
|
+
end
|
|
2165
2405
|
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2406
|
+
def get_server_greeting
|
|
2407
|
+
greeting = get_response
|
|
2408
|
+
raise Error, "No server greeting - connection closed" unless greeting
|
|
2409
|
+
record_untagged_response_code greeting
|
|
2410
|
+
raise ByeResponseError, greeting if greeting.name == "BYE"
|
|
2411
|
+
greeting
|
|
2412
|
+
end
|
|
2413
|
+
|
|
2414
|
+
def start_receiver_thread
|
|
2415
|
+
Thread.start do
|
|
2416
|
+
receive_responses
|
|
2417
|
+
rescue Exception => ex
|
|
2418
|
+
@receiver_thread_exception = ex
|
|
2419
|
+
# don't exit the thread with an exception
|
|
2177
2420
|
end
|
|
2178
2421
|
end
|
|
2179
2422
|
|
|
@@ -2222,11 +2465,7 @@ module Net
|
|
|
2222
2465
|
@continuation_request_arrival.signal
|
|
2223
2466
|
end
|
|
2224
2467
|
when UntaggedResponse
|
|
2225
|
-
|
|
2226
|
-
if resp.data.instance_of?(ResponseText) &&
|
|
2227
|
-
(code = resp.data.code)
|
|
2228
|
-
record_response(code.name, code.data)
|
|
2229
|
-
end
|
|
2468
|
+
record_untagged_response(resp)
|
|
2230
2469
|
if resp.name == "BYE" && @logout_command_tag.nil?
|
|
2231
2470
|
@sock.close
|
|
2232
2471
|
@exception = ByeResponseError.new(resp)
|
|
@@ -2285,19 +2524,61 @@ module Net
|
|
|
2285
2524
|
end
|
|
2286
2525
|
|
|
2287
2526
|
def get_response
|
|
2288
|
-
buff =
|
|
2527
|
+
buff = String.new
|
|
2528
|
+
while true
|
|
2529
|
+
s = @sock.gets(CRLF)
|
|
2530
|
+
break unless s
|
|
2531
|
+
buff.concat(s)
|
|
2532
|
+
if /\{(\d+)\}\r\n/n =~ s
|
|
2533
|
+
s = @sock.read($1.to_i)
|
|
2534
|
+
buff.concat(s)
|
|
2535
|
+
else
|
|
2536
|
+
break
|
|
2537
|
+
end
|
|
2538
|
+
end
|
|
2289
2539
|
return nil if buff.length == 0
|
|
2290
|
-
|
|
2291
|
-
|
|
2540
|
+
if @@debug
|
|
2541
|
+
$stderr.print(buff.gsub(/^/n, "S: "))
|
|
2542
|
+
end
|
|
2543
|
+
return @parser.parse(buff)
|
|
2292
2544
|
end
|
|
2293
2545
|
|
|
2294
2546
|
#############################
|
|
2547
|
+
# built-in response handlers
|
|
2548
|
+
|
|
2549
|
+
# store name => [..., data]
|
|
2550
|
+
def record_untagged_response(resp)
|
|
2551
|
+
@responses[resp.name] << resp.data
|
|
2552
|
+
record_untagged_response_code resp
|
|
2553
|
+
end
|
|
2554
|
+
|
|
2555
|
+
# store code.name => [..., code.data]
|
|
2556
|
+
def record_untagged_response_code(resp)
|
|
2557
|
+
return unless resp.data.is_a?(ResponseText)
|
|
2558
|
+
return unless (code = resp.data.code)
|
|
2559
|
+
@responses[code.name] << code.data
|
|
2560
|
+
end
|
|
2561
|
+
|
|
2562
|
+
# NOTE: only call this for greeting, login, and authenticate
|
|
2563
|
+
def capabilities_from_resp_code(resp)
|
|
2564
|
+
return unless %w[PREAUTH OK].any? { _1.casecmp? resp.name }
|
|
2565
|
+
return unless (code = resp.data.code)
|
|
2566
|
+
return unless code.name.casecmp?("CAPABILITY")
|
|
2567
|
+
code.data.freeze
|
|
2568
|
+
end
|
|
2295
2569
|
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2570
|
+
#############################
|
|
2571
|
+
|
|
2572
|
+
# Calls send_command, yielding the text of each ContinuationRequest and
|
|
2573
|
+
# responding with each block result. Returns TaggedResponse. Raises
|
|
2574
|
+
# NoResponseError or BadResponseError.
|
|
2575
|
+
def send_command_with_continuations(cmd, *args)
|
|
2576
|
+
send_command(cmd, *args) do |server_response|
|
|
2577
|
+
if server_response.instance_of?(ContinuationRequest)
|
|
2578
|
+
client_response = yield server_response.data.text
|
|
2579
|
+
put_string(client_response + CRLF)
|
|
2580
|
+
end
|
|
2299
2581
|
end
|
|
2300
|
-
@responses[name].push(data)
|
|
2301
2582
|
end
|
|
2302
2583
|
|
|
2303
2584
|
def send_command(cmd, *args, &block)
|
|
@@ -2311,7 +2592,6 @@ module Net
|
|
|
2311
2592
|
put_string(" ")
|
|
2312
2593
|
send_data(i, tag)
|
|
2313
2594
|
end
|
|
2314
|
-
guard_against_tagged_response_skipping_handler!(tag)
|
|
2315
2595
|
put_string(CRLF)
|
|
2316
2596
|
if cmd == "LOGOUT"
|
|
2317
2597
|
@logout_command_tag = tag
|
|
@@ -2327,17 +2607,6 @@ module Net
|
|
|
2327
2607
|
end
|
|
2328
2608
|
end
|
|
2329
2609
|
end
|
|
2330
|
-
rescue InvalidResponseError
|
|
2331
|
-
disconnect
|
|
2332
|
-
raise
|
|
2333
|
-
end
|
|
2334
|
-
|
|
2335
|
-
def guard_against_tagged_response_skipping_handler!(tag)
|
|
2336
|
-
return unless (resp = @tagged_responses[tag])&.name&.upcase == "OK"
|
|
2337
|
-
raise(InvalidResponseError,
|
|
2338
|
-
"Server sent tagged 'OK' before command was finished: %p. " \
|
|
2339
|
-
"This could indicate a malicious server or client-side " \
|
|
2340
|
-
"command injection. Disconnecting." % [resp.raw_data.chomp])
|
|
2341
2610
|
end
|
|
2342
2611
|
|
|
2343
2612
|
def generate_tag
|
|
@@ -2351,8 +2620,8 @@ module Net
|
|
|
2351
2620
|
if @debug_output_bol
|
|
2352
2621
|
$stderr.print("C: ")
|
|
2353
2622
|
end
|
|
2354
|
-
$stderr.print(str.gsub(/\n
|
|
2355
|
-
if /\
|
|
2623
|
+
$stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " })
|
|
2624
|
+
if /\n\z/n.match(str)
|
|
2356
2625
|
@debug_output_bol = true
|
|
2357
2626
|
else
|
|
2358
2627
|
@debug_output_bol = false
|
|
@@ -2372,7 +2641,7 @@ module Net
|
|
|
2372
2641
|
else
|
|
2373
2642
|
send_command(cmd, *keys)
|
|
2374
2643
|
end
|
|
2375
|
-
|
|
2644
|
+
clear_responses("SEARCH").last
|
|
2376
2645
|
end
|
|
2377
2646
|
end
|
|
2378
2647
|
|
|
@@ -2387,13 +2656,13 @@ module Net
|
|
|
2387
2656
|
end
|
|
2388
2657
|
|
|
2389
2658
|
synchronize do
|
|
2390
|
-
|
|
2659
|
+
clear_responses("FETCH")
|
|
2391
2660
|
if mod
|
|
2392
2661
|
send_command(cmd, MessageSet.new(set), attr, mod)
|
|
2393
2662
|
else
|
|
2394
2663
|
send_command(cmd, MessageSet.new(set), attr)
|
|
2395
2664
|
end
|
|
2396
|
-
|
|
2665
|
+
clear_responses("FETCH")
|
|
2397
2666
|
end
|
|
2398
2667
|
end
|
|
2399
2668
|
|
|
@@ -2402,9 +2671,9 @@ module Net
|
|
|
2402
2671
|
attr = RawData.new(attr)
|
|
2403
2672
|
end
|
|
2404
2673
|
synchronize do
|
|
2405
|
-
|
|
2674
|
+
clear_responses("FETCH")
|
|
2406
2675
|
send_command(cmd, MessageSet.new(set), attr, flags)
|
|
2407
|
-
|
|
2676
|
+
clear_responses("FETCH")
|
|
2408
2677
|
end
|
|
2409
2678
|
end
|
|
2410
2679
|
|
|
@@ -2421,7 +2690,7 @@ module Net
|
|
|
2421
2690
|
normalize_searching_criteria(search_keys)
|
|
2422
2691
|
synchronize do
|
|
2423
2692
|
send_command(cmd, sort_keys, charset, *search_keys)
|
|
2424
|
-
|
|
2693
|
+
clear_responses("SORT").last
|
|
2425
2694
|
end
|
|
2426
2695
|
end
|
|
2427
2696
|
|
|
@@ -2432,8 +2701,10 @@ module Net
|
|
|
2432
2701
|
normalize_searching_criteria(search_keys)
|
|
2433
2702
|
end
|
|
2434
2703
|
normalize_searching_criteria(search_keys)
|
|
2435
|
-
|
|
2436
|
-
|
|
2704
|
+
synchronize do
|
|
2705
|
+
send_command(cmd, algorithm, charset, *search_keys)
|
|
2706
|
+
clear_responses("THREAD").last
|
|
2707
|
+
end
|
|
2437
2708
|
end
|
|
2438
2709
|
|
|
2439
2710
|
def normalize_searching_criteria(keys)
|
|
@@ -2447,50 +2718,45 @@ module Net
|
|
|
2447
2718
|
end
|
|
2448
2719
|
end
|
|
2449
2720
|
|
|
2450
|
-
def
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2721
|
+
def build_ssl_ctx(ssl)
|
|
2722
|
+
if ssl
|
|
2723
|
+
params = (Hash.try_convert(ssl) || {}).freeze
|
|
2724
|
+
context = SSLContext.new
|
|
2725
|
+
context.set_params(params)
|
|
2726
|
+
if defined?(VerifyCallbackProc)
|
|
2727
|
+
context.verify_callback = VerifyCallbackProc
|
|
2457
2728
|
end
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
params[:verify_mode] = VERIFY_PEER
|
|
2729
|
+
context.freeze
|
|
2730
|
+
[params, context]
|
|
2461
2731
|
else
|
|
2462
|
-
|
|
2732
|
+
false
|
|
2463
2733
|
end
|
|
2464
|
-
return params
|
|
2465
2734
|
end
|
|
2466
2735
|
|
|
2467
|
-
def start_tls_session
|
|
2468
|
-
unless defined?(OpenSSL::SSL)
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
raise RuntimeError, "already using SSL"
|
|
2473
|
-
end
|
|
2474
|
-
begin
|
|
2475
|
-
params = params.to_hash
|
|
2476
|
-
rescue NoMethodError
|
|
2477
|
-
params = {}
|
|
2478
|
-
end
|
|
2479
|
-
context = SSLContext.new
|
|
2480
|
-
context.set_params(params)
|
|
2481
|
-
if defined?(VerifyCallbackProc)
|
|
2482
|
-
context.verify_callback = VerifyCallbackProc
|
|
2483
|
-
end
|
|
2484
|
-
@sock = SSLSocket.new(@sock, context)
|
|
2485
|
-
@reader = ResponseReader.new(self, @sock)
|
|
2736
|
+
def start_tls_session
|
|
2737
|
+
raise "SSL extension not installed" unless defined?(OpenSSL::SSL)
|
|
2738
|
+
raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
|
|
2739
|
+
raise "cannot start TLS without SSLContext" unless ssl_ctx
|
|
2740
|
+
@sock = SSLSocket.new(@sock, ssl_ctx)
|
|
2486
2741
|
@sock.sync_close = true
|
|
2487
2742
|
@sock.hostname = @host if @sock.respond_to? :hostname=
|
|
2488
2743
|
ssl_socket_connect(@sock, @open_timeout)
|
|
2489
|
-
if
|
|
2744
|
+
if ssl_ctx.verify_mode != VERIFY_NONE
|
|
2490
2745
|
@sock.post_connection_check(@host)
|
|
2746
|
+
@tls_verified = true
|
|
2491
2747
|
end
|
|
2492
2748
|
end
|
|
2493
2749
|
|
|
2750
|
+
#--
|
|
2751
|
+
# We could get the saslprep method by extending the SASLprep module
|
|
2752
|
+
# directly. It's done indirectly, so SASLprep can be lazily autoloaded,
|
|
2753
|
+
# because most users won't need it.
|
|
2754
|
+
#++
|
|
2755
|
+
# Delegates to Net::IMAP::StringPrep::SASLprep#saslprep.
|
|
2756
|
+
def self.saslprep(string, **opts)
|
|
2757
|
+
Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
|
|
2758
|
+
end
|
|
2759
|
+
|
|
2494
2760
|
end
|
|
2495
2761
|
end
|
|
2496
2762
|
|
|
@@ -2501,4 +2767,6 @@ require_relative "imap/flags"
|
|
|
2501
2767
|
require_relative "imap/response_data"
|
|
2502
2768
|
require_relative "imap/response_parser"
|
|
2503
2769
|
require_relative "imap/authenticators"
|
|
2504
|
-
|
|
2770
|
+
|
|
2771
|
+
require_relative "imap/deprecated_client_options"
|
|
2772
|
+
Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions
|