net-imap 0.3.7 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of net-imap might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/workflows/pages.yml +46 -0
- data/.github/workflows/test.yml +5 -12
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/README.md +15 -4
- data/Rakefile +0 -7
- data/docs/styles.css +0 -12
- data/lib/net/imap/authenticators.rb +26 -57
- data/lib/net/imap/command_data.rb +13 -6
- data/lib/net/imap/data_encoding.rb +14 -2
- data/lib/net/imap/deprecated_client_options.rb +139 -0
- data/lib/net/imap/errors.rb +20 -0
- data/lib/net/imap/fetch_data.rb +518 -0
- data/lib/net/imap/response_data.rb +178 -255
- data/lib/net/imap/response_parser/parser_utils.rb +240 -0
- data/lib/net/imap/response_parser.rb +1722 -1193
- data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
- data/lib/net/imap/sasl/authentication_exchange.rb +107 -0
- data/lib/net/imap/sasl/authenticators.rb +118 -0
- data/lib/net/imap/sasl/client_adapter.rb +72 -0
- data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +21 -11
- data/lib/net/imap/sasl/digest_md5_authenticator.rb +180 -0
- data/lib/net/imap/sasl/external_authenticator.rb +83 -0
- data/lib/net/imap/sasl/gs2_header.rb +80 -0
- data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +25 -16
- data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
- data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
- data/lib/net/imap/sasl/protocol_adapters.rb +45 -0
- data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
- data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
- data/lib/net/imap/sasl/stringprep.rb +6 -66
- data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
- data/lib/net/imap/sasl.rb +144 -43
- data/lib/net/imap/sasl_adapter.rb +21 -0
- data/lib/net/imap/search_result.rb +150 -0
- data/lib/net/imap/sequence_set.rb +1414 -0
- 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 +1213 -636
- data/net-imap.gemspec +5 -3
- data/rakelib/benchmarks.rake +91 -0
- data/rakelib/saslprep.rake +4 -4
- data/rakelib/string_prep_tables_generator.rb +82 -60
- metadata +34 -14
- data/benchmarks/stringprep.yml +0 -65
- data/benchmarks/table-regexps.yml +0 -39
- data/lib/net/imap/authenticators/digest_md5.rb +0 -115
- data/lib/net/imap/authenticators/plain.rb +0 -41
- data/lib/net/imap/authenticators/xoauth2.rb +0 -20
- 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
|
#
|
@@ -77,31 +75,22 @@ module Net
|
|
77
75
|
# UIDs have to be reassigned. An \IMAP client thus cannot
|
78
76
|
# rearrange message orders.
|
79
77
|
#
|
80
|
-
# ===
|
78
|
+
# === Examples of Usage
|
81
79
|
#
|
82
|
-
#
|
83
|
-
# #capability. Users of the class must check for required capabilities before
|
84
|
-
# issuing commands. Special care should be taken to follow all #capability
|
85
|
-
# requirements for #starttls, #login, and #authenticate.
|
86
|
-
#
|
87
|
-
# See the #capability method for more information.
|
88
|
-
#
|
89
|
-
# == Examples of Usage
|
90
|
-
#
|
91
|
-
# === List sender and subject of all recent messages in the default mailbox
|
80
|
+
# ==== List sender and subject of all recent messages in the default mailbox
|
92
81
|
#
|
93
82
|
# imap = Net::IMAP.new('mail.example.com')
|
94
|
-
# imap.authenticate('
|
83
|
+
# imap.authenticate('PLAIN', 'joe_user', 'joes_password')
|
95
84
|
# imap.examine('INBOX')
|
96
85
|
# imap.search(["RECENT"]).each do |message_id|
|
97
86
|
# envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
|
98
87
|
# puts "#{envelope.from[0].name}: \t#{envelope.subject}"
|
99
88
|
# end
|
100
89
|
#
|
101
|
-
#
|
90
|
+
# ==== Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
|
102
91
|
#
|
103
92
|
# imap = Net::IMAP.new('mail.example.com')
|
104
|
-
# imap.authenticate('
|
93
|
+
# imap.authenticate('PLAIN', 'joe_user', 'joes_password')
|
105
94
|
# imap.select('Mail/sent-mail')
|
106
95
|
# if not imap.list('Mail/', 'sent-apr03')
|
107
96
|
# imap.create('Mail/sent-apr03')
|
@@ -112,12 +101,96 @@ module Net
|
|
112
101
|
# end
|
113
102
|
# imap.expunge
|
114
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.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
|
+
#
|
115
188
|
# == Thread Safety
|
116
189
|
#
|
117
190
|
# Net::IMAP supports concurrent threads. For example,
|
118
191
|
#
|
119
192
|
# imap = Net::IMAP.new("imap.foo.net", "imap2")
|
120
|
-
# imap.authenticate("
|
193
|
+
# imap.authenticate("scram-md5", "bar", "password")
|
121
194
|
# imap.select("inbox")
|
122
195
|
# fetch_thread = Thread.start { imap.fetch(1..-1, "UID") }
|
123
196
|
# search_result = imap.search(["BODY", "hello"])
|
@@ -173,24 +246,54 @@ module Net
|
|
173
246
|
# == What's here?
|
174
247
|
#
|
175
248
|
# * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods]
|
176
|
-
# * {
|
177
|
-
# * {...for any state}[rdoc-ref:Net::IMAP@IMAP+commands+for+any+state]
|
178
|
-
# * {...for the "not authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Not+Authenticated-22+state]
|
179
|
-
# * {...for the "authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Authenticated-22+state]
|
180
|
-
# * {...for the "selected" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Selected-22+state]
|
181
|
-
# * {...for the "logout" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Logout-22+state]
|
182
|
-
# * {Supported IMAP extensions}[rdoc-ref:Net::IMAP@Supported+IMAP+extensions]
|
249
|
+
# * {Server capabilities}[rdoc-ref:Net::IMAP@Server+capabilities]
|
183
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]
|
184
258
|
#
|
185
259
|
# === Connection control methods
|
186
260
|
#
|
187
|
-
# - Net::IMAP.new:
|
188
|
-
# 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.
|
189
263
|
# - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
|
190
264
|
# - #logout: Tells the server to end the session. Enters the "_logout_" state.
|
191
265
|
# - #disconnect: Disconnects the connection (without sending #logout first).
|
192
266
|
# - #disconnected?: True if the connection has been closed.
|
193
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
|
+
#
|
194
297
|
# === Core \IMAP commands
|
195
298
|
#
|
196
299
|
# The following commands are defined either by
|
@@ -199,69 +302,48 @@ module Net
|
|
199
302
|
# [IDLE[https://tools.ietf.org/html/rfc2177]],
|
200
303
|
# [NAMESPACE[https://tools.ietf.org/html/rfc2342]],
|
201
304
|
# [UNSELECT[https://tools.ietf.org/html/rfc3691]],
|
202
|
-
|
203
|
-
# TODO: [ENABLE[https://tools.ietf.org/html/rfc5161]],
|
204
|
-
# TODO: [LIST-EXTENDED[https://tools.ietf.org/html/rfc5258]],
|
205
|
-
# TODO: [LIST-STATUS[https://tools.ietf.org/html/rfc5819]],
|
206
|
-
#++
|
305
|
+
# [ENABLE[https://tools.ietf.org/html/rfc5161]],
|
207
306
|
# [MOVE[https://tools.ietf.org/html/rfc6851]].
|
208
307
|
# These extensions are widely supported by modern IMAP4rev1 servers and have
|
209
308
|
# all been integrated into [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
|
210
|
-
# <em
|
211
|
-
#
|
212
|
-
|
213
|
-
# TODO: When IMAP4rev2 is supported, add the following to the each of the
|
214
|
-
# appropriate commands below.
|
215
|
-
# Note:: CHECK has been removed from IMAP4rev2.
|
216
|
-
# Note:: LSUB is obsoleted by +LIST-EXTENDED and has been removed from IMAP4rev2.
|
217
|
-
# <em>Some arguments require the +LIST-EXTENDED+ or +IMAP4rev2+ capability.</em>
|
218
|
-
# <em>Requires either the +ENABLE+ or +IMAP4rev2+ capability.</em>
|
219
|
-
# <em>Requires either the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
|
220
|
-
# <em>Requires either the +IDLE+ or +IMAP4rev2+ capability.</em>
|
221
|
-
# <em>Requires either the +UNSELECT+ or +IMAP4rev2+ capability.</em>
|
222
|
-
# <em>Requires either the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
|
223
|
-
# <em>Requires either the +MOVE+ or +IMAP4rev2+ capability.</em>
|
224
|
-
#++
|
225
|
-
#
|
226
|
-
# ==== \IMAP commands for any state
|
309
|
+
# <em>*NOTE:* Net::IMAP doesn't support IMAP4rev2 yet.</em>
|
310
|
+
#
|
311
|
+
# ==== Any state
|
227
312
|
#
|
228
313
|
# - #capability: Returns the server's capabilities as an array of strings.
|
229
314
|
#
|
230
|
-
# <em>
|
231
|
-
#
|
315
|
+
# <em>In general, #capable? should be used rather than explicitly sending a
|
316
|
+
# +CAPABILITY+ command to the server.</em>
|
232
317
|
# - #noop: Allows the server to send unsolicited untagged #responses.
|
233
318
|
# - #logout: Tells the server to end the session. Enters the "_logout_" state.
|
234
319
|
#
|
235
|
-
# ====
|
320
|
+
# ==== Not Authenticated state
|
236
321
|
#
|
237
|
-
# In addition to the
|
238
|
-
# the "<em>not authenticated</em>" state:
|
322
|
+
# In addition to the commands for any state, the following commands are valid
|
323
|
+
# in the "<em>not authenticated</em>" state:
|
239
324
|
#
|
240
325
|
# - #starttls: Upgrades a clear-text connection to use TLS.
|
241
326
|
#
|
242
327
|
# <em>Requires the +STARTTLS+ capability.</em>
|
243
|
-
# - #authenticate: Identifies the client to the server using
|
244
|
-
# mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
245
|
-
# Enters the "_authenticated_" state.
|
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.
|
246
331
|
#
|
247
|
-
# <em>
|
248
|
-
#
|
332
|
+
# <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
|
333
|
+
# supported mechanisms.</em>
|
249
334
|
# - #login: Identifies the client to the server using a plain text password.
|
250
335
|
# Using #authenticate is generally preferred. Enters the "_authenticated_"
|
251
336
|
# state.
|
252
337
|
#
|
253
338
|
# <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
|
254
339
|
#
|
255
|
-
# ====
|
256
|
-
#
|
257
|
-
# In addition to the universal commands, the following commands are valid in
|
258
|
-
# the "_authenticated_" state:
|
340
|
+
# ==== Authenticated state
|
259
341
|
#
|
260
|
-
|
261
|
-
#
|
342
|
+
# In addition to the commands for any state, the following commands are valid
|
343
|
+
# in the "_authenticated_" state:
|
262
344
|
#
|
263
|
-
#
|
264
|
-
|
345
|
+
# - #enable: Enables backwards incompatible server extensions.
|
346
|
+
# <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
|
265
347
|
# - #select: Open a mailbox and enter the "_selected_" state.
|
266
348
|
# - #examine: Open a mailbox read-only, and enter the "_selected_" state.
|
267
349
|
# - #create: Creates a new mailbox.
|
@@ -271,37 +353,31 @@ module Net
|
|
271
353
|
# - #unsubscribe: Removes a mailbox from the "subscribed" set.
|
272
354
|
# - #list: Returns names and attributes of mailboxes matching a given pattern.
|
273
355
|
# - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
|
274
|
-
#
|
275
|
-
# <em>Requires the +NAMESPACE+ capability.</em>
|
356
|
+
# <em>Requires the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
|
276
357
|
# - #status: Returns mailbox information, e.g. message count, unseen message
|
277
358
|
# count, +UIDVALIDITY+ and +UIDNEXT+.
|
278
359
|
# - #append: Appends a message to the end of a mailbox.
|
279
360
|
# - #idle: Allows the server to send updates to the client, without the client
|
280
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.
|
281
365
|
#
|
282
|
-
# <em
|
283
|
-
# - #lsub: Lists mailboxes the user has declared "active" or "subscribed".
|
284
|
-
#--
|
285
|
-
# <em>Replaced by</em> <tt>LIST-EXTENDED</tt> <em>and removed from</em>
|
286
|
-
# +IMAP4rev2+. <em>However, Net::IMAP hasn't implemented</em>
|
287
|
-
# <tt>LIST-EXTENDED</tt> _yet_.
|
288
|
-
#++
|
366
|
+
# <em>*Note:* Net::IMAP hasn't implemented <tt>LIST-EXTENDED</tt> yet.</em>
|
289
367
|
#
|
290
|
-
# ====
|
368
|
+
# ==== Selected state
|
291
369
|
#
|
292
|
-
# In addition to the
|
293
|
-
# following commands are valid in the "_selected_" state:
|
370
|
+
# In addition to the commands for any state and the "_authenticated_"
|
371
|
+
# commands, the following commands are valid in the "_selected_" state:
|
294
372
|
#
|
295
373
|
# - #close: Closes the mailbox and returns to the "_authenticated_" state,
|
296
374
|
# expunging deleted messages, unless the mailbox was opened as read-only.
|
297
375
|
# - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
|
298
376
|
# without expunging any messages.
|
299
|
-
#
|
300
|
-
# <em>Requires the +UNSELECT+ capability.</em>
|
377
|
+
# <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
|
301
378
|
# - #expunge: Permanently removes messages which have the Deleted flag set.
|
302
|
-
# - #uid_expunge: Restricts
|
303
|
-
#
|
304
|
-
# <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>
|
305
381
|
# - #search, #uid_search: Returns sequence numbers or UIDs of messages that
|
306
382
|
# match the given searching criteria.
|
307
383
|
# - #fetch, #uid_fetch: Returns data associated with a set of messages,
|
@@ -311,45 +387,33 @@ module Net
|
|
311
387
|
# specified destination mailbox.
|
312
388
|
# - #move, #uid_move: Moves the specified messages to the end of the
|
313
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.
|
314
393
|
#
|
315
|
-
#
|
316
|
-
# - #check: Mostly obsolete. Can be replaced with #noop or #idle.
|
317
|
-
#--
|
318
|
-
# <em>Removed from IMAP4rev2.</em>
|
319
|
-
#++
|
320
|
-
#
|
321
|
-
# ==== \IMAP commands for the "Logout" state
|
394
|
+
# ==== Logout state
|
322
395
|
#
|
323
|
-
# No \IMAP commands are valid in the
|
396
|
+
# No \IMAP commands are valid in the "_logout_" state. If the socket is still
|
324
397
|
# open, Net::IMAP will close it after receiving server confirmation.
|
325
398
|
# Exceptions will be raised by \IMAP commands that have already started and
|
326
399
|
# are waiting for a response, as well as any that are called after logout.
|
327
400
|
#
|
328
|
-
# ===
|
401
|
+
# === \IMAP extension support
|
329
402
|
#
|
330
403
|
# ==== RFC9051: +IMAP4rev2+
|
331
404
|
#
|
332
|
-
# Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is
|
333
|
-
# yet
|
334
|
-
#
|
335
|
-
|
336
|
-
#
|
337
|
-
#
|
338
|
-
#
|
339
|
-
#
|
340
|
-
#
|
341
|
-
#
|
342
|
-
#
|
343
|
-
#
|
344
|
-
# TODO: +BINARY+ (only the FETCH side)
|
345
|
-
# TODO: +SPECIAL-USE+
|
346
|
-
# implicitly supported, but we can do better: Response codes: RFC5530, etc
|
347
|
-
# implicitly supported, but we can do better: <tt>STATUS=SIZE</tt>
|
348
|
-
# implicitly supported, but we can do better: <tt>STATUS DELETED</tt>
|
349
|
-
#++
|
350
|
-
# Commands for these extensions are included with the {Core IMAP
|
351
|
-
# commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above. Other supported
|
352
|
-
# 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+, +UNSELECT+,
|
408
|
+
# <tt>STATUS=SIZE</tt>, and the fetch side of +BINARY+.
|
409
|
+
# Commands for these extensions are listed with the {Core IMAP
|
410
|
+
# commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above.
|
411
|
+
#
|
412
|
+
# >>>
|
413
|
+
# <em>The following are folded into +IMAP4rev2+ but are currently
|
414
|
+
# unsupported or incompletely supported by</em> Net::IMAP<em>: RFC4466
|
415
|
+
# extensions, +ESEARCH+, +SEARCHRES+, +LIST-EXTENDED+, +LIST-STATUS+,
|
416
|
+
# +LITERAL-+, and +SPECIAL-USE+.</em>
|
353
417
|
#
|
354
418
|
# ==== RFC2087: +QUOTA+
|
355
419
|
# - #getquota: returns the resource usage and limits for a quota root
|
@@ -358,92 +422,56 @@ module Net
|
|
358
422
|
# - #setquota: sets the resource limits for a given quota root.
|
359
423
|
#
|
360
424
|
# ==== RFC2177: +IDLE+
|
361
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]
|
362
|
-
#
|
425
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
426
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
363
427
|
# - #idle: Allows the server to send updates to the client, without the client
|
364
428
|
# needing to poll using #noop.
|
365
429
|
#
|
366
430
|
# ==== RFC2342: +NAMESPACE+
|
367
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]
|
368
|
-
#
|
431
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
432
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
369
433
|
# - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
|
370
434
|
#
|
371
435
|
# ==== RFC2971: +ID+
|
372
436
|
# - #id: exchanges client and server implementation information.
|
373
437
|
#
|
374
|
-
#--
|
375
|
-
# ==== RFC3502: +MULTIAPPEND+
|
376
|
-
# TODO...
|
377
|
-
#++
|
378
|
-
#
|
379
|
-
#--
|
380
438
|
# ==== RFC3516: +BINARY+
|
381
|
-
#
|
382
|
-
|
439
|
+
# The fetch side of +BINARY+ has been folded into
|
440
|
+
# IMAP4rev2[https://tools.ietf.org/html/rfc9051].
|
441
|
+
# - Updates #fetch and #uid_fetch with the +BINARY+, +BINARY.PEEK+, and
|
442
|
+
# +BINARY.SIZE+ items. See FetchData#binary and FetchData#binary_size.
|
443
|
+
#
|
444
|
+
# >>>
|
445
|
+
# *NOTE:* The binary extension the #append command is _not_ supported yet.
|
383
446
|
#
|
384
447
|
# ==== RFC3691: +UNSELECT+
|
385
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]
|
386
|
-
#
|
448
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
449
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
387
450
|
# - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
|
388
451
|
# without expunging any messages.
|
389
452
|
#
|
390
453
|
# ==== RFC4314: +ACL+
|
391
454
|
# - #getacl: lists the authenticated user's access rights to a mailbox.
|
392
455
|
# - #setacl: sets the access rights for a user on a mailbox
|
393
|
-
|
394
|
-
#
|
395
|
-
#++
|
396
|
-
# - *_Note:_* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
|
456
|
+
# >>>
|
457
|
+
# *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
|
397
458
|
#
|
398
459
|
# ==== RFC4315: +UIDPLUS+
|
399
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]
|
400
|
-
#
|
460
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
461
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
401
462
|
# - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
|
402
463
|
# - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode
|
403
464
|
# - Updates #append with the +APPENDUID+ ResponseCode
|
404
465
|
# - Updates #copy, #move with the +COPYUID+ ResponseCode
|
405
466
|
#
|
406
|
-
#--
|
407
|
-
# ==== RFC4466: Collected Extensions to IMAP4 ABNF
|
408
|
-
# TODO...
|
409
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this RFC updates
|
410
|
-
# the protocol to enable new optional parameters to many commands: #select,
|
411
|
-
# #examine, #create, #rename, #fetch, #uid_fetch, #store, #uid_store, #search,
|
412
|
-
# #uid_search, and #append. However, specific parameters are not defined.
|
413
|
-
# Extensions to these commands use this syntax whenever possible. Net::IMAP
|
414
|
-
# may be partially compatible with extensions to these commands, even without
|
415
|
-
# any explicit support.
|
416
|
-
#++
|
417
|
-
#
|
418
|
-
#--
|
419
|
-
# ==== RFC4731 +ESEARCH+
|
420
|
-
# TODO...
|
421
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
|
422
|
-
# - Updates #search, #uid_search to accept result options: +MIN+, +MAX+,
|
423
|
-
# +ALL+, +COUNT+, and to return ExtendedSearchData.
|
424
|
-
#++
|
425
|
-
#
|
426
|
-
#--
|
427
467
|
# ==== RFC4959: +SASL-IR+
|
428
|
-
# TODO...
|
429
468
|
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
|
430
|
-
# - Updates #authenticate to
|
431
|
-
#++
|
432
|
-
#
|
433
|
-
#--
|
434
|
-
# ==== RFC4978: COMPRESS=DEFLATE
|
435
|
-
# TODO...
|
436
|
-
#++
|
469
|
+
# - Updates #authenticate with the option to send an initial response.
|
437
470
|
#
|
438
|
-
|
439
|
-
#
|
440
|
-
#
|
441
|
-
#
|
442
|
-
# - Updates #search, #uid_search with the +SAVE+ result option.
|
443
|
-
# - Updates #copy, #uid_copy, #fetch, #uid_fetch, #move, #uid_move, #search,
|
444
|
-
# #uid_search, #store, #uid_store, and #uid_expunge with ability to
|
445
|
-
# reference the saved result of a previous #search or #uid_search command.
|
446
|
-
#++
|
471
|
+
# ==== RFC5161: +ENABLE+
|
472
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
473
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
474
|
+
# - #enable: Enables backwards incompatible server extensions.
|
447
475
|
#
|
448
476
|
# ==== RFC5256: +SORT+
|
449
477
|
# - #sort, #uid_sort: An alternate version of #search or #uid_search which
|
@@ -453,75 +481,54 @@ module Net
|
|
453
481
|
# which arranges the results into ordered groups or threads according to a
|
454
482
|
# chosen algorithm.
|
455
483
|
#
|
456
|
-
|
457
|
-
#
|
458
|
-
#
|
459
|
-
#
|
460
|
-
#
|
461
|
-
#
|
462
|
-
# even without any explicit support.
|
463
|
-
# - Updates #list to accept selection options: +SUBSCRIBED+, +REMOTE+, and
|
464
|
-
# +RECURSIVEMATCH+, and return options: +SUBSCRIBED+ and +CHILDREN+.
|
465
|
-
#++
|
466
|
-
#
|
467
|
-
#--
|
468
|
-
# ==== RFC5819 +LIST-STATUS+
|
469
|
-
# TODO...
|
470
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
|
471
|
-
# - Updates #list with +STATUS+ return option.
|
472
|
-
#++
|
473
|
-
#
|
474
|
-
# ==== +XLIST+ (non-standard, deprecated)
|
484
|
+
# ==== +X-GM-EXT-1+
|
485
|
+
# +X-GM-EXT-1+ is a non-standard Gmail extension. See {Google's
|
486
|
+
# documentation}[https://developers.google.com/gmail/imap/imap-extensions].
|
487
|
+
# - Updates #fetch and #uid_fetch with support for +X-GM-MSGID+ (unique
|
488
|
+
# message ID), +X-GM-THRID+ (thread ID), and +X-GM-LABELS+ (Gmail labels).
|
489
|
+
# - Updates #search with the +X-GM-RAW+ search attribute.
|
475
490
|
# - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses.
|
476
491
|
#
|
477
|
-
|
478
|
-
#
|
479
|
-
# TODO...
|
480
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
|
481
|
-
# - Updates #list with the +SPECIAL-USE+ selection and return options.
|
482
|
-
#++
|
492
|
+
# *NOTE:* The +OBJECTID+ extension should replace +X-GM-MSGID+ and
|
493
|
+
# +X-GM-THRID+, but Gmail does not support it (as of 2023-11-10).
|
483
494
|
#
|
484
495
|
# ==== RFC6851: +MOVE+
|
485
|
-
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]
|
486
|
-
#
|
496
|
+
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
497
|
+
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
487
498
|
# - #move, #uid_move: Moves the specified messages to the end of the
|
488
499
|
# specified destination mailbox, expunging them from the current mailbox.
|
489
500
|
#
|
490
|
-
|
491
|
-
# ==== RFC6855: UTF8=ACCEPT
|
492
|
-
# TODO...
|
493
|
-
# ==== RFC6855: UTF8=ONLY
|
494
|
-
# TODO...
|
495
|
-
#++
|
496
|
-
#
|
497
|
-
#--
|
498
|
-
# ==== RFC7888: <tt>LITERAL+</tt>, +LITERAL-+
|
499
|
-
# TODO...
|
500
|
-
# ==== RFC7162: +QRESYNC+
|
501
|
-
# TODO...
|
502
|
-
# ==== RFC7162: +CONDSTORE+
|
503
|
-
# TODO...
|
504
|
-
# ==== RFC8474: +OBJECTID+
|
505
|
-
# TODO...
|
506
|
-
# ==== RFC9208: +QUOTA+
|
507
|
-
# TODO...
|
508
|
-
#++
|
501
|
+
# ==== RFC6855: <tt>UTF8=ACCEPT</tt>, <tt>UTF8=ONLY</tt>
|
509
502
|
#
|
510
|
-
#
|
503
|
+
# - See #enable for information about support for UTF-8 string encoding.
|
511
504
|
#
|
512
|
-
#
|
513
|
-
#
|
514
|
-
# - #
|
515
|
-
#
|
516
|
-
# - #
|
517
|
-
#
|
518
|
-
#
|
505
|
+
# ==== RFC7162: +CONDSTORE+
|
506
|
+
#
|
507
|
+
# - Updates #enable with +CONDSTORE+ parameter. +CONDSTORE+ will also be
|
508
|
+
# enabled by using any of the extension's command parameters, listed below.
|
509
|
+
# - Updates #status with the +HIGHESTMODSEQ+ status attribute.
|
510
|
+
# - Updates #select and #examine with the +condstore+ modifier, and adds
|
511
|
+
# either a +HIGHESTMODSEQ+ or +NOMODSEQ+ ResponseCode to the responses.
|
512
|
+
# - Updates #search, #uid_search, #sort, and #uid_sort with the +MODSEQ+
|
513
|
+
# search criterion, and adds SearchResult#modseq to the search response.
|
514
|
+
# - Updates #thread and #uid_thread with the +MODSEQ+ search criterion
|
515
|
+
# <em>(but thread responses are unchanged)</em>.
|
516
|
+
# - Updates #fetch and #uid_fetch with the +changedsince+ modifier and
|
517
|
+
# +MODSEQ+ FetchData attribute.
|
518
|
+
# - Updates #store and #uid_store with the +unchangedsince+ modifier and adds
|
519
|
+
# the +MODIFIED+ ResponseCode to the tagged response.
|
520
|
+
#
|
521
|
+
# ==== RFC8438: <tt>STATUS=SIZE</tt>
|
522
|
+
# - Updates #status with the +SIZE+ status attribute.
|
519
523
|
#
|
524
|
+
# ==== RFC8474: +OBJECTID+
|
525
|
+
# - Adds +MAILBOXID+ ResponseCode to #create tagged response.
|
526
|
+
# - Adds +MAILBOXID+ ResponseCode to #select and #examine untagged response.
|
527
|
+
# - Updates #fetch and #uid_fetch with the +EMAILID+ and +THREADID+ items.
|
528
|
+
# See FetchData#emailid and FetchData#emailid.
|
529
|
+
# - Updates #status with support for the +MAILBOXID+ status attribute.
|
520
530
|
#
|
521
531
|
# == References
|
522
|
-
#--
|
523
|
-
# TODO: Consider moving references list to REFERENCES.md or REFERENCES.rdoc.
|
524
|
-
#++
|
525
532
|
#
|
526
533
|
# [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
|
527
534
|
# Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
|
@@ -622,27 +629,21 @@ module Net
|
|
622
629
|
# RFC 1864, DOI 10.17487/RFC1864, October 1995,
|
623
630
|
# <https://www.rfc-editor.org/info/rfc1864>.
|
624
631
|
#
|
625
|
-
|
626
|
-
#
|
632
|
+
# [RFC3503[https://tools.ietf.org/html/rfc3503]]::
|
633
|
+
# Melnikov, A., "Message Disposition Notification (MDN)
|
634
|
+
# profile for Internet Message Access Protocol (IMAP)",
|
635
|
+
# RFC 3503, DOI 10.17487/RFC3503, March 2003,
|
636
|
+
# <https://www.rfc-editor.org/info/rfc3503>.
|
627
637
|
#
|
628
|
-
#
|
629
|
-
# Melnikov, A., "Message Disposition Notification (MDN)
|
630
|
-
# profile for Internet Message Access Protocol (IMAP)",
|
631
|
-
# RFC 3503, DOI 10.17487/RFC3503, March 2003,
|
632
|
-
# <https://www.rfc-editor.org/info/rfc3503>.
|
633
|
-
#++
|
638
|
+
# === \IMAP Extensions
|
634
639
|
#
|
635
|
-
# === Supported \IMAP Extensions
|
636
|
-
#
|
637
|
-
# [QUOTA[https://tools.ietf.org/html/rfc2087]]::
|
638
|
-
# Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
|
639
|
-
# January 1997, <https://www.rfc-editor.org/info/rfc2087>.
|
640
|
-
#--
|
641
|
-
# TODO: test compatibility with updated QUOTA extension:
|
642
640
|
# [QUOTA[https://tools.ietf.org/html/rfc9208]]::
|
643
641
|
# Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
|
644
642
|
# March 2022, <https://www.rfc-editor.org/info/rfc9208>.
|
645
|
-
|
643
|
+
#
|
644
|
+
# <em>Note: obsoletes</em>
|
645
|
+
# RFC-2087[https://tools.ietf.org/html/rfc2087]<em> (January 1997)</em>.
|
646
|
+
# <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
|
646
647
|
# [IDLE[https://tools.ietf.org/html/rfc2177]]::
|
647
648
|
# Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
|
648
649
|
# June 1997, <https://www.rfc-editor.org/info/rfc2177>.
|
@@ -652,6 +653,10 @@ module Net
|
|
652
653
|
# [ID[https://tools.ietf.org/html/rfc2971]]::
|
653
654
|
# Showalter, T., "IMAP4 ID extension", RFC 2971, DOI 10.17487/RFC2971,
|
654
655
|
# October 2000, <https://www.rfc-editor.org/info/rfc2971>.
|
656
|
+
# [BINARY[https://tools.ietf.org/html/rfc3516]]::
|
657
|
+
# Nerenberg, L., "IMAP4 Binary Content Extension", RFC 3516,
|
658
|
+
# DOI 10.17487/RFC3516, April 2003,
|
659
|
+
# <https://www.rfc-editor.org/info/rfc3516>.
|
655
660
|
# [ACL[https://tools.ietf.org/html/rfc4314]]::
|
656
661
|
# Melnikov, A., "IMAP4 Access Control List (ACL) Extension", RFC 4314,
|
657
662
|
# DOI 10.17487/RFC4314, December 2005,
|
@@ -675,31 +680,54 @@ module Net
|
|
675
680
|
# Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
|
676
681
|
# (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
|
677
682
|
# <https://www.rfc-editor.org/info/rfc6851>.
|
683
|
+
# [UTF8=ACCEPT[https://tools.ietf.org/html/rfc6855]]::
|
684
|
+
# [UTF8=ONLY[https://tools.ietf.org/html/rfc6855]]::
|
685
|
+
# Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed.,
|
686
|
+
# "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013,
|
687
|
+
# <https://www.rfc-editor.org/info/rfc6855>.
|
688
|
+
# [CONDSTORE[https://tools.ietf.org/html/rfc7162]]::
|
689
|
+
# [QRESYNC[https://tools.ietf.org/html/rfc7162]]::
|
690
|
+
# Melnikov, A. and D. Cridland, "IMAP Extensions: Quick Flag Changes
|
691
|
+
# Resynchronization (CONDSTORE) and Quick Mailbox Resynchronization
|
692
|
+
# (QRESYNC)", RFC 7162, DOI 10.17487/RFC7162, May 2014,
|
693
|
+
# <https://www.rfc-editor.org/info/rfc7162>.
|
694
|
+
# [OBJECTID[https://tools.ietf.org/html/rfc8474]]::
|
695
|
+
# Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
|
696
|
+
# RFC 8474, DOI 10.17487/RFC8474, September 2018,
|
697
|
+
# <https://www.rfc-editor.org/info/rfc8474>.
|
678
698
|
#
|
679
699
|
# === IANA registries
|
680
|
-
#
|
681
700
|
# * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
|
682
701
|
# * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
|
683
702
|
# * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
|
684
703
|
# * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
|
685
704
|
# * {IMAP Threading Algorithms}[https://www.iana.org/assignments/imap-threading-algorithms/imap-threading-algorithms.xhtml]
|
686
|
-
#--
|
687
|
-
# * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
|
688
|
-
# * [{LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
|
689
|
-
# * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
|
690
|
-
# * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
|
691
|
-
# * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
|
692
|
-
# * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
|
693
|
-
#++
|
694
705
|
# * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
695
706
|
# * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]:
|
696
707
|
# +imap+: tcp/143, +imaps+: tcp/993
|
697
708
|
# * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
|
698
709
|
# +imap+
|
699
710
|
# * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
|
711
|
+
# ===== For currently unsupported features:
|
712
|
+
# * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
|
713
|
+
# * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
|
714
|
+
# * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
|
715
|
+
# * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
|
716
|
+
# * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
|
717
|
+
# * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
|
700
718
|
#
|
701
719
|
class IMAP < Protocol
|
702
|
-
VERSION = "0.
|
720
|
+
VERSION = "0.4.9"
|
721
|
+
|
722
|
+
# Aliases for supported capabilities, to be used with the #enable command.
|
723
|
+
ENABLE_ALIASES = {
|
724
|
+
utf8: "UTF8=ACCEPT",
|
725
|
+
"UTF8=ONLY" => "UTF8=ACCEPT",
|
726
|
+
}.freeze
|
727
|
+
|
728
|
+
autoload :SASL, File.expand_path("imap/sasl", __dir__)
|
729
|
+
autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
|
730
|
+
autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
|
703
731
|
|
704
732
|
include MonitorMixin
|
705
733
|
if defined?(OpenSSL::SSL)
|
@@ -707,35 +735,6 @@ module Net
|
|
707
735
|
include SSL
|
708
736
|
end
|
709
737
|
|
710
|
-
# Returns the initial greeting the server, an UntaggedResponse.
|
711
|
-
attr_reader :greeting
|
712
|
-
|
713
|
-
# Returns a hash with arrays of unhandled <em>non-+nil+</em>
|
714
|
-
# UntaggedResponse#data keyed by UntaggedResponse#name, and
|
715
|
-
# ResponseCode#data keyed by ResponseCode#name.
|
716
|
-
#
|
717
|
-
# For example:
|
718
|
-
#
|
719
|
-
# imap.select("inbox")
|
720
|
-
# p imap.responses["EXISTS"][-1]
|
721
|
-
# #=> 2
|
722
|
-
# p imap.responses["UIDVALIDITY"][-1]
|
723
|
-
# #=> 968263756
|
724
|
-
attr_reader :responses
|
725
|
-
|
726
|
-
# Returns all response handlers.
|
727
|
-
attr_reader :response_handlers
|
728
|
-
|
729
|
-
# Seconds to wait until a connection is opened.
|
730
|
-
# If the IMAP object cannot open a connection within this time,
|
731
|
-
# it raises a Net::OpenTimeout exception. The default value is 30 seconds.
|
732
|
-
attr_reader :open_timeout
|
733
|
-
|
734
|
-
# Seconds to wait until an IDLE response is received.
|
735
|
-
attr_reader :idle_response_timeout
|
736
|
-
|
737
|
-
attr_accessor :client_thread # :nodoc:
|
738
|
-
|
739
738
|
# Returns the debug mode.
|
740
739
|
def self.debug
|
741
740
|
return @@debug
|
@@ -762,9 +761,175 @@ module Net
|
|
762
761
|
alias default_ssl_port default_tls_port
|
763
762
|
end
|
764
763
|
|
764
|
+
# Returns the initial greeting the server, an UntaggedResponse.
|
765
|
+
attr_reader :greeting
|
766
|
+
|
767
|
+
# Seconds to wait until a connection is opened.
|
768
|
+
# If the IMAP object cannot open a connection within this time,
|
769
|
+
# it raises a Net::OpenTimeout exception. The default value is 30 seconds.
|
770
|
+
attr_reader :open_timeout
|
771
|
+
|
772
|
+
# Seconds to wait until an IDLE response is received.
|
773
|
+
attr_reader :idle_response_timeout
|
774
|
+
|
775
|
+
# The hostname this client connected to
|
776
|
+
attr_reader :host
|
777
|
+
|
778
|
+
# The port this client connected to
|
779
|
+
attr_reader :port
|
780
|
+
|
781
|
+
# Returns the
|
782
|
+
# {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]
|
783
|
+
# used by the SSLSocket when TLS is attempted, even when the TLS handshake
|
784
|
+
# is unsuccessful. The context object will be frozen.
|
785
|
+
#
|
786
|
+
# Returns +nil+ for a plaintext connection.
|
787
|
+
attr_reader :ssl_ctx
|
788
|
+
|
789
|
+
# Returns the parameters that were sent to #ssl_ctx
|
790
|
+
# {set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]
|
791
|
+
# when the connection tries to use TLS (even when unsuccessful).
|
792
|
+
#
|
793
|
+
# Returns +false+ for a plaintext connection.
|
794
|
+
attr_reader :ssl_ctx_params
|
795
|
+
|
796
|
+
# Creates a new Net::IMAP object and connects it to the specified
|
797
|
+
# +host+.
|
798
|
+
#
|
799
|
+
# ==== Options
|
800
|
+
#
|
801
|
+
# Accepts the following options:
|
802
|
+
#
|
803
|
+
# [port]
|
804
|
+
# Port number. Defaults to 993 when +ssl+ is truthy, and 143 otherwise.
|
805
|
+
#
|
806
|
+
# [ssl]
|
807
|
+
# If +true+, the connection will use TLS with the default params set by
|
808
|
+
# {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params].
|
809
|
+
# If +ssl+ is a hash, it's passed to
|
810
|
+
# {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
|
811
|
+
# the keys are names of attribute assignment methods on
|
812
|
+
# SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
|
813
|
+
#
|
814
|
+
# [open_timeout]
|
815
|
+
# Seconds to wait until a connection is opened
|
816
|
+
# [idle_response_timeout]
|
817
|
+
# Seconds to wait until an IDLE response is received
|
818
|
+
#
|
819
|
+
# See DeprecatedClientOptions.new for deprecated arguments.
|
820
|
+
#
|
821
|
+
# ==== Examples
|
822
|
+
#
|
823
|
+
# Connect to cleartext port 143 at mail.example.com and recieve the server greeting:
|
824
|
+
# imap = Net::IMAP.new('mail.example.com', ssl: false) # => #<Net::IMAP:0x00007f79b0872bd0>
|
825
|
+
# imap.port => 143
|
826
|
+
# imap.tls_verified? => false
|
827
|
+
# imap.greeting => name: ("OK" | "PREAUTH") => status
|
828
|
+
# status # => "OK"
|
829
|
+
# # The client is connected in the "Not Authenticated" state.
|
830
|
+
#
|
831
|
+
# Connect with TLS to port 993
|
832
|
+
# imap = Net::IMAP.new('mail.example.com', ssl: true) # => #<Net::IMAP:0x00007f79b0872bd0>
|
833
|
+
# imap.port => 993
|
834
|
+
# imap.tls_verified? => true
|
835
|
+
# imap.greeting => name: (/OK/i | /PREAUTH/i) => status
|
836
|
+
# case status
|
837
|
+
# in /OK/i
|
838
|
+
# # The client is connected in the "Not Authenticated" state.
|
839
|
+
# imap.authenticate("PLAIN", "joe_user", "joes_password")
|
840
|
+
# in /PREAUTH/i
|
841
|
+
# # The client is connected in the "Authenticated" state.
|
842
|
+
# end
|
843
|
+
#
|
844
|
+
# Connect with prior authentication, for example using an SSL certificate:
|
845
|
+
# ssl_ctx_params = {
|
846
|
+
# cert: OpenSSL::X509::Certificate.new(File.read("client.crt")),
|
847
|
+
# key: OpenSSL::PKey::EC.new(File.read('client.key')),
|
848
|
+
# extra_chain_cert: [
|
849
|
+
# OpenSSL::X509::Certificate.new(File.read("intermediate.crt")),
|
850
|
+
# ],
|
851
|
+
# }
|
852
|
+
# imap = Net::IMAP.new('mail.example.com', ssl: ssl_ctx_params)
|
853
|
+
# imap.port => 993
|
854
|
+
# imap.tls_verified? => true
|
855
|
+
# imap.greeting => name: "PREAUTH"
|
856
|
+
# # The client is connected in the "Authenticated" state.
|
857
|
+
#
|
858
|
+
# ==== Exceptions
|
859
|
+
#
|
860
|
+
# The most common errors are:
|
861
|
+
#
|
862
|
+
# [Errno::ECONNREFUSED]
|
863
|
+
# Connection refused by +host+ or an intervening firewall.
|
864
|
+
# [Errno::ETIMEDOUT]
|
865
|
+
# Connection timed out (possibly due to packets being dropped by an
|
866
|
+
# intervening firewall).
|
867
|
+
# [Errno::ENETUNREACH]
|
868
|
+
# There is no route to that network.
|
869
|
+
# [SocketError]
|
870
|
+
# Hostname not known or other socket error.
|
871
|
+
# [Net::IMAP::ByeResponseError]
|
872
|
+
# Connected to the host successfully, but it immediately said goodbye.
|
873
|
+
#
|
874
|
+
def initialize(host, port: nil, ssl: nil,
|
875
|
+
open_timeout: 30, idle_response_timeout: 5)
|
876
|
+
super()
|
877
|
+
# Config options
|
878
|
+
@host = host
|
879
|
+
@port = port || (ssl ? SSL_PORT : PORT)
|
880
|
+
@open_timeout = Integer(open_timeout)
|
881
|
+
@idle_response_timeout = Integer(idle_response_timeout)
|
882
|
+
@ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
|
883
|
+
|
884
|
+
# Basic Client State
|
885
|
+
@utf8_strings = false
|
886
|
+
@debug_output_bol = true
|
887
|
+
@exception = nil
|
888
|
+
@greeting = nil
|
889
|
+
@capabilities = nil
|
890
|
+
|
891
|
+
# Client Protocol Reciever
|
892
|
+
@parser = ResponseParser.new
|
893
|
+
@responses = Hash.new {|h, k| h[k] = [] }
|
894
|
+
@response_handlers = []
|
895
|
+
@receiver_thread = nil
|
896
|
+
@receiver_thread_exception = nil
|
897
|
+
@receiver_thread_terminating = false
|
898
|
+
|
899
|
+
# Client Protocol Sender (including state for currently running commands)
|
900
|
+
@tag_prefix = "RUBY"
|
901
|
+
@tagno = 0
|
902
|
+
@tagged_responses = {}
|
903
|
+
@tagged_response_arrival = new_cond
|
904
|
+
@continued_command_tag = nil
|
905
|
+
@continuation_request_arrival = new_cond
|
906
|
+
@continuation_request_exception = nil
|
907
|
+
@idle_done_cond = nil
|
908
|
+
@logout_command_tag = nil
|
909
|
+
|
910
|
+
# Connection
|
911
|
+
@tls_verified = false
|
912
|
+
@sock = tcp_socket(@host, @port)
|
913
|
+
start_tls_session if ssl_ctx
|
914
|
+
start_imap_connection
|
915
|
+
|
916
|
+
# DEPRECATED: to remove in next version
|
917
|
+
@client_thread = Thread.current
|
918
|
+
end
|
919
|
+
|
920
|
+
# Returns true after the TLS negotiation has completed and the remote
|
921
|
+
# hostname has been verified. Returns false when TLS has been established
|
922
|
+
# but peer verification was disabled.
|
923
|
+
def tls_verified?; @tls_verified end
|
924
|
+
|
925
|
+
def client_thread # :nodoc:
|
926
|
+
warn "Net::IMAP#client_thread is deprecated and will be removed soon."
|
927
|
+
@client_thread
|
928
|
+
end
|
929
|
+
|
765
930
|
# Disconnects from the server.
|
766
931
|
#
|
767
|
-
# Related: #logout
|
932
|
+
# Related: #logout, #logout!
|
768
933
|
def disconnect
|
769
934
|
return if disconnected?
|
770
935
|
begin
|
@@ -794,62 +959,123 @@ module Net
|
|
794
959
|
return @sock.closed?
|
795
960
|
end
|
796
961
|
|
797
|
-
#
|
798
|
-
#
|
799
|
-
#
|
962
|
+
# Returns whether the server supports a given +capability+. When available,
|
963
|
+
# cached #capabilities are used without sending a new #capability command to
|
964
|
+
# the server.
|
800
965
|
#
|
801
|
-
#
|
802
|
-
#
|
803
|
-
# of all standard capabilities, and their reference RFCs.
|
966
|
+
# <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
|
967
|
+
# behaviour according to the server's advertised #capabilities.</em>
|
804
968
|
#
|
805
|
-
#
|
806
|
-
#
|
807
|
-
#
|
808
|
-
|
809
|
-
|
810
|
-
|
969
|
+
# See Net::IMAP@Capabilities for more about \IMAP capabilities.
|
970
|
+
#
|
971
|
+
# Related: #auth_capable?, #capabilities, #capability, #enable
|
972
|
+
def capable?(capability) capabilities.include? capability.to_s.upcase end
|
973
|
+
alias capability? capable?
|
974
|
+
|
975
|
+
# Returns the server capabilities. When available, cached capabilities are
|
976
|
+
# used without sending a new #capability command to the server.
|
977
|
+
#
|
978
|
+
# To ensure a case-insensitive comparison, #capable? can be used instead.
|
979
|
+
#
|
980
|
+
# <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
|
981
|
+
# behaviour according to the server's advertised #capabilities.</em>
|
982
|
+
#
|
983
|
+
# See Net::IMAP@Capabilities for more about \IMAP capabilities.
|
984
|
+
#
|
985
|
+
# Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable
|
986
|
+
def capabilities
|
987
|
+
@capabilities || capability
|
988
|
+
end
|
989
|
+
|
990
|
+
# Returns the #authenticate mechanisms that the server claims to support.
|
991
|
+
# These are derived from the #capabilities with an <tt>AUTH=</tt> prefix.
|
992
|
+
#
|
993
|
+
# This may be different when the connection is cleartext or using TLS. Most
|
994
|
+
# servers will drop all <tt>AUTH=</tt> mechanisms from #capabilities after
|
995
|
+
# the connection has authenticated.
|
811
996
|
#
|
812
|
-
#
|
813
|
-
#
|
997
|
+
# imap = Net::IMAP.new(hostname, ssl: false)
|
998
|
+
# imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"]
|
999
|
+
# imap.auth_mechanisms # => []
|
814
1000
|
#
|
815
|
-
#
|
1001
|
+
# imap.starttls
|
1002
|
+
# imap.capabilities # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2",
|
1003
|
+
# # "AUTH=OAUTHBEARER"]
|
1004
|
+
# imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"]
|
816
1005
|
#
|
817
|
-
#
|
818
|
-
#
|
819
|
-
# <tt>AUTH=PLAIN</tt>, and +LOGINDISABLED+ capabilities, and clients must
|
820
|
-
# respect their presence or absence. See the capabilites requirements on
|
821
|
-
# #starttls, #login, and #authenticate.
|
1006
|
+
# imap.authenticate("XOAUTH2", username, oauth2_access_token)
|
1007
|
+
# imap.auth_mechanisms # => []
|
822
1008
|
#
|
823
|
-
#
|
1009
|
+
# Related: #authenticate, #auth_capable?, #capabilities
|
1010
|
+
def auth_mechanisms
|
1011
|
+
capabilities
|
1012
|
+
.grep(/\AAUTH=/i)
|
1013
|
+
.map { _1.delete_prefix("AUTH=") }
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
# Returns whether the server supports a given SASL +mechanism+ for use with
|
1017
|
+
# the #authenticate command. The +mechanism+ is supported when
|
1018
|
+
# #capabilities includes <tt>"AUTH=#{mechanism.to_s.upcase}"</tt>. When
|
1019
|
+
# available, cached capabilities are used without sending a new #capability
|
1020
|
+
# command to the server.
|
824
1021
|
#
|
825
|
-
#
|
826
|
-
#
|
827
|
-
#
|
828
|
-
# compatible behavior, such as response codes or mailbox attributes, may
|
829
|
-
# be sent at any time.
|
1022
|
+
# imap.capable? "AUTH=PLAIN" # => true
|
1023
|
+
# imap.auth_capable? "PLAIN" # => true
|
1024
|
+
# imap.auth_capable? "blurdybloop" # => false
|
830
1025
|
#
|
831
|
-
#
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
1026
|
+
# Related: #authenticate, #auth_mechanisms, #capable?, #capabilities
|
1027
|
+
def auth_capable?(mechanism)
|
1028
|
+
capable? "AUTH=#{mechanism}"
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
# Returns whether capabilities have been cached. When true, #capable? and
|
1032
|
+
# #capabilities don't require sending a #capability command to the server.
|
836
1033
|
#
|
837
|
-
#
|
1034
|
+
# See Net::IMAP@Capabilities for more about \IMAP capabilities.
|
838
1035
|
#
|
839
|
-
#
|
840
|
-
|
841
|
-
|
842
|
-
|
1036
|
+
# Related: #capable?, #capability, #clear_cached_capabilities
|
1037
|
+
def capabilities_cached?
|
1038
|
+
!!@capabilities
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
# Clears capabilities that have been remembered by the Net::IMAP client.
|
1042
|
+
# This forces a #capability command to be sent the next time a #capabilities
|
1043
|
+
# query method is called.
|
843
1044
|
#
|
844
|
-
#
|
845
|
-
#
|
846
|
-
#
|
847
|
-
# #starttls is sent clear-text and cannot be trusted.
|
1045
|
+
# Net::IMAP automatically discards its cached capabilities when they can
|
1046
|
+
# change. Explicitly calling this _should_ be unnecessary for well-behaved
|
1047
|
+
# servers.
|
848
1048
|
#
|
1049
|
+
# Related: #capable?, #capability, #capabilities_cached?
|
1050
|
+
def clear_cached_capabilities
|
1051
|
+
synchronize do
|
1052
|
+
clear_responses("CAPABILITY")
|
1053
|
+
@capabilities = nil
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
# Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
|
1058
|
+
# and returns an array of capabilities that are supported by the server.
|
1059
|
+
# The result is stored for use by #capable? and #capabilities.
|
1060
|
+
#
|
1061
|
+
# <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
|
1062
|
+
# behaviour according to the server's advertised #capabilities.</em>
|
1063
|
+
#
|
1064
|
+
# Net::IMAP automatically stores and discards capability data according to
|
1065
|
+
# the requirements and recommendations in
|
1066
|
+
# {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
|
1067
|
+
# {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
|
1068
|
+
# {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
|
1069
|
+
# Use #capable?, #auth_capable?, or #capabilities to this cache and avoid
|
1070
|
+
# sending the #capability command unnecessarily.
|
1071
|
+
#
|
1072
|
+
# See Net::IMAP@Capabilities for more about \IMAP capabilities.
|
1073
|
+
#
|
1074
|
+
# Related: #capable?, #auth_capable?, #capability, #enable
|
849
1075
|
def capability
|
850
1076
|
synchronize do
|
851
1077
|
send_command("CAPABILITY")
|
852
|
-
|
1078
|
+
@capabilities = clear_responses("CAPABILITY").last.freeze
|
853
1079
|
end
|
854
1080
|
end
|
855
1081
|
|
@@ -860,8 +1086,7 @@ module Net
|
|
860
1086
|
# Note that the user should first check if the server supports the ID
|
861
1087
|
# capability. For example:
|
862
1088
|
#
|
863
|
-
#
|
864
|
-
# if capabilities.include?("ID")
|
1089
|
+
# if capable?(:ID)
|
865
1090
|
# id = imap.id(
|
866
1091
|
# name: "my IMAP client (ruby)",
|
867
1092
|
# version: MyIMAP::VERSION,
|
@@ -875,11 +1100,11 @@ module Net
|
|
875
1100
|
# ===== Capabilities
|
876
1101
|
#
|
877
1102
|
# The server's capabilities must include +ID+
|
878
|
-
# [RFC2971[https://tools.ietf.org/html/rfc2971]]
|
1103
|
+
# [RFC2971[https://tools.ietf.org/html/rfc2971]].
|
879
1104
|
def id(client_id=nil)
|
880
1105
|
synchronize do
|
881
1106
|
send_command("ID", ClientID.new(client_id))
|
882
|
-
|
1107
|
+
clear_responses("ID").last
|
883
1108
|
end
|
884
1109
|
end
|
885
1110
|
|
@@ -888,7 +1113,7 @@ module Net
|
|
888
1113
|
#
|
889
1114
|
# This allows the server to send unsolicited untagged EXPUNGE #responses,
|
890
1115
|
# but does not execute any client request. \IMAP servers are permitted to
|
891
|
-
# send unsolicited untagged responses at any time, except for
|
1116
|
+
# send unsolicited untagged responses at any time, except for +EXPUNGE+:
|
892
1117
|
#
|
893
1118
|
# * +EXPUNGE+ can only be sent while a command is in progress.
|
894
1119
|
# * +EXPUNGE+ must _not_ be sent during #fetch, #store, or #search.
|
@@ -903,15 +1128,43 @@ module Net
|
|
903
1128
|
# to inform the command to inform the server that the client is done with
|
904
1129
|
# the connection.
|
905
1130
|
#
|
906
|
-
# Related: #disconnect
|
1131
|
+
# Related: #disconnect, #logout!
|
907
1132
|
def logout
|
908
1133
|
send_command("LOGOUT")
|
909
1134
|
end
|
910
1135
|
|
1136
|
+
# Calls #logout then, after receiving the TaggedResponse for the +LOGOUT+,
|
1137
|
+
# calls #disconnect. Returns the TaggedResponse from +LOGOUT+. Returns
|
1138
|
+
# +nil+ when the client is already disconnected, in contrast to #logout
|
1139
|
+
# which raises an exception.
|
1140
|
+
#
|
1141
|
+
# If #logout raises a StandardError, a warning will be printed but the
|
1142
|
+
# exception will not be re-raised.
|
1143
|
+
#
|
1144
|
+
# This is useful in situations where the connection must be dropped, for
|
1145
|
+
# example for security or after tests. If logout errors need to be handled,
|
1146
|
+
# use #logout and #disconnect instead.
|
1147
|
+
#
|
1148
|
+
# Related: #logout, #disconnect
|
1149
|
+
def logout!
|
1150
|
+
logout unless disconnected?
|
1151
|
+
rescue => ex
|
1152
|
+
warn "%s during <Net::IMAP %s:%s> logout!: %s" % [
|
1153
|
+
ex.class, host, port, ex
|
1154
|
+
]
|
1155
|
+
ensure
|
1156
|
+
disconnect
|
1157
|
+
end
|
1158
|
+
|
911
1159
|
# Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1]
|
912
1160
|
# to start a TLS session.
|
913
1161
|
#
|
914
|
-
# Any +options+ are forwarded to
|
1162
|
+
# Any +options+ are forwarded directly to
|
1163
|
+
# {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
|
1164
|
+
# the keys are names of attribute assignment methods on
|
1165
|
+
# SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
|
1166
|
+
#
|
1167
|
+
# See DeprecatedClientOptions#starttls for deprecated arguments.
|
915
1168
|
#
|
916
1169
|
# This method returns after TLS negotiation and hostname verification are
|
917
1170
|
# both successful. Any error indicates that the connection has not been
|
@@ -921,132 +1174,156 @@ module Net
|
|
921
1174
|
# >>>
|
922
1175
|
# Any #response_handlers added before STARTTLS should be aware that the
|
923
1176
|
# TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation.
|
924
|
-
# TLS
|
1177
|
+
# TLS starts immediately _after_ that response. Any response code sent
|
1178
|
+
# with the response (e.g. CAPABILITY) is insecure and cannot be trusted.
|
925
1179
|
#
|
926
1180
|
# Related: Net::IMAP.new, #login, #authenticate
|
927
1181
|
#
|
928
1182
|
# ===== Capability
|
929
|
-
#
|
930
|
-
#
|
1183
|
+
# Clients should not call #starttls unless the server advertises the
|
1184
|
+
# +STARTTLS+ capability.
|
931
1185
|
#
|
932
1186
|
# Server capabilities may change after #starttls, #login, and #authenticate.
|
933
|
-
# Cached capabilities
|
934
|
-
#
|
935
|
-
# The TaggedResponse to #starttls is sent clear-text, so the server <em>must
|
936
|
-
# *not*</em> send capabilities in the #starttls response and clients <em>must
|
937
|
-
# not</em> use them if they are sent. Servers will generally send an
|
938
|
-
# unsolicited untagged response immeditely _after_ #starttls completes.
|
1187
|
+
# Cached #capabilities will be cleared when this method completes.
|
939
1188
|
#
|
940
|
-
def starttls(options
|
1189
|
+
def starttls(**options)
|
1190
|
+
@ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
|
941
1191
|
send_command("STARTTLS") do |resp|
|
942
1192
|
if resp.kind_of?(TaggedResponse) && resp.name == "OK"
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
options = create_ssl_params(certs, verify)
|
947
|
-
rescue NoMethodError
|
948
|
-
end
|
949
|
-
start_tls_session(options)
|
1193
|
+
clear_cached_capabilities
|
1194
|
+
clear_responses
|
1195
|
+
start_tls_session
|
950
1196
|
end
|
951
1197
|
end
|
952
1198
|
end
|
953
1199
|
|
954
1200
|
# :call-seq:
|
955
|
-
# authenticate(mechanism,
|
956
|
-
# authenticate(mech, *creds, **props) {|prop, auth| val } -> ok_resp
|
957
|
-
# authenticate(mechanism, authnid, credentials, authzid=nil) -> ok_resp
|
958
|
-
# authenticate(mechanism, **properties) -> ok_resp
|
959
|
-
# authenticate(mechanism) {|propname, authctx| prop_value } -> ok_resp
|
1201
|
+
# authenticate(mechanism, *, sasl_ir: true, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp
|
960
1202
|
#
|
961
1203
|
# Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
|
962
1204
|
# to authenticate the client. If successful, the connection enters the
|
963
1205
|
# "_authenticated_" state.
|
964
1206
|
#
|
965
1207
|
# +mechanism+ is the name of the \SASL authentication mechanism to be used.
|
966
|
-
# All other arguments are forwarded to the authenticator for the requested
|
967
|
-
# mechanism. The listed call signatures are suggestions. <em>The
|
968
|
-
# documentation for each individual mechanism must be consulted for its
|
969
|
-
# specific parameters.</em>
|
970
1208
|
#
|
971
|
-
#
|
1209
|
+
# +sasl_ir+ allows or disallows sending an "initial response" (see the
|
1210
|
+
# +SASL-IR+ capability, below).
|
972
1211
|
#
|
973
|
-
#
|
1212
|
+
# All other arguments are forwarded to the registered SASL authenticator for
|
1213
|
+
# the requested mechanism. <em>The documentation for each individual
|
1214
|
+
# mechanism must be consulted for its specific parameters.</em>
|
974
1215
|
#
|
975
|
-
#
|
1216
|
+
# Related: #login, #starttls, #auth_capable?, #auth_mechanisms
|
976
1217
|
#
|
977
|
-
#
|
978
|
-
# Login using clear-text username and password.
|
1218
|
+
# ==== Mechanisms
|
979
1219
|
#
|
980
|
-
#
|
981
|
-
#
|
982
|
-
# Non-standard and obsoleted by +OAUTHBEARER+, but widely
|
983
|
-
# supported.
|
1220
|
+
# Each mechanism has different properties and requirements. Please consult
|
1221
|
+
# the documentation for the specific mechanisms you are using:
|
984
1222
|
#
|
985
|
-
#
|
986
|
-
#
|
987
|
-
#
|
1223
|
+
# +ANONYMOUS+::
|
1224
|
+
# See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
|
1225
|
+
#
|
1226
|
+
# Allows the user to gain access to public services or resources without
|
1227
|
+
# authenticating or disclosing an identity.
|
1228
|
+
#
|
1229
|
+
# +EXTERNAL+::
|
1230
|
+
# See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
|
1231
|
+
#
|
1232
|
+
# Authenticates using already established credentials, such as a TLS
|
1233
|
+
# certificate or IPsec.
|
1234
|
+
#
|
1235
|
+
# +OAUTHBEARER+::
|
1236
|
+
# See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator].
|
988
1237
|
#
|
989
|
-
#
|
1238
|
+
# Login using an OAuth2 Bearer token. This is the standard mechanism
|
1239
|
+
# for using OAuth2 with \SASL, but it is not yet deployed as widely as
|
1240
|
+
# +XOAUTH2+.
|
990
1241
|
#
|
991
|
-
#
|
1242
|
+
# +PLAIN+::
|
1243
|
+
# See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator].
|
992
1244
|
#
|
993
|
-
#
|
1245
|
+
# Login using clear-text username and password.
|
994
1246
|
#
|
995
|
-
#
|
1247
|
+
# +SCRAM-SHA-1+::
|
1248
|
+
# +SCRAM-SHA-256+::
|
1249
|
+
# See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator].
|
996
1250
|
#
|
997
|
-
#
|
998
|
-
#
|
1251
|
+
# Login by username and password. The password is not sent to the
|
1252
|
+
# server but is used in a salted challenge/response exchange.
|
1253
|
+
# +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
|
1254
|
+
# Net::IMAP::SASL. New authenticators can easily be added for any other
|
1255
|
+
# <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
|
1256
|
+
# OpenSSL::Digest.
|
1257
|
+
#
|
1258
|
+
# +XOAUTH2+::
|
1259
|
+
# See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator].
|
1260
|
+
#
|
1261
|
+
# Login using a username and an OAuth2 access token. Non-standard and
|
1262
|
+
# obsoleted by +OAUTHBEARER+, but widely supported.
|
1263
|
+
#
|
1264
|
+
# See the {SASL mechanism
|
999
1265
|
# registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
1000
|
-
# for
|
1266
|
+
# for a list of all SASL mechanisms and their specifications. To register
|
1267
|
+
# new authenticators, see Authenticators.
|
1001
1268
|
#
|
1002
|
-
# =====
|
1269
|
+
# ===== Deprecated mechanisms
|
1003
1270
|
#
|
1004
|
-
#
|
1005
|
-
#
|
1271
|
+
# <em>Obsolete mechanisms should be avoided, but are still available for
|
1272
|
+
# backwards compatibility. See</em> Net::IMAP::SASL@Deprecated+mechanisms.
|
1273
|
+
# <em>Using a deprecated mechanism will print a warning.</em>
|
1006
1274
|
#
|
1007
|
-
#
|
1008
|
-
#
|
1009
|
-
#
|
1010
|
-
#
|
1011
|
-
#
|
1012
|
-
#
|
1013
|
-
#
|
1014
|
-
#
|
1015
|
-
#
|
1016
|
-
#
|
1017
|
-
#
|
1018
|
-
# creds = {
|
1019
|
-
# authcid: username,
|
1020
|
-
# password: proc { password ||= ui.prompt_for_password },
|
1021
|
-
# oauth2_token: proc { accesstok ||= kms.fresh_access_token },
|
1022
|
-
# }
|
1023
|
-
# capa = imap.capability
|
1024
|
-
# if capa.include? "AUTH=OAUTHBEARER"
|
1025
|
-
# imap.authenticate "OAUTHBEARER", **creds # authcid, oauth2_token
|
1026
|
-
# elsif capa.include? "AUTH=XOAUTH2"
|
1027
|
-
# imap.authenticate "XOAUTH2", **creds # authcid, oauth2_token
|
1028
|
-
# elsif capa.include? "AUTH=SCRAM-SHA-256"
|
1029
|
-
# imap.authenticate "SCRAM-SHA-256", **creds # authcid, password
|
1030
|
-
# elsif capa.include? "AUTH=PLAIN"
|
1031
|
-
# imap.authenticate "PLAIN", **creds # authcid, password
|
1032
|
-
# elsif capa.include? "AUTH=DIGEST-MD5"
|
1033
|
-
# imap.authenticate "DIGEST-MD5", **creds # authcid, password
|
1034
|
-
# elsif capa.include? "LOGINDISABLED"
|
1035
|
-
# raise "the server has disabled login"
|
1036
|
-
# else
|
1275
|
+
# ==== Capabilities
|
1276
|
+
#
|
1277
|
+
# <tt>"AUTH=#{mechanism}"</tt> capabilities indicate server support for
|
1278
|
+
# mechanisms. Use #auth_capable? or #auth_mechanisms to check for support
|
1279
|
+
# before using a particular mechanism.
|
1280
|
+
#
|
1281
|
+
# if imap.auth_capable? "XOAUTH2"
|
1282
|
+
# imap.authenticate "XOAUTH2", username, oauth2_access_token
|
1283
|
+
# elsif imap.auth_capable? "PLAIN"
|
1284
|
+
# imap.authenticate "PLAIN", username, password
|
1285
|
+
# elsif !imap.capability? "LOGINDISABLED"
|
1037
1286
|
# imap.login username, password
|
1287
|
+
# else
|
1288
|
+
# raise "No acceptable authentication mechanism is available"
|
1038
1289
|
# end
|
1039
1290
|
#
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1291
|
+
# Although servers should list all supported \SASL mechanisms, they may
|
1292
|
+
# allow authentication with an unlisted +mechanism+.
|
1293
|
+
#
|
1294
|
+
# If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported
|
1295
|
+
# and the appropriate <tt>"AUTH=#{mechanism}"</tt> capability is present,
|
1296
|
+
# an "initial response" may be sent as an argument to the +AUTHENTICATE+
|
1297
|
+
# command, saving a round-trip. The SASL exchange allows for server
|
1298
|
+
# challenges and client responses, but many mechanisms expect the client to
|
1299
|
+
# "respond" first. The initial response will only be sent for
|
1300
|
+
# "client-first" mechanisms.
|
1301
|
+
#
|
1302
|
+
# Server capabilities may change after #starttls, #login, and #authenticate.
|
1303
|
+
# Previously cached #capabilities will be cleared when this method
|
1304
|
+
# completes. If the TaggedResponse to #authenticate includes updated
|
1305
|
+
# capabilities, they will be cached.
|
1306
|
+
def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback)
|
1307
|
+
mechanism = mechanism.to_s.tr("_", "-").upcase
|
1308
|
+
authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
|
1309
|
+
cmdargs = ["AUTHENTICATE", mechanism]
|
1310
|
+
if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
|
1311
|
+
authenticator.respond_to?(:initial_response?) &&
|
1312
|
+
authenticator.initial_response?
|
1313
|
+
response = authenticator.process(nil)
|
1314
|
+
cmdargs << (response.empty? ? "=" : [response].pack("m0"))
|
1315
|
+
end
|
1316
|
+
result = send_command_with_continuations(*cmdargs) {|data|
|
1317
|
+
challenge = data.unpack1("m")
|
1318
|
+
response = authenticator.process challenge
|
1319
|
+
[response].pack("m0")
|
1320
|
+
}
|
1321
|
+
if authenticator.respond_to?(:done?) && !authenticator.done?
|
1322
|
+
logout!
|
1323
|
+
raise SASL::AuthenticationIncomplete, result
|
1049
1324
|
end
|
1325
|
+
@capabilities = capabilities_from_resp_code result
|
1326
|
+
result
|
1050
1327
|
end
|
1051
1328
|
|
1052
1329
|
# Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
|
@@ -1054,16 +1331,25 @@ module Net
|
|
1054
1331
|
# this +user+. If successful, the connection enters the "_authenticated_"
|
1055
1332
|
# state.
|
1056
1333
|
#
|
1057
|
-
# Using #authenticate
|
1058
|
-
#
|
1334
|
+
# Using #authenticate {should be
|
1335
|
+
# preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command]
|
1336
|
+
# over #login. The LOGIN command is not the same as #authenticate with the
|
1337
|
+
# "LOGIN" +mechanism+.
|
1059
1338
|
#
|
1060
1339
|
# A Net::IMAP::NoResponseError is raised if authentication fails.
|
1061
1340
|
#
|
1062
1341
|
# Related: #authenticate, #starttls
|
1063
1342
|
#
|
1064
|
-
#
|
1065
|
-
#
|
1066
|
-
#
|
1343
|
+
# ===== Capabilities
|
1344
|
+
#
|
1345
|
+
# An IMAP client MUST NOT call #login when the server advertises the
|
1346
|
+
# +LOGINDISABLED+ capability.
|
1347
|
+
#
|
1348
|
+
# if imap.capability? "LOGINDISABLED"
|
1349
|
+
# raise "Remote server has disabled the login command"
|
1350
|
+
# else
|
1351
|
+
# imap.login username, password
|
1352
|
+
# end
|
1067
1353
|
#
|
1068
1354
|
# Server capabilities may change after #starttls, #login, and #authenticate.
|
1069
1355
|
# Cached capabilities _must_ be invalidated after this method completes.
|
@@ -1072,17 +1358,24 @@ module Net
|
|
1072
1358
|
#
|
1073
1359
|
def login(user, password)
|
1074
1360
|
send_command("LOGIN", user, password)
|
1361
|
+
.tap { @capabilities = capabilities_from_resp_code _1 }
|
1075
1362
|
end
|
1076
1363
|
|
1077
1364
|
# Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
|
1078
1365
|
# to select a +mailbox+ so that messages in the +mailbox+ can be accessed.
|
1079
1366
|
#
|
1080
1367
|
# After you have selected a mailbox, you may retrieve the number of items in
|
1081
|
-
# that mailbox from <tt>imap.responses
|
1082
|
-
# recent messages from <tt>imap.responses
|
1083
|
-
# these values can change if new messages arrive during a session
|
1084
|
-
# existing messages are expunged; see #add_response_handler for a
|
1085
|
-
# detect these events.
|
1368
|
+
# that mailbox from <tt>imap.responses("EXISTS", &:last)</tt>, and the
|
1369
|
+
# number of recent messages from <tt>imap.responses("RECENT", &:last)</tt>.
|
1370
|
+
# Note that these values can change if new messages arrive during a session
|
1371
|
+
# or when existing messages are expunged; see #add_response_handler for a
|
1372
|
+
# way to detect these events.
|
1373
|
+
#
|
1374
|
+
# When the +condstore+ keyword argument is true, the server is told to
|
1375
|
+
# enable the extension. If +mailbox+ supports persistence of mod-sequences,
|
1376
|
+
# the +HIGHESTMODSEQ+ ResponseCode will be sent as an untagged response to
|
1377
|
+
# #select and all `FETCH` responses will include FetchData#modseq.
|
1378
|
+
# Otherwise, the +NOMODSEQ+ ResponseCode will be sent.
|
1086
1379
|
#
|
1087
1380
|
# A Net::IMAP::NoResponseError is raised if the mailbox does not
|
1088
1381
|
# exist or is for some reason non-selectable.
|
@@ -1095,11 +1388,18 @@ module Net
|
|
1095
1388
|
# the server may return an untagged "NO" response with a "UIDNOTSTICKY"
|
1096
1389
|
# response code indicating that the mailstore does not support persistent
|
1097
1390
|
# UIDs:
|
1098
|
-
#
|
1099
|
-
|
1391
|
+
# imap.responses("NO", &:last)&.code&.name == "UIDNOTSTICKY"
|
1392
|
+
#
|
1393
|
+
# If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported,
|
1394
|
+
# the +condstore+ keyword parameter may be used.
|
1395
|
+
# imap.select("mbox", condstore: true)
|
1396
|
+
# modseq = imap.responses("HIGHESTMODSEQ", &:last)
|
1397
|
+
def select(mailbox, condstore: false)
|
1398
|
+
args = ["SELECT", mailbox]
|
1399
|
+
args << ["CONDSTORE"] if condstore
|
1100
1400
|
synchronize do
|
1101
1401
|
@responses.clear
|
1102
|
-
send_command(
|
1402
|
+
send_command(*args)
|
1103
1403
|
end
|
1104
1404
|
end
|
1105
1405
|
|
@@ -1112,10 +1412,12 @@ module Net
|
|
1112
1412
|
# exist or is for some reason non-examinable.
|
1113
1413
|
#
|
1114
1414
|
# Related: #select
|
1115
|
-
def examine(mailbox)
|
1415
|
+
def examine(mailbox, condstore: false)
|
1416
|
+
args = ["EXAMINE", mailbox]
|
1417
|
+
args << ["CONDSTORE"] if condstore
|
1116
1418
|
synchronize do
|
1117
1419
|
@responses.clear
|
1118
|
-
send_command(
|
1420
|
+
send_command(*args)
|
1119
1421
|
end
|
1120
1422
|
end
|
1121
1423
|
|
@@ -1185,10 +1487,10 @@ module Net
|
|
1185
1487
|
# to the client. +refname+ provides a context (for instance, a base
|
1186
1488
|
# directory in a directory-based mailbox hierarchy). +mailbox+ specifies a
|
1187
1489
|
# mailbox or (via wildcards) mailboxes under that context. Two wildcards
|
1188
|
-
# may be used in +mailbox+:
|
1189
|
-
# the hierarchy delimiter (for instance,
|
1190
|
-
# directory-based mailbox hierarchy); and
|
1191
|
-
# *except* the hierarchy delimiter.
|
1490
|
+
# may be used in +mailbox+: <tt>"*"</tt>, which matches all characters
|
1491
|
+
# *including* the hierarchy delimiter (for instance, "/" on a UNIX-hosted
|
1492
|
+
# directory-based mailbox hierarchy); and <tt>"%"</tt>, which matches all
|
1493
|
+
# characters *except* the hierarchy delimiter.
|
1192
1494
|
#
|
1193
1495
|
# If +refname+ is empty, +mailbox+ is used directly to determine
|
1194
1496
|
# which mailboxes to match. If +mailbox+ is empty, the root
|
@@ -1213,7 +1515,7 @@ module Net
|
|
1213
1515
|
def list(refname, mailbox)
|
1214
1516
|
synchronize do
|
1215
1517
|
send_command("LIST", refname, mailbox)
|
1216
|
-
|
1518
|
+
clear_responses("LIST")
|
1217
1519
|
end
|
1218
1520
|
end
|
1219
1521
|
|
@@ -1236,23 +1538,22 @@ module Net
|
|
1236
1538
|
# servers, then folder creation (and listing, moving, etc) can lead to
|
1237
1539
|
# errors.
|
1238
1540
|
#
|
1239
|
-
# From RFC2342:
|
1240
|
-
#
|
1241
|
-
# Although typically a server will support only a single Personal
|
1541
|
+
# From RFC2342[https://tools.ietf.org/html/rfc2342]:
|
1542
|
+
# >>>
|
1543
|
+
# <em>Although typically a server will support only a single Personal
|
1242
1544
|
# Namespace, and a single Other User's Namespace, circumstances exist
|
1243
1545
|
# where there MAY be multiples of these, and a client MUST be prepared
|
1244
1546
|
# for them. If a client is configured such that it is required to create
|
1245
1547
|
# a certain mailbox, there can be circumstances where it is unclear which
|
1246
1548
|
# Personal Namespaces it should create the mailbox in. In these
|
1247
1549
|
# situations a client SHOULD let the user select which namespaces to
|
1248
|
-
# create the mailbox in
|
1550
|
+
# create the mailbox in.</em>
|
1249
1551
|
#
|
1250
1552
|
# Related: #list, Namespaces, Namespace
|
1251
1553
|
#
|
1252
1554
|
# ===== For example:
|
1253
1555
|
#
|
1254
|
-
#
|
1255
|
-
# if capabilities.include?("NAMESPACE")
|
1556
|
+
# if capable?("NAMESPACE")
|
1256
1557
|
# namespaces = imap.namespace
|
1257
1558
|
# if namespace = namespaces.personal.first
|
1258
1559
|
# prefix = namespace.prefix # e.g. "" or "INBOX."
|
@@ -1271,7 +1572,7 @@ module Net
|
|
1271
1572
|
def namespace
|
1272
1573
|
synchronize do
|
1273
1574
|
send_command("NAMESPACE")
|
1274
|
-
|
1575
|
+
clear_responses("NAMESPACE").last
|
1275
1576
|
end
|
1276
1577
|
end
|
1277
1578
|
|
@@ -1315,7 +1616,7 @@ module Net
|
|
1315
1616
|
def xlist(refname, mailbox)
|
1316
1617
|
synchronize do
|
1317
1618
|
send_command("XLIST", refname, mailbox)
|
1318
|
-
|
1619
|
+
clear_responses("XLIST")
|
1319
1620
|
end
|
1320
1621
|
end
|
1321
1622
|
|
@@ -1334,8 +1635,8 @@ module Net
|
|
1334
1635
|
synchronize do
|
1335
1636
|
send_command("GETQUOTAROOT", mailbox)
|
1336
1637
|
result = []
|
1337
|
-
result.concat(
|
1338
|
-
result.concat(
|
1638
|
+
result.concat(clear_responses("QUOTAROOT"))
|
1639
|
+
result.concat(clear_responses("QUOTA"))
|
1339
1640
|
return result
|
1340
1641
|
end
|
1341
1642
|
end
|
@@ -1354,7 +1655,7 @@ module Net
|
|
1354
1655
|
def getquota(mailbox)
|
1355
1656
|
synchronize do
|
1356
1657
|
send_command("GETQUOTA", mailbox)
|
1357
|
-
|
1658
|
+
clear_responses("QUOTA")
|
1358
1659
|
end
|
1359
1660
|
end
|
1360
1661
|
|
@@ -1410,7 +1711,7 @@ module Net
|
|
1410
1711
|
def getacl(mailbox)
|
1411
1712
|
synchronize do
|
1412
1713
|
send_command("GETACL", mailbox)
|
1413
|
-
|
1714
|
+
clear_responses("ACL").last
|
1414
1715
|
end
|
1415
1716
|
end
|
1416
1717
|
|
@@ -1425,31 +1726,74 @@ module Net
|
|
1425
1726
|
def lsub(refname, mailbox)
|
1426
1727
|
synchronize do
|
1427
1728
|
send_command("LSUB", refname, mailbox)
|
1428
|
-
|
1729
|
+
clear_responses("LSUB")
|
1429
1730
|
end
|
1430
1731
|
end
|
1431
1732
|
|
1432
|
-
# Sends a {STATUS
|
1733
|
+
# Sends a {STATUS command [IMAP4rev1 §6.3.10]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.10]
|
1433
1734
|
# and returns the status of the indicated +mailbox+. +attr+ is a list of one
|
1434
|
-
# or more attributes whose statuses are to be requested.
|
1435
|
-
#
|
1735
|
+
# or more attributes whose statuses are to be requested.
|
1736
|
+
#
|
1737
|
+
# The return value is a hash of attributes. Most status attributes return
|
1738
|
+
# integer values, but some return other value types (documented below).
|
1739
|
+
#
|
1740
|
+
# A Net::IMAP::NoResponseError is raised if status values
|
1741
|
+
# for +mailbox+ cannot be returned; for instance, because it
|
1742
|
+
# does not exist.
|
1743
|
+
#
|
1744
|
+
# ===== Supported attributes
|
1745
|
+
#
|
1746
|
+
# +MESSAGES+:: The number of messages in the mailbox.
|
1747
|
+
#
|
1748
|
+
# +UIDNEXT+:: The next unique identifier value of the mailbox.
|
1749
|
+
#
|
1750
|
+
# +UIDVALIDITY+:: The unique identifier validity value of the mailbox.
|
1436
1751
|
#
|
1437
|
-
#
|
1438
|
-
# RECENT:: the number of recent messages in the mailbox.
|
1439
|
-
# UNSEEN:: the number of unseen messages in the mailbox.
|
1752
|
+
# +UNSEEN+:: The number of messages without the <tt>\Seen</tt> flag.
|
1440
1753
|
#
|
1441
|
-
# The
|
1754
|
+
# +DELETED+:: The number of messages with the <tt>\Deleted</tt> flag.
|
1755
|
+
#
|
1756
|
+
# +SIZE+::
|
1757
|
+
# The approximate size of the mailbox---must be greater than or equal to
|
1758
|
+
# the sum of all messages' +RFC822.SIZE+ fetch item values.
|
1759
|
+
#
|
1760
|
+
# +HIGHESTMODSEQ+::
|
1761
|
+
# The highest mod-sequence value of all messages in the mailbox. See
|
1762
|
+
# +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
|
1763
|
+
#
|
1764
|
+
# +MAILBOXID+::
|
1765
|
+
# A server-allocated unique _string_ identifier for the mailbox. See
|
1766
|
+
# +OBJECTID+ {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
|
1767
|
+
#
|
1768
|
+
# +RECENT+::
|
1769
|
+
# The number of messages with the <tt>\Recent</tt> flag.
|
1770
|
+
# _NOTE:_ +RECENT+ was removed from IMAP4rev2.
|
1771
|
+
#
|
1772
|
+
# Unsupported attributes may be requested. The attribute value will be
|
1773
|
+
# either an Integer or an ExtensionData object.
|
1774
|
+
#
|
1775
|
+
# ===== For example:
|
1442
1776
|
#
|
1443
1777
|
# p imap.status("inbox", ["MESSAGES", "RECENT"])
|
1444
1778
|
# #=> {"RECENT"=>0, "MESSAGES"=>44}
|
1445
1779
|
#
|
1446
|
-
#
|
1447
|
-
#
|
1448
|
-
#
|
1780
|
+
# ===== Capabilities
|
1781
|
+
#
|
1782
|
+
# +SIZE+ requires the server's capabilities to include either +IMAP4rev2+ or
|
1783
|
+
# <tt>STATUS=SIZE</tt>
|
1784
|
+
# {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html].
|
1785
|
+
#
|
1786
|
+
# +DELETED+ requires the server's capabilities to include +IMAP4rev2+.
|
1787
|
+
#
|
1788
|
+
# +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+
|
1789
|
+
# {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
|
1790
|
+
#
|
1791
|
+
# +MAILBOXID+ requires the server's capabilities to include +OBJECTID+
|
1792
|
+
# {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
|
1449
1793
|
def status(mailbox, attr)
|
1450
1794
|
synchronize do
|
1451
1795
|
send_command("STATUS", mailbox, attr)
|
1452
|
-
|
1796
|
+
clear_responses("STATUS").last&.attr
|
1453
1797
|
end
|
1454
1798
|
end
|
1455
1799
|
|
@@ -1538,7 +1882,7 @@ module Net
|
|
1538
1882
|
def expunge
|
1539
1883
|
synchronize do
|
1540
1884
|
send_command("EXPUNGE")
|
1541
|
-
|
1885
|
+
clear_responses("EXPUNGE")
|
1542
1886
|
end
|
1543
1887
|
end
|
1544
1888
|
|
@@ -1570,7 +1914,7 @@ module Net
|
|
1570
1914
|
def uid_expunge(uid_set)
|
1571
1915
|
synchronize do
|
1572
1916
|
send_command("UID EXPUNGE", MessageSet.new(uid_set))
|
1573
|
-
|
1917
|
+
clear_responses("EXPUNGE")
|
1574
1918
|
end
|
1575
1919
|
end
|
1576
1920
|
|
@@ -1580,6 +1924,10 @@ module Net
|
|
1580
1924
|
# string holding the entire search string, or a single-dimension array of
|
1581
1925
|
# search keywords and arguments.
|
1582
1926
|
#
|
1927
|
+
# Returns a SearchResult object. SearchResult inherits from Array (for
|
1928
|
+
# backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
|
1929
|
+
# capability has been enabled.
|
1930
|
+
#
|
1583
1931
|
# Related: #uid_search
|
1584
1932
|
#
|
1585
1933
|
# ===== Search criteria
|
@@ -1589,7 +1937,7 @@ module Net
|
|
1589
1937
|
# or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
|
1590
1938
|
# in addition to documentation for
|
1591
1939
|
# any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
|
1592
|
-
# reported by #
|
1940
|
+
# reported by #capabilities which may define additional search filters, e.g:
|
1593
1941
|
# +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
|
1594
1942
|
# +SAVEDATE+. The following are some common search criteria:
|
1595
1943
|
#
|
@@ -1628,6 +1976,15 @@ module Net
|
|
1628
1976
|
# p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
|
1629
1977
|
# #=> [1, 6, 7, 8]
|
1630
1978
|
#
|
1979
|
+
# ===== Capabilities
|
1980
|
+
#
|
1981
|
+
# If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported
|
1982
|
+
# and enabled for the selected mailbox, a non-empty SearchResult will
|
1983
|
+
# include a +MODSEQ+ value.
|
1984
|
+
# imap.select("mbox", condstore: true)
|
1985
|
+
# result = imap.search(["SUBJECT", "hi there", "not", "new")
|
1986
|
+
# #=> Net::IMAP::SearchResult[1, 6, 7, 8, modseq: 5594]
|
1987
|
+
# result.modseq # => 5594
|
1631
1988
|
def search(keys, charset = nil)
|
1632
1989
|
return search_internal("SEARCH", keys, charset)
|
1633
1990
|
end
|
@@ -1636,11 +1993,18 @@ module Net
|
|
1636
1993
|
# to search the mailbox for messages that match the given searching
|
1637
1994
|
# criteria, and returns unique identifiers (<tt>UID</tt>s).
|
1638
1995
|
#
|
1996
|
+
# Returns a SearchResult object. SearchResult inherits from Array (for
|
1997
|
+
# backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
|
1998
|
+
# capability has been enabled.
|
1999
|
+
#
|
1639
2000
|
# See #search for documentation of search criteria.
|
1640
2001
|
def uid_search(keys, charset = nil)
|
1641
2002
|
return search_internal("UID SEARCH", keys, charset)
|
1642
2003
|
end
|
1643
2004
|
|
2005
|
+
# :call-seq:
|
2006
|
+
# fetch(set, attr, changedsince: nil) -> array of FetchData
|
2007
|
+
#
|
1644
2008
|
# Sends a {FETCH command [IMAP4rev1 §6.4.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.5]
|
1645
2009
|
# to retrieve data associated with a message in the mailbox.
|
1646
2010
|
#
|
@@ -1656,8 +2020,10 @@ module Net
|
|
1656
2020
|
# +attr+ is a list of attributes to fetch; see the documentation
|
1657
2021
|
# for FetchData for a list of valid attributes.
|
1658
2022
|
#
|
1659
|
-
#
|
1660
|
-
#
|
2023
|
+
# +changedsince+ is an optional integer mod-sequence. It limits results to
|
2024
|
+
# messages with a mod-sequence greater than +changedsince+.
|
2025
|
+
#
|
2026
|
+
# The return value is an array of FetchData.
|
1661
2027
|
#
|
1662
2028
|
# Related: #uid_search, FetchData
|
1663
2029
|
#
|
@@ -1678,10 +2044,23 @@ module Net
|
|
1678
2044
|
# #=> "12-Oct-2000 22:40:59 +0900"
|
1679
2045
|
# p data.attr["UID"]
|
1680
2046
|
# #=> 98
|
1681
|
-
|
1682
|
-
|
2047
|
+
#
|
2048
|
+
# ===== Capabilities
|
2049
|
+
#
|
2050
|
+
# Many extensions define new message +attr+ names. See FetchData for a list
|
2051
|
+
# of supported extension fields.
|
2052
|
+
#
|
2053
|
+
# The server's capabilities must include +CONDSTORE+
|
2054
|
+
# {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the
|
2055
|
+
# +changedsince+ argument. Using +changedsince+ implicitly enables the
|
2056
|
+
# +CONDSTORE+ extension.
|
2057
|
+
def fetch(set, attr, mod = nil, changedsince: nil)
|
2058
|
+
fetch_internal("FETCH", set, attr, mod, changedsince: changedsince)
|
1683
2059
|
end
|
1684
2060
|
|
2061
|
+
# :call-seq:
|
2062
|
+
# uid_fetch(set, attr, changedsince: nil) -> array of FetchData
|
2063
|
+
#
|
1685
2064
|
# Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
|
1686
2065
|
# to retrieve data associated with a message in the mailbox.
|
1687
2066
|
#
|
@@ -1694,32 +2073,63 @@ module Net
|
|
1694
2073
|
# whether a +UID+ was specified as a message data item to the +FETCH+.
|
1695
2074
|
#
|
1696
2075
|
# Related: #fetch, FetchData
|
1697
|
-
|
1698
|
-
|
2076
|
+
#
|
2077
|
+
# ===== Capabilities
|
2078
|
+
# Same as #fetch.
|
2079
|
+
def uid_fetch(set, attr, mod = nil, changedsince: nil)
|
2080
|
+
fetch_internal("UID FETCH", set, attr, mod, changedsince: changedsince)
|
1699
2081
|
end
|
1700
2082
|
|
2083
|
+
# :call-seq:
|
2084
|
+
# store(set, attr, value, unchangedsince: nil) -> array of FetchData
|
2085
|
+
#
|
1701
2086
|
# Sends a {STORE command [IMAP4rev1 §6.4.6]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.6]
|
1702
2087
|
# to alter data associated with messages in the mailbox, in particular their
|
1703
|
-
# flags.
|
1704
|
-
#
|
1705
|
-
#
|
1706
|
-
#
|
1707
|
-
#
|
2088
|
+
# flags.
|
2089
|
+
#
|
2090
|
+
# +set+ is a number, an array of numbers, or a Range object. Each number is
|
2091
|
+
# a message sequence number.
|
2092
|
+
#
|
2093
|
+
# +attr+ is the name of a data item to store. The semantics of +value+
|
2094
|
+
# varies based on +attr+:
|
2095
|
+
# * When +attr+ is <tt>"FLAGS"</tt>, the flags in +value+ replace the
|
2096
|
+
# message's flag list.
|
2097
|
+
# * When +attr+ is <tt>"+FLAGS"</tt>, the flags in +value+ are added to
|
2098
|
+
# the flags for the message.
|
2099
|
+
# * When +attr+ is <tt>"-FLAGS"</tt>, the flags in +value+ are removed
|
2100
|
+
# from the message.
|
1708
2101
|
#
|
1709
|
-
#
|
2102
|
+
# +unchangedsince+ is an optional integer mod-sequence. It prohibits any
|
2103
|
+
# changes to messages with +mod-sequence+ greater than the specified
|
2104
|
+
# +unchangedsince+ value. A SequenceSet of any messages that fail this
|
2105
|
+
# check will be returned in a +MODIFIED+ ResponseCode.
|
2106
|
+
#
|
2107
|
+
# The return value is an array of FetchData.
|
1710
2108
|
#
|
1711
2109
|
# Related: #uid_store
|
1712
2110
|
#
|
1713
2111
|
# ===== For example:
|
1714
2112
|
#
|
1715
2113
|
# p imap.store(6..8, "+FLAGS", [:Deleted])
|
1716
|
-
# #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>,
|
1717
|
-
# #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>,
|
2114
|
+
# #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>,
|
2115
|
+
# #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>,
|
1718
2116
|
# #<Net::IMAP::FetchData seqno=8, attr={"FLAGS"=>[:Seen, :Deleted]}>]
|
1719
|
-
|
1720
|
-
|
2117
|
+
#
|
2118
|
+
# ===== Capabilities
|
2119
|
+
#
|
2120
|
+
# Extensions may define new data items to be used with #store.
|
2121
|
+
#
|
2122
|
+
# The server's capabilities must include +CONDSTORE+
|
2123
|
+
# {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the
|
2124
|
+
# +unchangedsince+ argument. Using +unchangedsince+ implicitly enables the
|
2125
|
+
# +CONDSTORE+ extension.
|
2126
|
+
def store(set, attr, flags, unchangedsince: nil)
|
2127
|
+
store_internal("STORE", set, attr, flags, unchangedsince: unchangedsince)
|
1721
2128
|
end
|
1722
2129
|
|
2130
|
+
# :call-seq:
|
2131
|
+
# uid_store(set, attr, value, unchangedsince: nil) -> array of FetchData
|
2132
|
+
#
|
1723
2133
|
# Sends a {UID STORE command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
|
1724
2134
|
# to alter data associated with messages in the mailbox, in particular their
|
1725
2135
|
# flags.
|
@@ -1728,8 +2138,11 @@ module Net
|
|
1728
2138
|
# message sequence numbers.
|
1729
2139
|
#
|
1730
2140
|
# Related: #store
|
1731
|
-
|
1732
|
-
|
2141
|
+
#
|
2142
|
+
# ===== Capabilities
|
2143
|
+
# Same as #store.
|
2144
|
+
def uid_store(set, attr, flags, unchangedsince: nil)
|
2145
|
+
store_internal("UID STORE", set, attr, flags, unchangedsince: unchangedsince)
|
1733
2146
|
end
|
1734
2147
|
|
1735
2148
|
# Sends a {COPY command [IMAP4rev1 §6.4.7]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.7]
|
@@ -1884,6 +2297,94 @@ module Net
|
|
1884
2297
|
return thread_internal("UID THREAD", algorithm, search_keys, charset)
|
1885
2298
|
end
|
1886
2299
|
|
2300
|
+
# Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1]
|
2301
|
+
# {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1]
|
2302
|
+
# to enable the specified server +capabilities+. Each capability may be an
|
2303
|
+
# array, string, or symbol. Returns a list of the capabilities that were
|
2304
|
+
# enabled.
|
2305
|
+
#
|
2306
|
+
# The +ENABLE+ command is only valid in the _authenticated_ state, before
|
2307
|
+
# any mailbox is selected.
|
2308
|
+
#
|
2309
|
+
# Related: #capable?, #capabilities, #capability
|
2310
|
+
#
|
2311
|
+
# ===== Capabilities
|
2312
|
+
#
|
2313
|
+
# The server's capabilities must include
|
2314
|
+
# +ENABLE+ [RFC5161[https://tools.ietf.org/html/rfc5161]]
|
2315
|
+
# or +IMAP4REV2+ [RFC9051[https://tools.ietf.org/html/rfc9051]].
|
2316
|
+
#
|
2317
|
+
# Additionally, the server capabilities must include a capability matching
|
2318
|
+
# each enabled extension (usually the same name as the enabled extension).
|
2319
|
+
# The following capabilities may be enabled:
|
2320
|
+
#
|
2321
|
+
# [+CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]]
|
2322
|
+
#
|
2323
|
+
# Updates various commands to return +CONDSTORE+ extension responses. It
|
2324
|
+
# is not necessary to explicitly enable +CONDSTORE+—using any of the
|
2325
|
+
# command parameters defined by the extension will implicitly enable it.
|
2326
|
+
# See {[RFC7162 §3.1]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1].
|
2327
|
+
#
|
2328
|
+
# [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
|
2329
|
+
#
|
2330
|
+
# In a future release, <tt>enable(:utf8)</tt> will enable either
|
2331
|
+
# <tt>"UTF8=ACCEPT"</tt> or <tt>"IMAP4rev2"</tt>, depending on server
|
2332
|
+
# capabilities.
|
2333
|
+
#
|
2334
|
+
# [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
|
2335
|
+
#
|
2336
|
+
# The server's capabilities must include <tt>UTF8=ACCEPT</tt> _or_
|
2337
|
+
# <tt>UTF8=ONLY</tt>.
|
2338
|
+
#
|
2339
|
+
# This allows the server to send strings encoded as UTF-8 which might
|
2340
|
+
# otherwise need to use a 7-bit encoding, such as {modified
|
2341
|
+
# UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for
|
2342
|
+
# message headers.
|
2343
|
+
#
|
2344
|
+
# *Note:* <em>A future update may set string encodings slightly
|
2345
|
+
# differently</em>, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8"
|
2346
|
+
# when it is. Currently, the encoding of strings sent as "quoted" or
|
2347
|
+
# "text" will _always_ be "UTF-8", even when only ASCII characters are
|
2348
|
+
# used (e.g. "Subject: Agenda") And currently, string "literals" sent
|
2349
|
+
# by the server will always have an "ASCII-8BIT" (binary)
|
2350
|
+
# encoding, even if they generally contain UTF-8 data, if they are
|
2351
|
+
# text at all.
|
2352
|
+
#
|
2353
|
+
# [<tt>"UTF8=ONLY"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
|
2354
|
+
#
|
2355
|
+
# A server that reports the <tt>UTF8=ONLY</tt> capability _requires_ that
|
2356
|
+
# the client <tt>enable("UTF8=ACCEPT")</tt> before any mailboxes may be
|
2357
|
+
# selected. For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
|
2358
|
+
# <tt>enable("UTF8=ACCEPT")</tt>.
|
2359
|
+
#
|
2360
|
+
# ===== Unsupported capabilities
|
2361
|
+
#
|
2362
|
+
# *Note:* Some extensions that use ENABLE permit the server to send syntax
|
2363
|
+
# that Net::IMAP cannot parse, which may raise an exception and disconnect.
|
2364
|
+
# Some extensions may work, but the support may be incomplete, untested, or
|
2365
|
+
# experimental.
|
2366
|
+
#
|
2367
|
+
# Until a capability is documented here as supported, enabling it may result
|
2368
|
+
# in undocumented behavior and a future release may update with incompatible
|
2369
|
+
# behavior <em>without warning or deprecation</em>.
|
2370
|
+
#
|
2371
|
+
# <em>Caution is advised.</em>
|
2372
|
+
#
|
2373
|
+
def enable(*capabilities)
|
2374
|
+
capabilities = capabilities
|
2375
|
+
.flatten
|
2376
|
+
.map {|e| ENABLE_ALIASES[e] || e }
|
2377
|
+
.uniq
|
2378
|
+
.join(' ')
|
2379
|
+
synchronize do
|
2380
|
+
send_command("ENABLE #{capabilities}")
|
2381
|
+
result = clear_responses("ENABLED").last || []
|
2382
|
+
@utf8_strings ||= result.include? "UTF8=ACCEPT"
|
2383
|
+
@utf8_strings ||= result.include? "IMAP4REV2"
|
2384
|
+
result
|
2385
|
+
end
|
2386
|
+
end
|
2387
|
+
|
1887
2388
|
# Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3]
|
1888
2389
|
# {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13]
|
1889
2390
|
# that waits for notifications of new or expunged messages. Yields
|
@@ -1948,6 +2449,104 @@ module Net
|
|
1948
2449
|
end
|
1949
2450
|
end
|
1950
2451
|
|
2452
|
+
# :call-seq:
|
2453
|
+
# responses {|hash| ...} -> block result
|
2454
|
+
# responses(type) {|array| ...} -> block result
|
2455
|
+
#
|
2456
|
+
# Yields unhandled responses and returns the result of the block.
|
2457
|
+
#
|
2458
|
+
# Unhandled responses are stored in a hash, with arrays of
|
2459
|
+
# <em>non-+nil+</em> UntaggedResponse#data keyed by UntaggedResponse#name
|
2460
|
+
# and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
|
2461
|
+
# yield the entire responses hash. Call with +type+ to yield only the array
|
2462
|
+
# of responses for that type.
|
2463
|
+
#
|
2464
|
+
# For example:
|
2465
|
+
#
|
2466
|
+
# imap.select("inbox")
|
2467
|
+
# p imap.responses("EXISTS", &:last)
|
2468
|
+
# #=> 2
|
2469
|
+
# p imap.responses("UIDVALIDITY", &:last)
|
2470
|
+
# #=> 968263756
|
2471
|
+
#
|
2472
|
+
# >>>
|
2473
|
+
# *Note:* Access to the responses hash is synchronized for thread-safety.
|
2474
|
+
# The receiver thread and response_handlers cannot process new responses
|
2475
|
+
# until the block completes. Accessing either the response hash or its
|
2476
|
+
# response type arrays outside of the block is unsafe.
|
2477
|
+
#
|
2478
|
+
# Calling without a block is unsafe and deprecated. Future releases will
|
2479
|
+
# raise ArgumentError unless a block is given.
|
2480
|
+
#
|
2481
|
+
# Previously unhandled responses are automatically cleared before entering a
|
2482
|
+
# mailbox with #select or #examine. Long-lived connections can receive many
|
2483
|
+
# unhandled server responses, which must be pruned or they will continually
|
2484
|
+
# consume more memory. Update or clear the responses hash or arrays inside
|
2485
|
+
# the block, or use #clear_responses.
|
2486
|
+
#
|
2487
|
+
# Only non-+nil+ data is stored. Many important response codes have no data
|
2488
|
+
# of their own, but are used as "tags" on the ResponseText object they are
|
2489
|
+
# attached to. ResponseText will be accessible by its response types:
|
2490
|
+
# "+OK+", "+NO+", "+BAD+", "+BYE+", or "+PREAUTH+".
|
2491
|
+
#
|
2492
|
+
# TaggedResponse#data is not saved to #responses, nor is any
|
2493
|
+
# ResponseCode#data on tagged responses. Although some command methods do
|
2494
|
+
# return the TaggedResponse directly, #add_response_handler must be used to
|
2495
|
+
# handle all response codes.
|
2496
|
+
#
|
2497
|
+
# Related: #clear_responses, #response_handlers, #greeting
|
2498
|
+
def responses(type = nil)
|
2499
|
+
if block_given?
|
2500
|
+
synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
|
2501
|
+
elsif type
|
2502
|
+
raise ArgumentError, "Pass a block or use #clear_responses"
|
2503
|
+
else
|
2504
|
+
# warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1)
|
2505
|
+
@responses
|
2506
|
+
end
|
2507
|
+
end
|
2508
|
+
|
2509
|
+
# :call-seq:
|
2510
|
+
# clear_responses -> hash
|
2511
|
+
# clear_responses(type) -> array
|
2512
|
+
#
|
2513
|
+
# Clears and returns the unhandled #responses hash or the unhandled
|
2514
|
+
# responses array for a single response +type+.
|
2515
|
+
#
|
2516
|
+
# Clearing responses is synchronized with other threads. The lock is
|
2517
|
+
# released before returning.
|
2518
|
+
#
|
2519
|
+
# Related: #responses, #response_handlers
|
2520
|
+
def clear_responses(type = nil)
|
2521
|
+
synchronize {
|
2522
|
+
if type
|
2523
|
+
@responses.delete(type) || []
|
2524
|
+
else
|
2525
|
+
@responses.dup.transform_values(&:freeze)
|
2526
|
+
.tap { _1.default = [].freeze }
|
2527
|
+
.tap { @responses.clear }
|
2528
|
+
end
|
2529
|
+
}
|
2530
|
+
.freeze
|
2531
|
+
end
|
2532
|
+
|
2533
|
+
# Returns all response handlers, including those that are added internally
|
2534
|
+
# by commands. Each response handler will be called with every new
|
2535
|
+
# UntaggedResponse, TaggedResponse, and ContinuationRequest.
|
2536
|
+
#
|
2537
|
+
# Response handlers are called with a mutex inside the receiver thread. New
|
2538
|
+
# responses cannot be processed and commands from other threads must wait
|
2539
|
+
# until all response_handlers return. An exception will shut-down the
|
2540
|
+
# receiver thread and close the connection.
|
2541
|
+
#
|
2542
|
+
# For thread-safety, the returned array is a frozen copy of the internal
|
2543
|
+
# array.
|
2544
|
+
#
|
2545
|
+
# Related: #add_response_handler, #remove_response_handler
|
2546
|
+
def response_handlers
|
2547
|
+
synchronize { @response_handlers.clone.freeze }
|
2548
|
+
end
|
2549
|
+
|
1951
2550
|
# Adds a response handler. For example, to detect when
|
1952
2551
|
# the server sends a new EXISTS response (which normally
|
1953
2552
|
# indicates new messages being added to the mailbox),
|
@@ -1960,14 +2559,21 @@ module Net
|
|
1960
2559
|
# end
|
1961
2560
|
# }
|
1962
2561
|
#
|
2562
|
+
# Related: #remove_response_handler, #response_handlers
|
1963
2563
|
def add_response_handler(handler = nil, &block)
|
1964
2564
|
raise ArgumentError, "two Procs are passed" if handler && block
|
1965
|
-
|
2565
|
+
synchronize do
|
2566
|
+
@response_handlers.push(block || handler)
|
2567
|
+
end
|
1966
2568
|
end
|
1967
2569
|
|
1968
2570
|
# Removes the response handler.
|
2571
|
+
#
|
2572
|
+
# Related: #add_response_handler, #response_handlers
|
1969
2573
|
def remove_response_handler(handler)
|
1970
|
-
|
2574
|
+
synchronize do
|
2575
|
+
@response_handlers.delete(handler)
|
2576
|
+
end
|
1971
2577
|
end
|
1972
2578
|
|
1973
2579
|
private
|
@@ -1978,93 +2584,29 @@ module Net
|
|
1978
2584
|
|
1979
2585
|
@@debug = false
|
1980
2586
|
|
1981
|
-
|
1982
|
-
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
1989
|
-
|
1990
|
-
|
1991
|
-
|
1992
|
-
|
1993
|
-
|
1994
|
-
|
1995
|
-
|
1996
|
-
|
1997
|
-
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2001
|
-
|
2002
|
-
|
2003
|
-
|
2004
|
-
# being dropped by an intervening firewall).
|
2005
|
-
# Errno::ENETUNREACH:: There is no route to that network.
|
2006
|
-
# SocketError:: Hostname not known or other socket error.
|
2007
|
-
# Net::IMAP::ByeResponseError:: The connected to the host was successful, but
|
2008
|
-
# it immediately said goodbye.
|
2009
|
-
def initialize(host, port_or_options = {},
|
2010
|
-
usessl = false, certs = nil, verify = true)
|
2011
|
-
super()
|
2012
|
-
@host = host
|
2013
|
-
begin
|
2014
|
-
options = port_or_options.to_hash
|
2015
|
-
rescue NoMethodError
|
2016
|
-
# for backward compatibility
|
2017
|
-
options = {}
|
2018
|
-
options[:port] = port_or_options
|
2019
|
-
if usessl
|
2020
|
-
options[:ssl] = create_ssl_params(certs, verify)
|
2021
|
-
end
|
2022
|
-
end
|
2023
|
-
@port = options[:port] || (options[:ssl] ? SSL_PORT : PORT)
|
2024
|
-
@tag_prefix = "RUBY"
|
2025
|
-
@tagno = 0
|
2026
|
-
@open_timeout = options[:open_timeout] || 30
|
2027
|
-
@idle_response_timeout = options[:idle_response_timeout] || 5
|
2028
|
-
@parser = ResponseParser.new
|
2029
|
-
@sock = tcp_socket(@host, @port)
|
2030
|
-
begin
|
2031
|
-
if options[:ssl]
|
2032
|
-
start_tls_session(options[:ssl])
|
2033
|
-
@usessl = true
|
2034
|
-
else
|
2035
|
-
@usessl = false
|
2036
|
-
end
|
2037
|
-
@responses = Hash.new([].freeze)
|
2038
|
-
@tagged_responses = {}
|
2039
|
-
@response_handlers = []
|
2040
|
-
@tagged_response_arrival = new_cond
|
2041
|
-
@continued_command_tag = nil
|
2042
|
-
@continuation_request_arrival = new_cond
|
2043
|
-
@continuation_request_exception = nil
|
2044
|
-
@idle_done_cond = nil
|
2045
|
-
@logout_command_tag = nil
|
2046
|
-
@debug_output_bol = true
|
2047
|
-
@exception = nil
|
2048
|
-
|
2049
|
-
@greeting = get_response
|
2050
|
-
if @greeting.nil?
|
2051
|
-
raise Error, "connection closed"
|
2052
|
-
end
|
2053
|
-
if @greeting.name == "BYE"
|
2054
|
-
raise ByeResponseError, @greeting
|
2055
|
-
end
|
2056
|
-
|
2057
|
-
@client_thread = Thread.current
|
2058
|
-
@receiver_thread = Thread.start {
|
2059
|
-
begin
|
2060
|
-
receive_responses
|
2061
|
-
rescue Exception
|
2062
|
-
end
|
2063
|
-
}
|
2064
|
-
@receiver_thread_terminating = false
|
2065
|
-
rescue Exception
|
2066
|
-
@sock.close
|
2067
|
-
raise
|
2587
|
+
def start_imap_connection
|
2588
|
+
@greeting = get_server_greeting
|
2589
|
+
@capabilities = capabilities_from_resp_code @greeting
|
2590
|
+
@receiver_thread = start_receiver_thread
|
2591
|
+
rescue Exception
|
2592
|
+
@sock.close
|
2593
|
+
raise
|
2594
|
+
end
|
2595
|
+
|
2596
|
+
def get_server_greeting
|
2597
|
+
greeting = get_response
|
2598
|
+
raise Error, "No server greeting - connection closed" unless greeting
|
2599
|
+
record_untagged_response_code greeting
|
2600
|
+
raise ByeResponseError, greeting if greeting.name == "BYE"
|
2601
|
+
greeting
|
2602
|
+
end
|
2603
|
+
|
2604
|
+
def start_receiver_thread
|
2605
|
+
Thread.start do
|
2606
|
+
receive_responses
|
2607
|
+
rescue Exception => ex
|
2608
|
+
@receiver_thread_exception = ex
|
2609
|
+
# don't exit the thread with an exception
|
2068
2610
|
end
|
2069
2611
|
end
|
2070
2612
|
|
@@ -2113,11 +2655,7 @@ module Net
|
|
2113
2655
|
@continuation_request_arrival.signal
|
2114
2656
|
end
|
2115
2657
|
when UntaggedResponse
|
2116
|
-
|
2117
|
-
if resp.data.instance_of?(ResponseText) &&
|
2118
|
-
(code = resp.data.code)
|
2119
|
-
record_response(code.name, code.data)
|
2120
|
-
end
|
2658
|
+
record_untagged_response(resp)
|
2121
2659
|
if resp.name == "BYE" && @logout_command_tag.nil?
|
2122
2660
|
@sock.close
|
2123
2661
|
@exception = ByeResponseError.new(resp)
|
@@ -2171,7 +2709,8 @@ module Net
|
|
2171
2709
|
when /\A(?:BAD)\z/ni
|
2172
2710
|
raise BadResponseError, resp
|
2173
2711
|
else
|
2174
|
-
|
2712
|
+
disconnect
|
2713
|
+
raise InvalidResponseError, "invalid tagged resp: %p" % [resp.raw.chomp]
|
2175
2714
|
end
|
2176
2715
|
end
|
2177
2716
|
|
@@ -2195,11 +2734,42 @@ module Net
|
|
2195
2734
|
return @parser.parse(buff)
|
2196
2735
|
end
|
2197
2736
|
|
2198
|
-
|
2199
|
-
|
2200
|
-
|
2737
|
+
#############################
|
2738
|
+
# built-in response handlers
|
2739
|
+
|
2740
|
+
# store name => [..., data]
|
2741
|
+
def record_untagged_response(resp)
|
2742
|
+
@responses[resp.name] << resp.data
|
2743
|
+
record_untagged_response_code resp
|
2744
|
+
end
|
2745
|
+
|
2746
|
+
# store code.name => [..., code.data]
|
2747
|
+
def record_untagged_response_code(resp)
|
2748
|
+
return unless resp.data.is_a?(ResponseText)
|
2749
|
+
return unless (code = resp.data.code)
|
2750
|
+
@responses[code.name] << code.data
|
2751
|
+
end
|
2752
|
+
|
2753
|
+
# NOTE: only call this for greeting, login, and authenticate
|
2754
|
+
def capabilities_from_resp_code(resp)
|
2755
|
+
return unless %w[PREAUTH OK].any? { _1.casecmp? resp.name }
|
2756
|
+
return unless (code = resp.data.code)
|
2757
|
+
return unless code.name.casecmp?("CAPABILITY")
|
2758
|
+
code.data.freeze
|
2759
|
+
end
|
2760
|
+
|
2761
|
+
#############################
|
2762
|
+
|
2763
|
+
# Calls send_command, yielding the text of each ContinuationRequest and
|
2764
|
+
# responding with each block result. Returns TaggedResponse. Raises
|
2765
|
+
# NoResponseError or BadResponseError.
|
2766
|
+
def send_command_with_continuations(cmd, *args)
|
2767
|
+
send_command(cmd, *args) do |server_response|
|
2768
|
+
if server_response.instance_of?(ContinuationRequest)
|
2769
|
+
client_response = yield server_response.data.text
|
2770
|
+
put_string(client_response + CRLF)
|
2771
|
+
end
|
2201
2772
|
end
|
2202
|
-
@responses[name].push(data)
|
2203
2773
|
end
|
2204
2774
|
|
2205
2775
|
def send_command(cmd, *args, &block)
|
@@ -2241,8 +2811,8 @@ module Net
|
|
2241
2811
|
if @debug_output_bol
|
2242
2812
|
$stderr.print("C: ")
|
2243
2813
|
end
|
2244
|
-
$stderr.print(str.gsub(/\n
|
2245
|
-
if /\
|
2814
|
+
$stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " })
|
2815
|
+
if /\n\z/n.match(str)
|
2246
2816
|
@debug_output_bol = true
|
2247
2817
|
else
|
2248
2818
|
@debug_output_bol = false
|
@@ -2262,11 +2832,15 @@ module Net
|
|
2262
2832
|
else
|
2263
2833
|
send_command(cmd, *keys)
|
2264
2834
|
end
|
2265
|
-
|
2835
|
+
clear_responses("SEARCH").last || []
|
2266
2836
|
end
|
2267
2837
|
end
|
2268
2838
|
|
2269
|
-
def fetch_internal(cmd, set, attr, mod = nil)
|
2839
|
+
def fetch_internal(cmd, set, attr, mod = nil, changedsince: nil)
|
2840
|
+
if changedsince
|
2841
|
+
mod ||= []
|
2842
|
+
mod << "CHANGEDSINCE" << Integer(changedsince)
|
2843
|
+
end
|
2270
2844
|
case attr
|
2271
2845
|
when String then
|
2272
2846
|
attr = RawData.new(attr)
|
@@ -2277,24 +2851,25 @@ module Net
|
|
2277
2851
|
end
|
2278
2852
|
|
2279
2853
|
synchronize do
|
2280
|
-
|
2854
|
+
clear_responses("FETCH")
|
2281
2855
|
if mod
|
2282
2856
|
send_command(cmd, MessageSet.new(set), attr, mod)
|
2283
2857
|
else
|
2284
2858
|
send_command(cmd, MessageSet.new(set), attr)
|
2285
2859
|
end
|
2286
|
-
|
2860
|
+
clear_responses("FETCH")
|
2287
2861
|
end
|
2288
2862
|
end
|
2289
2863
|
|
2290
|
-
def store_internal(cmd, set, attr, flags)
|
2291
|
-
if attr.instance_of?(String)
|
2292
|
-
|
2293
|
-
|
2864
|
+
def store_internal(cmd, set, attr, flags, unchangedsince: nil)
|
2865
|
+
attr = RawData.new(attr) if attr.instance_of?(String)
|
2866
|
+
args = [MessageSet.new(set)]
|
2867
|
+
args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
|
2868
|
+
args << attr << flags
|
2294
2869
|
synchronize do
|
2295
|
-
|
2296
|
-
send_command(cmd,
|
2297
|
-
|
2870
|
+
clear_responses("FETCH")
|
2871
|
+
send_command(cmd, *args)
|
2872
|
+
clear_responses("FETCH")
|
2298
2873
|
end
|
2299
2874
|
end
|
2300
2875
|
|
@@ -2308,10 +2883,9 @@ module Net
|
|
2308
2883
|
else
|
2309
2884
|
normalize_searching_criteria(search_keys)
|
2310
2885
|
end
|
2311
|
-
normalize_searching_criteria(search_keys)
|
2312
2886
|
synchronize do
|
2313
2887
|
send_command(cmd, sort_keys, charset, *search_keys)
|
2314
|
-
|
2888
|
+
clear_responses("SORT").last || []
|
2315
2889
|
end
|
2316
2890
|
end
|
2317
2891
|
|
@@ -2321,9 +2895,10 @@ module Net
|
|
2321
2895
|
else
|
2322
2896
|
normalize_searching_criteria(search_keys)
|
2323
2897
|
end
|
2324
|
-
|
2325
|
-
|
2326
|
-
|
2898
|
+
synchronize do
|
2899
|
+
send_command(cmd, algorithm, charset, *search_keys)
|
2900
|
+
clear_responses("THREAD").last || []
|
2901
|
+
end
|
2327
2902
|
end
|
2328
2903
|
|
2329
2904
|
def normalize_searching_criteria(keys)
|
@@ -2337,49 +2912,49 @@ module Net
|
|
2337
2912
|
end
|
2338
2913
|
end
|
2339
2914
|
|
2340
|
-
def
|
2341
|
-
|
2342
|
-
|
2343
|
-
|
2344
|
-
|
2345
|
-
|
2346
|
-
|
2915
|
+
def build_ssl_ctx(ssl)
|
2916
|
+
if ssl
|
2917
|
+
params = (Hash.try_convert(ssl) || {}).freeze
|
2918
|
+
context = SSLContext.new
|
2919
|
+
context.set_params(params)
|
2920
|
+
if defined?(VerifyCallbackProc)
|
2921
|
+
context.verify_callback = VerifyCallbackProc
|
2347
2922
|
end
|
2348
|
-
|
2349
|
-
|
2350
|
-
params[:verify_mode] = VERIFY_PEER
|
2923
|
+
context.freeze
|
2924
|
+
[params, context]
|
2351
2925
|
else
|
2352
|
-
|
2926
|
+
false
|
2353
2927
|
end
|
2354
|
-
return params
|
2355
2928
|
end
|
2356
2929
|
|
2357
|
-
def start_tls_session
|
2358
|
-
unless defined?(OpenSSL::SSL)
|
2359
|
-
|
2360
|
-
|
2361
|
-
|
2362
|
-
raise RuntimeError, "already using SSL"
|
2363
|
-
end
|
2364
|
-
begin
|
2365
|
-
params = params.to_hash
|
2366
|
-
rescue NoMethodError
|
2367
|
-
params = {}
|
2368
|
-
end
|
2369
|
-
context = SSLContext.new
|
2370
|
-
context.set_params(params)
|
2371
|
-
if defined?(VerifyCallbackProc)
|
2372
|
-
context.verify_callback = VerifyCallbackProc
|
2373
|
-
end
|
2374
|
-
@sock = SSLSocket.new(@sock, context)
|
2930
|
+
def start_tls_session
|
2931
|
+
raise "SSL extension not installed" unless defined?(OpenSSL::SSL)
|
2932
|
+
raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
|
2933
|
+
raise "cannot start TLS without SSLContext" unless ssl_ctx
|
2934
|
+
@sock = SSLSocket.new(@sock, ssl_ctx)
|
2375
2935
|
@sock.sync_close = true
|
2376
2936
|
@sock.hostname = @host if @sock.respond_to? :hostname=
|
2377
2937
|
ssl_socket_connect(@sock, @open_timeout)
|
2378
|
-
if
|
2938
|
+
if ssl_ctx.verify_mode != VERIFY_NONE
|
2379
2939
|
@sock.post_connection_check(@host)
|
2940
|
+
@tls_verified = true
|
2380
2941
|
end
|
2381
2942
|
end
|
2382
2943
|
|
2944
|
+
def sasl_adapter
|
2945
|
+
SASLAdapter.new(self, &method(:send_command_with_continuations))
|
2946
|
+
end
|
2947
|
+
|
2948
|
+
#--
|
2949
|
+
# We could get the saslprep method by extending the SASLprep module
|
2950
|
+
# directly. It's done indirectly, so SASLprep can be lazily autoloaded,
|
2951
|
+
# because most users won't need it.
|
2952
|
+
#++
|
2953
|
+
# Delegates to Net::IMAP::StringPrep::SASLprep#saslprep.
|
2954
|
+
def self.saslprep(string, **opts)
|
2955
|
+
Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
|
2956
|
+
end
|
2957
|
+
|
2383
2958
|
end
|
2384
2959
|
end
|
2385
2960
|
|
@@ -2390,4 +2965,6 @@ require_relative "imap/flags"
|
|
2390
2965
|
require_relative "imap/response_data"
|
2391
2966
|
require_relative "imap/response_parser"
|
2392
2967
|
require_relative "imap/authenticators"
|
2393
|
-
|
2968
|
+
|
2969
|
+
require_relative "imap/deprecated_client_options"
|
2970
|
+
Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions
|