net-imap 0.3.7 → 0.4.5

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.

Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pages.yml +46 -0
  3. data/.github/workflows/test.yml +5 -12
  4. data/.gitignore +2 -0
  5. data/Gemfile +3 -0
  6. data/README.md +15 -4
  7. data/Rakefile +0 -7
  8. data/docs/styles.css +0 -12
  9. data/lib/net/imap/authenticators.rb +26 -57
  10. data/lib/net/imap/command_data.rb +13 -6
  11. data/lib/net/imap/data_encoding.rb +14 -2
  12. data/lib/net/imap/deprecated_client_options.rb +139 -0
  13. data/lib/net/imap/errors.rb +20 -0
  14. data/lib/net/imap/fetch_data.rb +518 -0
  15. data/lib/net/imap/response_data.rb +116 -252
  16. data/lib/net/imap/response_parser/parser_utils.rb +240 -0
  17. data/lib/net/imap/response_parser.rb +1535 -1003
  18. data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
  19. data/lib/net/imap/sasl/authentication_exchange.rb +107 -0
  20. data/lib/net/imap/sasl/authenticators.rb +118 -0
  21. data/lib/net/imap/sasl/client_adapter.rb +72 -0
  22. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +21 -11
  23. data/lib/net/imap/sasl/digest_md5_authenticator.rb +180 -0
  24. data/lib/net/imap/sasl/external_authenticator.rb +83 -0
  25. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  26. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +25 -16
  27. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
  28. data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
  29. data/lib/net/imap/sasl/protocol_adapters.rb +45 -0
  30. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  31. data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
  32. data/lib/net/imap/sasl/stringprep.rb +6 -66
  33. data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
  34. data/lib/net/imap/sasl.rb +144 -43
  35. data/lib/net/imap/sasl_adapter.rb +21 -0
  36. data/lib/net/imap/sequence_set.rb +67 -0
  37. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  38. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  39. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  40. data/lib/net/imap/stringprep/tables.rb +146 -0
  41. data/lib/net/imap/stringprep/trace.rb +85 -0
  42. data/lib/net/imap/stringprep.rb +159 -0
  43. data/lib/net/imap.rb +1055 -612
  44. data/net-imap.gemspec +4 -3
  45. data/rakelib/benchmarks.rake +91 -0
  46. data/rakelib/saslprep.rake +4 -4
  47. data/rakelib/string_prep_tables_generator.rb +82 -60
  48. metadata +31 -13
  49. data/benchmarks/stringprep.yml +0 -65
  50. data/benchmarks/table-regexps.yml +0 -39
  51. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  52. data/lib/net/imap/authenticators/plain.rb +0 -41
  53. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  54. data/lib/net/imap/sasl/saslprep.rb +0 -55
  55. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  56. 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 in
28
- # [IMAP4rev1[https://tools.ietf.org/html/rfc3501]].
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
- # === Server capabilities and protocol extensions
78
+ # === Examples of Usage
81
79
  #
82
- # Net::IMAP <em>does not modify its behavior</em> according to server
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('LOGIN', 'joe_user', 'joes_password')
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
- # === Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
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('LOGIN', 'joe_user', 'joes_password')
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("cram-md5", "bar", "password")
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
- # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
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: A new client connects immediately and waits for a
188
- # successful server greeting before returning the new client object.
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>Note: Net::IMAP doesn't fully support IMAP4rev2 yet.</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>Capabilities may change after</em> #starttls, #authenticate, or #login
231
- # <em>and cached capabilities must be reloaded.</em>
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
- # ==== \IMAP commands for the "Not Authenticated" state
320
+ # ==== Not Authenticated state
236
321
  #
237
- # In addition to the universal commands, the following commands are valid in
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 a {SASL
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>Requires the <tt>AUTH=#{mechanism}</tt> capability for the chosen
248
- # mechanism.</em>
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
- # ==== \IMAP commands for the "Authenticated" state
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
- # - #enable: <em>Not implemented by Net::IMAP, yet.</em>
342
+ # In addition to the commands for any state, the following commands are valid
343
+ # in the "_authenticated_" state:
262
344
  #
263
- # <em>Requires the +ENABLE+ capability.</em>
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>Requires the +IDLE+ capability.</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
- # ==== \IMAP commands for the "Selected" state
368
+ # ==== Selected state
291
369
  #
292
- # In addition to the universal commands and the "authenticated" commands, 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 #expunge to only remove the specified UIDs.
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,35 @@ 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
- # <em>Requires the +MOVE+ capability.</em>
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 +logout+ state. If the socket is still
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
- # === Supported \IMAP extensions
401
+ # === \IMAP extension support
329
402
  #
330
403
  # ==== RFC9051: +IMAP4rev2+
331
404
  #
332
- # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is <em>not supported
333
- # yet</em>, Net::IMAP supports several extensions that have been folded into
334
- # it: +IDLE+, +MOVE+, +NAMESPACE+, +UIDPLUS+, and +UNSELECT+.
335
- #--
336
- # TODO: RFC4466, ABNF extensions (automatic support for other extensions)
337
- # TODO: +ESEARCH+, ExtendedSearchData
338
- # TODO: +SEARCHRES+,
339
- # TODO: +ENABLE+,
340
- # TODO: +SASL-IR+,
341
- # TODO: +LIST-EXTENDED+,
342
- # TODO: +LIST-STATUS+,
343
- # TODO: +LITERAL-+,
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+, and
408
+ # 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+. The following extensions are implicitly
417
+ # supported, but will be updated with more direct support: RFC5530 response
418
+ # codes, <tt>STATUS=SIZE</tt>, and <tt>STATUS=DELETED</tt>.</em>
353
419
  #
354
420
  # ==== RFC2087: +QUOTA+
355
421
  # - #getquota: returns the resource usage and limits for a quota root
@@ -358,92 +424,56 @@ module Net
358
424
  # - #setquota: sets the resource limits for a given quota root.
359
425
  #
360
426
  # ==== RFC2177: +IDLE+
361
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
362
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
427
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
428
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
363
429
  # - #idle: Allows the server to send updates to the client, without the client
364
430
  # needing to poll using #noop.
365
431
  #
366
432
  # ==== RFC2342: +NAMESPACE+
367
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
368
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
433
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
434
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
369
435
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
370
436
  #
371
437
  # ==== RFC2971: +ID+
372
438
  # - #id: exchanges client and server implementation information.
373
439
  #
374
- #--
375
- # ==== RFC3502: +MULTIAPPEND+
376
- # TODO...
377
- #++
378
- #
379
- #--
380
440
  # ==== RFC3516: +BINARY+
381
- # TODO...
382
- #++
441
+ # The fetch side of +BINARY+ has been folded into
442
+ # IMAP4rev2[https://tools.ietf.org/html/rfc9051].
443
+ # - Updates #fetch and #uid_fetch with the +BINARY+, +BINARY.PEEK+, and
444
+ # +BINARY.SIZE+ items. See FetchData#binary and FetchData#binary_size.
445
+ #
446
+ # >>>
447
+ # *NOTE:* The binary extension the #append command is _not_ supported yet.
383
448
  #
384
449
  # ==== RFC3691: +UNSELECT+
385
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
386
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
450
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
451
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
387
452
  # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
388
453
  # without expunging any messages.
389
454
  #
390
455
  # ==== RFC4314: +ACL+
391
456
  # - #getacl: lists the authenticated user's access rights to a mailbox.
392
457
  # - #setacl: sets the access rights for a user on a mailbox
393
- #--
394
- # TODO: #deleteacl, #listrights, #myrights
395
- #++
396
- # - *_Note:_* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
458
+ # >>>
459
+ # *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
397
460
  #
398
461
  # ==== RFC4315: +UIDPLUS+
399
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
400
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
462
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
463
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
401
464
  # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
402
465
  # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode
403
466
  # - Updates #append with the +APPENDUID+ ResponseCode
404
467
  # - Updates #copy, #move with the +COPYUID+ ResponseCode
405
468
  #
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
469
  # ==== RFC4959: +SASL-IR+
428
- # TODO...
429
470
  # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
430
- # - Updates #authenticate to reduce round-trips for supporting mechanisms.
431
- #++
432
- #
433
- #--
434
- # ==== RFC4978: COMPRESS=DEFLATE
435
- # TODO...
436
- #++
471
+ # - Updates #authenticate with the option to send an initial response.
437
472
  #
438
- #--
439
- # ==== RFC5182 +SEARCHRES+
440
- # TODO...
441
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
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
- #++
473
+ # ==== RFC5161: +ENABLE+
474
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
475
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
476
+ # - #enable: Enables backwards incompatible server extensions.
447
477
  #
448
478
  # ==== RFC5256: +SORT+
449
479
  # - #sort, #uid_sort: An alternate version of #search or #uid_search which
@@ -453,75 +483,35 @@ module Net
453
483
  # which arranges the results into ordered groups or threads according to a
454
484
  # chosen algorithm.
455
485
  #
456
- #--
457
- # ==== RFC5258 +LIST-EXTENDED+
458
- # TODO...
459
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this updates the
460
- # protocol with new optional parameters to the #list command, adding a few of
461
- # its own. Net::IMAP may be forward-compatible with future #list extensions,
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)
486
+ # ==== +X-GM-EXT-1+
487
+ # +X-GM-EXT-1+ is a non-standard Gmail extension. See {Google's
488
+ # documentation}[https://developers.google.com/gmail/imap/imap-extensions].
489
+ # - Updates #fetch and #uid_fetch with support for +X-GM-MSGID+ (unique
490
+ # message ID), +X-GM-THRID+ (thread ID), and +X-GM-LABELS+ (Gmail labels).
491
+ # - Updates #search with the +X-GM-RAW+ search attribute.
475
492
  # - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses.
476
493
  #
477
- #--
478
- # ==== RFC6154 +SPECIAL-USE+
479
- # TODO...
480
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
481
- # - Updates #list with the +SPECIAL-USE+ selection and return options.
482
- #++
494
+ # *NOTE:* The +OBJECTID+ extension should replace +X-GM-MSGID+ and
495
+ # +X-GM-THRID+, but Gmail does not support it (as of 2023-11-10).
483
496
  #
484
497
  # ==== RFC6851: +MOVE+
485
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
486
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
498
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
499
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
487
500
  # - #move, #uid_move: Moves the specified messages to the end of the
488
501
  # specified destination mailbox, expunging them from the current mailbox.
489
502
  #
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
- #++
503
+ # ==== RFC6855: <tt>UTF8=ACCEPT</tt>, <tt>UTF8=ONLY</tt>
509
504
  #
510
- # === Handling server responses
511
- #
512
- # - #greeting: The server's initial untagged response, which can indicate a
513
- # pre-authenticated connection.
514
- # - #responses: A hash with arrays of unhandled <em>non-+nil+</em>
515
- # UntaggedResponse and ResponseCode +#data+, keyed by +#name+.
516
- # - #add_response_handler: Add a block to be called inside the receiver thread
517
- # with every server response.
518
- # - #remove_response_handler: Remove a previously added response handler.
505
+ # - See #enable for information about support for UTF-8 string encoding.
519
506
  #
507
+ # ==== RFC8474: +OBJECTID+
508
+ # - Adds +MAILBOXID+ ResponseCode to #create tagged response.
509
+ # - Adds +MAILBOXID+ ResponseCode to #select and #examine untagged response.
510
+ # - Updates #fetch and #uid_fetch with the +EMAILID+ and +THREADID+ items.
511
+ # See FetchData#emailid and FetchData#emailid.
512
+ # - Updates #status with support for the +MAILBOXID+ status attribute.
520
513
  #
521
514
  # == References
522
- #--
523
- # TODO: Consider moving references list to REFERENCES.md or REFERENCES.rdoc.
524
- #++
525
515
  #
526
516
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
527
517
  # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
@@ -622,27 +612,21 @@ module Net
622
612
  # RFC 1864, DOI 10.17487/RFC1864, October 1995,
623
613
  # <https://www.rfc-editor.org/info/rfc1864>.
624
614
  #
625
- #--
626
- # TODO: Document IMAP keywords.
615
+ # [RFC3503[https://tools.ietf.org/html/rfc3503]]::
616
+ # Melnikov, A., "Message Disposition Notification (MDN)
617
+ # profile for Internet Message Access Protocol (IMAP)",
618
+ # RFC 3503, DOI 10.17487/RFC3503, March 2003,
619
+ # <https://www.rfc-editor.org/info/rfc3503>.
627
620
  #
628
- # [RFC3503[https://tools.ietf.org/html/rfc3503]]
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
- #++
621
+ # === \IMAP Extensions
634
622
  #
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
623
  # [QUOTA[https://tools.ietf.org/html/rfc9208]]::
643
624
  # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
644
625
  # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
645
- #++
626
+ #
627
+ # <em>Note: obsoletes</em>
628
+ # RFC-2087[https://tools.ietf.org/html/rfc2087]<em> (January 1997)</em>.
629
+ # <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
646
630
  # [IDLE[https://tools.ietf.org/html/rfc2177]]::
647
631
  # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
648
632
  # June 1997, <https://www.rfc-editor.org/info/rfc2177>.
@@ -652,6 +636,10 @@ module Net
652
636
  # [ID[https://tools.ietf.org/html/rfc2971]]::
653
637
  # Showalter, T., "IMAP4 ID extension", RFC 2971, DOI 10.17487/RFC2971,
654
638
  # October 2000, <https://www.rfc-editor.org/info/rfc2971>.
639
+ # [BINARY[https://tools.ietf.org/html/rfc3516]]::
640
+ # Nerenberg, L., "IMAP4 Binary Content Extension", RFC 3516,
641
+ # DOI 10.17487/RFC3516, April 2003,
642
+ # <https://www.rfc-editor.org/info/rfc3516>.
655
643
  # [ACL[https://tools.ietf.org/html/rfc4314]]::
656
644
  # Melnikov, A., "IMAP4 Access Control List (ACL) Extension", RFC 4314,
657
645
  # DOI 10.17487/RFC4314, December 2005,
@@ -675,31 +663,44 @@ module Net
675
663
  # Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
676
664
  # (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
677
665
  # <https://www.rfc-editor.org/info/rfc6851>.
666
+ # [UTF8=ACCEPT[https://tools.ietf.org/html/rfc6855]]::
667
+ # [UTF8=ONLY[https://tools.ietf.org/html/rfc6855]]::
668
+ # Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed.,
669
+ # "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013,
670
+ # <https://www.rfc-editor.org/info/rfc6855>.
678
671
  #
679
672
  # === IANA registries
680
- #
681
673
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
682
674
  # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
683
675
  # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
684
676
  # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
685
677
  # * {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
678
  # * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
695
679
  # * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]:
696
680
  # +imap+: tcp/143, +imaps+: tcp/993
697
681
  # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
698
682
  # +imap+
699
683
  # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
684
+ # ===== For currently unsupported features:
685
+ # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
686
+ # * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
687
+ # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
688
+ # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
689
+ # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
690
+ # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
700
691
  #
701
692
  class IMAP < Protocol
702
- VERSION = "0.3.7"
693
+ VERSION = "0.4.5"
694
+
695
+ # Aliases for supported capabilities, to be used with the #enable command.
696
+ ENABLE_ALIASES = {
697
+ utf8: "UTF8=ACCEPT",
698
+ "UTF8=ONLY" => "UTF8=ACCEPT",
699
+ }.freeze
700
+
701
+ autoload :SASL, File.expand_path("imap/sasl", __dir__)
702
+ autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
703
+ autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
703
704
 
704
705
  include MonitorMixin
705
706
  if defined?(OpenSSL::SSL)
@@ -707,35 +708,6 @@ module Net
707
708
  include SSL
708
709
  end
709
710
 
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
711
  # Returns the debug mode.
740
712
  def self.debug
741
713
  return @@debug
@@ -762,9 +734,175 @@ module Net
762
734
  alias default_ssl_port default_tls_port
763
735
  end
764
736
 
737
+ # Returns the initial greeting the server, an UntaggedResponse.
738
+ attr_reader :greeting
739
+
740
+ # Seconds to wait until a connection is opened.
741
+ # If the IMAP object cannot open a connection within this time,
742
+ # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
743
+ attr_reader :open_timeout
744
+
745
+ # Seconds to wait until an IDLE response is received.
746
+ attr_reader :idle_response_timeout
747
+
748
+ # The hostname this client connected to
749
+ attr_reader :host
750
+
751
+ # The port this client connected to
752
+ attr_reader :port
753
+
754
+ # Returns the
755
+ # {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]
756
+ # used by the SSLSocket when TLS is attempted, even when the TLS handshake
757
+ # is unsuccessful. The context object will be frozen.
758
+ #
759
+ # Returns +nil+ for a plaintext connection.
760
+ attr_reader :ssl_ctx
761
+
762
+ # Returns the parameters that were sent to #ssl_ctx
763
+ # {set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]
764
+ # when the connection tries to use TLS (even when unsuccessful).
765
+ #
766
+ # Returns +false+ for a plaintext connection.
767
+ attr_reader :ssl_ctx_params
768
+
769
+ # Creates a new Net::IMAP object and connects it to the specified
770
+ # +host+.
771
+ #
772
+ # ==== Options
773
+ #
774
+ # Accepts the following options:
775
+ #
776
+ # [port]
777
+ # Port number. Defaults to 993 when +ssl+ is truthy, and 143 otherwise.
778
+ #
779
+ # [ssl]
780
+ # If +true+, the connection will use TLS with the default params set by
781
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params].
782
+ # If +ssl+ is a hash, it's passed to
783
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
784
+ # the keys are names of attribute assignment methods on
785
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
786
+ #
787
+ # [open_timeout]
788
+ # Seconds to wait until a connection is opened
789
+ # [idle_response_timeout]
790
+ # Seconds to wait until an IDLE response is received
791
+ #
792
+ # See DeprecatedClientOptions.new for deprecated arguments.
793
+ #
794
+ # ==== Examples
795
+ #
796
+ # Connect to cleartext port 143 at mail.example.com and recieve the server greeting:
797
+ # imap = Net::IMAP.new('mail.example.com', ssl: false) # => #<Net::IMAP:0x00007f79b0872bd0>
798
+ # imap.port => 143
799
+ # imap.tls_verified? => false
800
+ # imap.greeting => name: ("OK" | "PREAUTH") => status
801
+ # status # => "OK"
802
+ # # The client is connected in the "Not Authenticated" state.
803
+ #
804
+ # Connect with TLS to port 993
805
+ # imap = Net::IMAP.new('mail.example.com', ssl: true) # => #<Net::IMAP:0x00007f79b0872bd0>
806
+ # imap.port => 993
807
+ # imap.tls_verified? => true
808
+ # imap.greeting => name: (/OK/i | /PREAUTH/i) => status
809
+ # case status
810
+ # in /OK/i
811
+ # # The client is connected in the "Not Authenticated" state.
812
+ # imap.authenticate("PLAIN", "joe_user", "joes_password")
813
+ # in /PREAUTH/i
814
+ # # The client is connected in the "Authenticated" state.
815
+ # end
816
+ #
817
+ # Connect with prior authentication, for example using an SSL certificate:
818
+ # ssl_ctx_params = {
819
+ # cert: OpenSSL::X509::Certificate.new(File.read("client.crt")),
820
+ # key: OpenSSL::PKey::EC.new(File.read('client.key')),
821
+ # extra_chain_cert: [
822
+ # OpenSSL::X509::Certificate.new(File.read("intermediate.crt")),
823
+ # ],
824
+ # }
825
+ # imap = Net::IMAP.new('mail.example.com', ssl: ssl_ctx_params)
826
+ # imap.port => 993
827
+ # imap.tls_verified? => true
828
+ # imap.greeting => name: "PREAUTH"
829
+ # # The client is connected in the "Authenticated" state.
830
+ #
831
+ # ==== Exceptions
832
+ #
833
+ # The most common errors are:
834
+ #
835
+ # [Errno::ECONNREFUSED]
836
+ # Connection refused by +host+ or an intervening firewall.
837
+ # [Errno::ETIMEDOUT]
838
+ # Connection timed out (possibly due to packets being dropped by an
839
+ # intervening firewall).
840
+ # [Errno::ENETUNREACH]
841
+ # There is no route to that network.
842
+ # [SocketError]
843
+ # Hostname not known or other socket error.
844
+ # [Net::IMAP::ByeResponseError]
845
+ # Connected to the host successfully, but it immediately said goodbye.
846
+ #
847
+ def initialize(host, port: nil, ssl: nil,
848
+ open_timeout: 30, idle_response_timeout: 5)
849
+ super()
850
+ # Config options
851
+ @host = host
852
+ @port = port || (ssl ? SSL_PORT : PORT)
853
+ @open_timeout = Integer(open_timeout)
854
+ @idle_response_timeout = Integer(idle_response_timeout)
855
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
856
+
857
+ # Basic Client State
858
+ @utf8_strings = false
859
+ @debug_output_bol = true
860
+ @exception = nil
861
+ @greeting = nil
862
+ @capabilities = nil
863
+
864
+ # Client Protocol Reciever
865
+ @parser = ResponseParser.new
866
+ @responses = Hash.new {|h, k| h[k] = [] }
867
+ @response_handlers = []
868
+ @receiver_thread = nil
869
+ @receiver_thread_exception = nil
870
+ @receiver_thread_terminating = false
871
+
872
+ # Client Protocol Sender (including state for currently running commands)
873
+ @tag_prefix = "RUBY"
874
+ @tagno = 0
875
+ @tagged_responses = {}
876
+ @tagged_response_arrival = new_cond
877
+ @continued_command_tag = nil
878
+ @continuation_request_arrival = new_cond
879
+ @continuation_request_exception = nil
880
+ @idle_done_cond = nil
881
+ @logout_command_tag = nil
882
+
883
+ # Connection
884
+ @tls_verified = false
885
+ @sock = tcp_socket(@host, @port)
886
+ start_tls_session if ssl_ctx
887
+ start_imap_connection
888
+
889
+ # DEPRECATED: to remove in next version
890
+ @client_thread = Thread.current
891
+ end
892
+
893
+ # Returns true after the TLS negotiation has completed and the remote
894
+ # hostname has been verified. Returns false when TLS has been established
895
+ # but peer verification was disabled.
896
+ def tls_verified?; @tls_verified end
897
+
898
+ def client_thread # :nodoc:
899
+ warn "Net::IMAP#client_thread is deprecated and will be removed soon."
900
+ @client_thread
901
+ end
902
+
765
903
  # Disconnects from the server.
766
904
  #
767
- # Related: #logout
905
+ # Related: #logout, #logout!
768
906
  def disconnect
769
907
  return if disconnected?
770
908
  begin
@@ -794,62 +932,123 @@ module Net
794
932
  return @sock.closed?
795
933
  end
796
934
 
797
- # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
798
- # and returns an array of capabilities that the server supports. Each
799
- # capability is a string.
935
+ # Returns whether the server supports a given +capability+. When available,
936
+ # cached #capabilities are used without sending a new #capability command to
937
+ # the server.
800
938
  #
801
- # See the {IANA IMAP4 capabilities
802
- # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list
803
- # of all standard capabilities, and their reference RFCs.
939
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
940
+ # behaviour according to the server's advertised #capabilities.</em>
804
941
  #
805
- # >>>
806
- # <em>*Note* that Net::IMAP does not currently modify its
807
- # behaviour according to the capabilities of the server;
808
- # it is up to the user of the class to ensure that
809
- # a certain capability is supported by a server before
810
- # using it.</em>
942
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
943
+ #
944
+ # Related: #auth_capable?, #capabilities, #capability, #enable
945
+ def capable?(capability) capabilities.include? capability.to_s.upcase end
946
+ alias capability? capable?
947
+
948
+ # Returns the server capabilities. When available, cached capabilities are
949
+ # used without sending a new #capability command to the server.
950
+ #
951
+ # To ensure a case-insensitive comparison, #capable? can be used instead.
952
+ #
953
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
954
+ # behaviour according to the server's advertised #capabilities.</em>
955
+ #
956
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
957
+ #
958
+ # Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable
959
+ def capabilities
960
+ @capabilities || capability
961
+ end
962
+
963
+ # Returns the #authenticate mechanisms that the server claims to support.
964
+ # These are derived from the #capabilities with an <tt>AUTH=</tt> prefix.
965
+ #
966
+ # This may be different when the connection is cleartext or using TLS. Most
967
+ # servers will drop all <tt>AUTH=</tt> mechanisms from #capabilities after
968
+ # the connection has authenticated.
969
+ #
970
+ # imap = Net::IMAP.new(hostname, ssl: false)
971
+ # imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"]
972
+ # imap.auth_mechanisms # => []
973
+ #
974
+ # imap.starttls
975
+ # imap.capabilities # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2",
976
+ # # "AUTH=OAUTHBEARER"]
977
+ # imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"]
978
+ #
979
+ # imap.authenticate("XOAUTH2", username, oauth2_access_token)
980
+ # imap.auth_mechanisms # => []
981
+ #
982
+ # Related: #authenticate, #auth_capable?, #capabilities
983
+ def auth_mechanisms
984
+ capabilities
985
+ .grep(/\AAUTH=/i)
986
+ .map { _1.delete_prefix("AUTH=") }
987
+ end
988
+
989
+ # Returns whether the server supports a given SASL +mechanism+ for use with
990
+ # the #authenticate command. The +mechanism+ is supported when
991
+ # #capabilities includes <tt>"AUTH=#{mechanism.to_s.upcase}"</tt>. When
992
+ # available, cached capabilities are used without sending a new #capability
993
+ # command to the server.
811
994
  #
812
- # Capability requirements—other than +IMAP4rev1+—are listed in the
813
- # documentation for each command method.
995
+ # imap.capable? "AUTH=PLAIN" # => true
996
+ # imap.auth_capable? "PLAIN" # => true
997
+ # imap.auth_capable? "blurdybloop" # => false
814
998
  #
815
- # ===== Basic IMAP4rev1 capabilities
999
+ # Related: #authenticate, #auth_mechanisms, #capable?, #capabilities
1000
+ def auth_capable?(mechanism)
1001
+ capable? "AUTH=#{mechanism}"
1002
+ end
1003
+
1004
+ # Returns whether capabilities have been cached. When true, #capable? and
1005
+ # #capabilities don't require sending a #capability command to the server.
816
1006
  #
817
- # All IMAP4rev1 servers must include +IMAP4rev1+ in their capabilities list.
818
- # All IMAP4rev1 servers must _implement_ the +STARTTLS+,
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.
1007
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
822
1008
  #
823
- # ===== Using IMAP4rev1 extensions
1009
+ # Related: #capable?, #capability, #clear_cached_capabilities
1010
+ def capabilities_cached?
1011
+ !!@capabilities
1012
+ end
1013
+
1014
+ # Clears capabilities that have been remembered by the Net::IMAP client.
1015
+ # This forces a #capability command to be sent the next time a #capabilities
1016
+ # query method is called.
824
1017
  #
825
- # IMAP4rev1 servers must not activate incompatible behavior until an
826
- # explicit client action invokes a capability, e.g. sending a command or
827
- # command argument specific to that capability. Extensions with backward
828
- # compatible behavior, such as response codes or mailbox attributes, may
829
- # be sent at any time.
1018
+ # Net::IMAP automatically discards its cached capabilities when they can
1019
+ # change. Explicitly calling this _should_ be unnecessary for well-behaved
1020
+ # servers.
830
1021
  #
831
- # Invoking capabilities which are unknown to Net::IMAP may cause unexpected
832
- # behavior and errors, for example ResponseParseError is raised when unknown
833
- # response syntax is received. Invoking commands or command parameters that
834
- # are unsupported by the server may raise NoResponseError, BadResponseError,
835
- # or cause other unexpected behavior.
1022
+ # Related: #capable?, #capability, #capabilities_cached?
1023
+ def clear_cached_capabilities
1024
+ synchronize do
1025
+ clear_responses("CAPABILITY")
1026
+ @capabilities = nil
1027
+ end
1028
+ end
1029
+
1030
+ # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
1031
+ # and returns an array of capabilities that are supported by the server.
1032
+ # The result is stored for use by #capable? and #capabilities.
836
1033
  #
837
- # ===== Caching +CAPABILITY+ responses
1034
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
1035
+ # behaviour according to the server's advertised #capabilities.</em>
838
1036
  #
839
- # Servers may send their capability list, unsolicited, using the
840
- # +CAPABILITY+ response code or an untagged +CAPABILITY+ response. These
841
- # responses can be retrieved and cached using #responses or
842
- # #add_response_handler.
1037
+ # Net::IMAP automatically stores and discards capability data according to
1038
+ # the requirements and recommendations in
1039
+ # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
1040
+ # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
1041
+ # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
1042
+ # Use #capable?, #auth_capable?, or #capabilities to this cache and avoid
1043
+ # sending the #capability command unnecessarily.
843
1044
  #
844
- # But cached capabilities _must_ be discarded after #starttls, #login, or
845
- # #authenticate. The OK TaggedResponse to #login and #authenticate may
846
- # include +CAPABILITY+ response code data, but the TaggedResponse for
847
- # #starttls is sent clear-text and cannot be trusted.
1045
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
848
1046
  #
1047
+ # Related: #capable?, #auth_capable?, #capability, #enable
849
1048
  def capability
850
1049
  synchronize do
851
1050
  send_command("CAPABILITY")
852
- return @responses.delete("CAPABILITY")[-1]
1051
+ @capabilities = clear_responses("CAPABILITY").last.freeze
853
1052
  end
854
1053
  end
855
1054
 
@@ -860,8 +1059,7 @@ module Net
860
1059
  # Note that the user should first check if the server supports the ID
861
1060
  # capability. For example:
862
1061
  #
863
- # capabilities = imap.capability
864
- # if capabilities.include?("ID")
1062
+ # if capable?(:ID)
865
1063
  # id = imap.id(
866
1064
  # name: "my IMAP client (ruby)",
867
1065
  # version: MyIMAP::VERSION,
@@ -875,11 +1073,11 @@ module Net
875
1073
  # ===== Capabilities
876
1074
  #
877
1075
  # The server's capabilities must include +ID+
878
- # [RFC2971[https://tools.ietf.org/html/rfc2971]]
1076
+ # [RFC2971[https://tools.ietf.org/html/rfc2971]].
879
1077
  def id(client_id=nil)
880
1078
  synchronize do
881
1079
  send_command("ID", ClientID.new(client_id))
882
- @responses.delete("ID")&.last
1080
+ clear_responses("ID").last
883
1081
  end
884
1082
  end
885
1083
 
@@ -888,7 +1086,7 @@ module Net
888
1086
  #
889
1087
  # This allows the server to send unsolicited untagged EXPUNGE #responses,
890
1088
  # but does not execute any client request. \IMAP servers are permitted to
891
- # send unsolicited untagged responses at any time, except for `EXPUNGE`.
1089
+ # send unsolicited untagged responses at any time, except for +EXPUNGE+:
892
1090
  #
893
1091
  # * +EXPUNGE+ can only be sent while a command is in progress.
894
1092
  # * +EXPUNGE+ must _not_ be sent during #fetch, #store, or #search.
@@ -903,15 +1101,43 @@ module Net
903
1101
  # to inform the command to inform the server that the client is done with
904
1102
  # the connection.
905
1103
  #
906
- # Related: #disconnect
1104
+ # Related: #disconnect, #logout!
907
1105
  def logout
908
1106
  send_command("LOGOUT")
909
1107
  end
910
1108
 
1109
+ # Calls #logout then, after receiving the TaggedResponse for the +LOGOUT+,
1110
+ # calls #disconnect. Returns the TaggedResponse from +LOGOUT+. Returns
1111
+ # +nil+ when the client is already disconnected, in contrast to #logout
1112
+ # which raises an exception.
1113
+ #
1114
+ # If #logout raises a StandardError, a warning will be printed but the
1115
+ # exception will not be re-raised.
1116
+ #
1117
+ # This is useful in situations where the connection must be dropped, for
1118
+ # example for security or after tests. If logout errors need to be handled,
1119
+ # use #logout and #disconnect instead.
1120
+ #
1121
+ # Related: #logout, #disconnect
1122
+ def logout!
1123
+ logout unless disconnected?
1124
+ rescue => ex
1125
+ warn "%s during <Net::IMAP %s:%s> logout!: %s" % [
1126
+ ex.class, host, port, ex
1127
+ ]
1128
+ ensure
1129
+ disconnect
1130
+ end
1131
+
911
1132
  # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1]
912
1133
  # to start a TLS session.
913
1134
  #
914
- # Any +options+ are forwarded to OpenSSL::SSL::SSLContext#set_params.
1135
+ # Any +options+ are forwarded directly to
1136
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
1137
+ # the keys are names of attribute assignment methods on
1138
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
1139
+ #
1140
+ # See DeprecatedClientOptions#starttls for deprecated arguments.
915
1141
  #
916
1142
  # This method returns after TLS negotiation and hostname verification are
917
1143
  # both successful. Any error indicates that the connection has not been
@@ -921,132 +1147,156 @@ module Net
921
1147
  # >>>
922
1148
  # Any #response_handlers added before STARTTLS should be aware that the
923
1149
  # TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation.
924
- # TLS negotiation starts immediately after that response.
1150
+ # TLS starts immediately _after_ that response. Any response code sent
1151
+ # with the response (e.g. CAPABILITY) is insecure and cannot be trusted.
925
1152
  #
926
1153
  # Related: Net::IMAP.new, #login, #authenticate
927
1154
  #
928
1155
  # ===== Capability
929
- #
930
- # The server's capabilities must include +STARTTLS+.
1156
+ # Clients should not call #starttls unless the server advertises the
1157
+ # +STARTTLS+ capability.
931
1158
  #
932
1159
  # Server capabilities may change after #starttls, #login, and #authenticate.
933
- # Cached capabilities _must_ be invalidated after this method completes.
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.
1160
+ # Cached #capabilities will be cleared when this method completes.
939
1161
  #
940
- def starttls(options = {}, verify = true)
1162
+ def starttls(**options)
1163
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
941
1164
  send_command("STARTTLS") do |resp|
942
1165
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
943
- begin
944
- # for backward compatibility
945
- certs = options.to_str
946
- options = create_ssl_params(certs, verify)
947
- rescue NoMethodError
948
- end
949
- start_tls_session(options)
1166
+ clear_cached_capabilities
1167
+ clear_responses
1168
+ start_tls_session
950
1169
  end
951
1170
  end
952
1171
  end
953
1172
 
954
1173
  # :call-seq:
955
- # authenticate(mechanism, ...) -> ok_resp
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
1174
+ # authenticate(mechanism, *, sasl_ir: true, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp
960
1175
  #
961
1176
  # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
962
1177
  # to authenticate the client. If successful, the connection enters the
963
1178
  # "_authenticated_" state.
964
1179
  #
965
1180
  # +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
1181
  #
971
- # An exception Net::IMAP::NoResponseError is raised if authentication fails.
1182
+ # +sasl_ir+ allows or disallows sending an "initial response" (see the
1183
+ # +SASL-IR+ capability, below).
972
1184
  #
973
- # Related: #login, #starttls
1185
+ # All other arguments are forwarded to the registered SASL authenticator for
1186
+ # the requested mechanism. <em>The documentation for each individual
1187
+ # mechanism must be consulted for its specific parameters.</em>
974
1188
  #
975
- # ==== Supported SASL Mechanisms
1189
+ # Related: #login, #starttls, #auth_capable?, #auth_mechanisms
976
1190
  #
977
- # +PLAIN+:: See PlainAuthenticator.
978
- # Login using clear-text username and password.
1191
+ # ==== Mechanisms
979
1192
  #
980
- # +XOAUTH2+:: See XOauth2Authenticator.
981
- # Login using a username and OAuth2 access token.
982
- # Non-standard and obsoleted by +OAUTHBEARER+, but widely
983
- # supported.
1193
+ # Each mechanism has different properties and requirements. Please consult
1194
+ # the documentation for the specific mechanisms you are using:
984
1195
  #
985
- # >>>
986
- # *Deprecated:* <em>Obsolete mechanisms are available for backwards
987
- # compatibility.</em>
1196
+ # +ANONYMOUS+::
1197
+ # See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
1198
+ #
1199
+ # Allows the user to gain access to public services or resources without
1200
+ # authenticating or disclosing an identity.
1201
+ #
1202
+ # +EXTERNAL+::
1203
+ # See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
1204
+ #
1205
+ # Authenticates using already established credentials, such as a TLS
1206
+ # certificate or IPsec.
988
1207
  #
989
- # For +DIGEST-MD5+ see DigestMD5Authenticator.
1208
+ # +OAUTHBEARER+::
1209
+ # See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator].
990
1210
  #
991
- # For +LOGIN+, see LoginAuthenticator.
1211
+ # Login using an OAuth2 Bearer token. This is the standard mechanism
1212
+ # for using OAuth2 with \SASL, but it is not yet deployed as widely as
1213
+ # +XOAUTH2+.
992
1214
  #
993
- # For +CRAM-MD5+, see CramMD5Authenticator.
1215
+ # +PLAIN+::
1216
+ # See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator].
994
1217
  #
995
- # <em>Using a deprecated mechanism will print a warning.</em>
1218
+ # Login using clear-text username and password.
996
1219
  #
997
- # See Net::IMAP::Authenticators for information on plugging in
998
- # authenticators for other mechanisms. See the {SASL mechanism
1220
+ # +SCRAM-SHA-1+::
1221
+ # +SCRAM-SHA-256+::
1222
+ # See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator].
1223
+ #
1224
+ # Login by username and password. The password is not sent to the
1225
+ # server but is used in a salted challenge/response exchange.
1226
+ # +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
1227
+ # Net::IMAP::SASL. New authenticators can easily be added for any other
1228
+ # <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
1229
+ # OpenSSL::Digest.
1230
+ #
1231
+ # +XOAUTH2+::
1232
+ # See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator].
1233
+ #
1234
+ # Login using a username and an OAuth2 access token. Non-standard and
1235
+ # obsoleted by +OAUTHBEARER+, but widely supported.
1236
+ #
1237
+ # See the {SASL mechanism
999
1238
  # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
1000
- # for information on these and other SASL mechanisms.
1239
+ # for a list of all SASL mechanisms and their specifications. To register
1240
+ # new authenticators, see Authenticators.
1001
1241
  #
1002
- # ===== Capabilities
1242
+ # ===== Deprecated mechanisms
1003
1243
  #
1004
- # Clients MUST NOT attempt to authenticate with a mechanism unless
1005
- # <tt>"AUTH=#{mechanism}"</tt> for that mechanism is a server capability.
1244
+ # <em>Obsolete mechanisms should be avoided, but are still available for
1245
+ # backwards compatibility. See</em> Net::IMAP::SASL@Deprecated+mechanisms.
1246
+ # <em>Using a deprecated mechanism will print a warning.</em>
1006
1247
  #
1007
- # Server capabilities may change after #starttls, #login, and #authenticate.
1008
- # Cached capabilities _must_ be invalidated after this method completes.
1009
- # The TaggedResponse to #authenticate may include updated capabilities in
1010
- # its ResponseCode.
1011
- #
1012
- # ===== Example
1013
- # If the authenticators ignore unhandled keyword arguments, the same config
1014
- # can be used for multiple mechanisms:
1015
- #
1016
- # password = nil # saved locally, so we don't ask more than once
1017
- # accesstok = nil # saved locally...
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
1248
+ # ==== Capabilities
1249
+ #
1250
+ # <tt>"AUTH=#{mechanism}"</tt> capabilities indicate server support for
1251
+ # mechanisms. Use #auth_capable? or #auth_mechanisms to check for support
1252
+ # before using a particular mechanism.
1253
+ #
1254
+ # if imap.auth_capable? "XOAUTH2"
1255
+ # imap.authenticate "XOAUTH2", username, oauth2_access_token
1256
+ # elsif imap.auth_capable? "PLAIN"
1257
+ # imap.authenticate "PLAIN", username, password
1258
+ # elsif !imap.capability? "LOGINDISABLED"
1037
1259
  # imap.login username, password
1260
+ # else
1261
+ # raise "No acceptable authentication mechanism is available"
1038
1262
  # end
1039
1263
  #
1040
- def authenticate(mechanism, *args, **props, &cb)
1041
- authenticator = self.class.authenticator(mechanism, *args, **props, &cb)
1042
- send_command("AUTHENTICATE", mechanism) do |resp|
1043
- if resp.instance_of?(ContinuationRequest)
1044
- data = authenticator.process(resp.data.text.unpack("m")[0])
1045
- s = [data].pack("m0")
1046
- send_string_data(s)
1047
- put_string(CRLF)
1048
- end
1264
+ # Although servers should list all supported \SASL mechanisms, they may
1265
+ # allow authentication with an unlisted +mechanism+.
1266
+ #
1267
+ # If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported
1268
+ # and the appropriate <tt>"AUTH=#{mechanism}"</tt> capability is present,
1269
+ # an "initial response" may be sent as an argument to the +AUTHENTICATE+
1270
+ # command, saving a round-trip. The SASL exchange allows for server
1271
+ # challenges and client responses, but many mechanisms expect the client to
1272
+ # "respond" first. The initial response will only be sent for
1273
+ # "client-first" mechanisms.
1274
+ #
1275
+ # Server capabilities may change after #starttls, #login, and #authenticate.
1276
+ # Previously cached #capabilities will be cleared when this method
1277
+ # completes. If the TaggedResponse to #authenticate includes updated
1278
+ # capabilities, they will be cached.
1279
+ def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback)
1280
+ mechanism = mechanism.to_s.tr("_", "-").upcase
1281
+ authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
1282
+ cmdargs = ["AUTHENTICATE", mechanism]
1283
+ if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
1284
+ authenticator.respond_to?(:initial_response?) &&
1285
+ authenticator.initial_response?
1286
+ response = authenticator.process(nil)
1287
+ cmdargs << (response.empty? ? "=" : [response].pack("m0"))
1288
+ end
1289
+ result = send_command_with_continuations(*cmdargs) {|data|
1290
+ challenge = data.unpack1("m")
1291
+ response = authenticator.process challenge
1292
+ [response].pack("m0")
1293
+ }
1294
+ if authenticator.respond_to?(:done?) && !authenticator.done?
1295
+ logout!
1296
+ raise SASL::AuthenticationIncomplete, result
1049
1297
  end
1298
+ @capabilities = capabilities_from_resp_code result
1299
+ result
1050
1300
  end
1051
1301
 
1052
1302
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1054,16 +1304,25 @@ module Net
1054
1304
  # this +user+. If successful, the connection enters the "_authenticated_"
1055
1305
  # state.
1056
1306
  #
1057
- # Using #authenticate is generally preferred over #login. The LOGIN command
1058
- # is not the same as #authenticate with the "LOGIN" +mechanism+.
1307
+ # Using #authenticate {should be
1308
+ # preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command]
1309
+ # over #login. The LOGIN command is not the same as #authenticate with the
1310
+ # "LOGIN" +mechanism+.
1059
1311
  #
1060
1312
  # A Net::IMAP::NoResponseError is raised if authentication fails.
1061
1313
  #
1062
1314
  # Related: #authenticate, #starttls
1063
1315
  #
1064
- # ==== Capabilities
1065
- # Clients MUST NOT call #login if +LOGINDISABLED+ is listed with the
1066
- # capabilities.
1316
+ # ===== Capabilities
1317
+ #
1318
+ # An IMAP client MUST NOT call #login when the server advertises the
1319
+ # +LOGINDISABLED+ capability.
1320
+ #
1321
+ # if imap.capability? "LOGINDISABLED"
1322
+ # raise "Remote server has disabled the login command"
1323
+ # else
1324
+ # imap.login username, password
1325
+ # end
1067
1326
  #
1068
1327
  # Server capabilities may change after #starttls, #login, and #authenticate.
1069
1328
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1072,17 +1331,18 @@ module Net
1072
1331
  #
1073
1332
  def login(user, password)
1074
1333
  send_command("LOGIN", user, password)
1334
+ .tap { @capabilities = capabilities_from_resp_code _1 }
1075
1335
  end
1076
1336
 
1077
1337
  # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
1078
1338
  # to select a +mailbox+ so that messages in the +mailbox+ can be accessed.
1079
1339
  #
1080
1340
  # After you have selected a mailbox, you may retrieve the number of items in
1081
- # that mailbox from <tt>imap.responses["EXISTS"][-1]</tt>, and the number of
1082
- # recent messages from <tt>imap.responses["RECENT"][-1]</tt>. Note that
1083
- # these values can change if new messages arrive during a session or when
1084
- # existing messages are expunged; see #add_response_handler for a way to
1085
- # detect these events.
1341
+ # that mailbox from <tt>imap.responses("EXISTS", &:last)</tt>, and the
1342
+ # number of recent messages from <tt>imap.responses("RECENT", &:last)</tt>.
1343
+ # Note that these values can change if new messages arrive during a session
1344
+ # or when existing messages are expunged; see #add_response_handler for a
1345
+ # way to detect these events.
1086
1346
  #
1087
1347
  # A Net::IMAP::NoResponseError is raised if the mailbox does not
1088
1348
  # exist or is for some reason non-selectable.
@@ -1095,7 +1355,7 @@ module Net
1095
1355
  # the server may return an untagged "NO" response with a "UIDNOTSTICKY"
1096
1356
  # response code indicating that the mailstore does not support persistent
1097
1357
  # UIDs:
1098
- # @responses["NO"].last.code.name == "UIDNOTSTICKY"
1358
+ # imap.responses("NO", &:last)&.code&.name == "UIDNOTSTICKY"
1099
1359
  def select(mailbox)
1100
1360
  synchronize do
1101
1361
  @responses.clear
@@ -1185,10 +1445,10 @@ module Net
1185
1445
  # to the client. +refname+ provides a context (for instance, a base
1186
1446
  # directory in a directory-based mailbox hierarchy). +mailbox+ specifies a
1187
1447
  # mailbox or (via wildcards) mailboxes under that context. Two wildcards
1188
- # may be used in +mailbox+: '*', which matches all characters *including*
1189
- # the hierarchy delimiter (for instance, '/' on a UNIX-hosted
1190
- # directory-based mailbox hierarchy); and '%', which matches all characters
1191
- # *except* the hierarchy delimiter.
1448
+ # may be used in +mailbox+: <tt>"*"</tt>, which matches all characters
1449
+ # *including* the hierarchy delimiter (for instance, "/" on a UNIX-hosted
1450
+ # directory-based mailbox hierarchy); and <tt>"%"</tt>, which matches all
1451
+ # characters *except* the hierarchy delimiter.
1192
1452
  #
1193
1453
  # If +refname+ is empty, +mailbox+ is used directly to determine
1194
1454
  # which mailboxes to match. If +mailbox+ is empty, the root
@@ -1213,7 +1473,7 @@ module Net
1213
1473
  def list(refname, mailbox)
1214
1474
  synchronize do
1215
1475
  send_command("LIST", refname, mailbox)
1216
- return @responses.delete("LIST")
1476
+ clear_responses("LIST")
1217
1477
  end
1218
1478
  end
1219
1479
 
@@ -1236,23 +1496,22 @@ module Net
1236
1496
  # servers, then folder creation (and listing, moving, etc) can lead to
1237
1497
  # errors.
1238
1498
  #
1239
- # From RFC2342:
1240
- #
1241
- # Although typically a server will support only a single Personal
1499
+ # From RFC2342[https://tools.ietf.org/html/rfc2342]:
1500
+ # >>>
1501
+ # <em>Although typically a server will support only a single Personal
1242
1502
  # Namespace, and a single Other User's Namespace, circumstances exist
1243
1503
  # where there MAY be multiples of these, and a client MUST be prepared
1244
1504
  # for them. If a client is configured such that it is required to create
1245
1505
  # a certain mailbox, there can be circumstances where it is unclear which
1246
1506
  # Personal Namespaces it should create the mailbox in. In these
1247
1507
  # situations a client SHOULD let the user select which namespaces to
1248
- # create the mailbox in.
1508
+ # create the mailbox in.</em>
1249
1509
  #
1250
1510
  # Related: #list, Namespaces, Namespace
1251
1511
  #
1252
1512
  # ===== For example:
1253
1513
  #
1254
- # capabilities = imap.capability
1255
- # if capabilities.include?("NAMESPACE")
1514
+ # if capable?("NAMESPACE")
1256
1515
  # namespaces = imap.namespace
1257
1516
  # if namespace = namespaces.personal.first
1258
1517
  # prefix = namespace.prefix # e.g. "" or "INBOX."
@@ -1271,7 +1530,7 @@ module Net
1271
1530
  def namespace
1272
1531
  synchronize do
1273
1532
  send_command("NAMESPACE")
1274
- return @responses.delete("NAMESPACE")[-1]
1533
+ clear_responses("NAMESPACE").last
1275
1534
  end
1276
1535
  end
1277
1536
 
@@ -1315,7 +1574,7 @@ module Net
1315
1574
  def xlist(refname, mailbox)
1316
1575
  synchronize do
1317
1576
  send_command("XLIST", refname, mailbox)
1318
- return @responses.delete("XLIST")
1577
+ clear_responses("XLIST")
1319
1578
  end
1320
1579
  end
1321
1580
 
@@ -1334,8 +1593,8 @@ module Net
1334
1593
  synchronize do
1335
1594
  send_command("GETQUOTAROOT", mailbox)
1336
1595
  result = []
1337
- result.concat(@responses.delete("QUOTAROOT"))
1338
- result.concat(@responses.delete("QUOTA"))
1596
+ result.concat(clear_responses("QUOTAROOT"))
1597
+ result.concat(clear_responses("QUOTA"))
1339
1598
  return result
1340
1599
  end
1341
1600
  end
@@ -1354,7 +1613,7 @@ module Net
1354
1613
  def getquota(mailbox)
1355
1614
  synchronize do
1356
1615
  send_command("GETQUOTA", mailbox)
1357
- return @responses.delete("QUOTA")
1616
+ clear_responses("QUOTA")
1358
1617
  end
1359
1618
  end
1360
1619
 
@@ -1410,7 +1669,7 @@ module Net
1410
1669
  def getacl(mailbox)
1411
1670
  synchronize do
1412
1671
  send_command("GETACL", mailbox)
1413
- return @responses.delete("ACL")[-1]
1672
+ clear_responses("ACL").last
1414
1673
  end
1415
1674
  end
1416
1675
 
@@ -1425,31 +1684,62 @@ module Net
1425
1684
  def lsub(refname, mailbox)
1426
1685
  synchronize do
1427
1686
  send_command("LSUB", refname, mailbox)
1428
- return @responses.delete("LSUB")
1687
+ clear_responses("LSUB")
1429
1688
  end
1430
1689
  end
1431
1690
 
1432
1691
  # Sends a {STATUS commands [IMAP4rev1 §6.3.10]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.10]
1433
1692
  # and returns the status of the indicated +mailbox+. +attr+ is a list of one
1434
- # or more attributes whose statuses are to be requested. Supported
1435
- # attributes include:
1693
+ # or more attributes whose statuses are to be requested.
1694
+ #
1695
+ # The return value is a hash of attributes.
1696
+ #
1697
+ # A Net::IMAP::NoResponseError is raised if status values
1698
+ # for +mailbox+ cannot be returned; for instance, because it
1699
+ # does not exist.
1700
+ #
1701
+ # ===== Supported attributes:
1702
+ #
1703
+ # +MESSAGES+:: The number of messages in the mailbox.
1704
+ #
1705
+ # +UIDNEXT+:: The next unique identifier value of the mailbox.
1706
+ #
1707
+ # +UIDVALIDITY+:: The unique identifier validity value of the mailbox.
1436
1708
  #
1437
- # MESSAGES:: the number of messages in the mailbox.
1438
- # RECENT:: the number of recent messages in the mailbox.
1439
- # UNSEEN:: the number of unseen messages in the mailbox.
1709
+ # +UNSEEN+:: The number of messages without the <tt>\Seen</tt> flag.
1440
1710
  #
1441
- # The return value is a hash of attributes. For example:
1711
+ # +DELETED+:: The number of messages with the <tt>\Deleted</tt> flag.
1712
+ #
1713
+ # +SIZE+::
1714
+ # The approximate size of the mailbox---must be greater than or equal to
1715
+ # the sum of all messages' +RFC822.SIZE+ fetch item values.
1716
+ #
1717
+ # +MAILBOXID+::
1718
+ # A server-allocated unique identifier for the mailbox.
1719
+ # See +OBJECTID+
1720
+ # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html#section-4].
1721
+ #
1722
+ # +RECENT+::
1723
+ # The number of messages with the <tt>\Recent</tt> flag.
1724
+ # _NOTE:_ +RECENT+ was removed from IMAP4rev2.
1725
+ #
1726
+ # ===== For example:
1442
1727
  #
1443
1728
  # p imap.status("inbox", ["MESSAGES", "RECENT"])
1444
1729
  # #=> {"RECENT"=>0, "MESSAGES"=>44}
1445
1730
  #
1446
- # A Net::IMAP::NoResponseError is raised if status values
1447
- # for +mailbox+ cannot be returned; for instance, because it
1448
- # does not exist.
1731
+ # ===== Capabilities
1732
+ #
1733
+ # +SIZE+ requires the server's capabilities to include either +IMAP4rev2+ or
1734
+ # <tt>STATUS=SIZE</tt>
1735
+ # {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html].
1736
+ #
1737
+ # +MAILBOXID+ requires the server's capabilities to include +OBJECTID+
1738
+ # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
1449
1739
  def status(mailbox, attr)
1450
1740
  synchronize do
1451
1741
  send_command("STATUS", mailbox, attr)
1452
- return @responses.delete("STATUS")[-1].attr
1742
+ clear_responses("STATUS").last&.attr
1453
1743
  end
1454
1744
  end
1455
1745
 
@@ -1538,7 +1828,7 @@ module Net
1538
1828
  def expunge
1539
1829
  synchronize do
1540
1830
  send_command("EXPUNGE")
1541
- return @responses.delete("EXPUNGE")
1831
+ clear_responses("EXPUNGE")
1542
1832
  end
1543
1833
  end
1544
1834
 
@@ -1570,7 +1860,7 @@ module Net
1570
1860
  def uid_expunge(uid_set)
1571
1861
  synchronize do
1572
1862
  send_command("UID EXPUNGE", MessageSet.new(uid_set))
1573
- return @responses.delete("EXPUNGE")
1863
+ clear_responses("EXPUNGE")
1574
1864
  end
1575
1865
  end
1576
1866
 
@@ -1589,7 +1879,7 @@ module Net
1589
1879
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
1590
1880
  # in addition to documentation for
1591
1881
  # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
1592
- # reported by #capability which may define additional search filters, e.g:
1882
+ # reported by #capabilities which may define additional search filters, e.g:
1593
1883
  # +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
1594
1884
  # +SAVEDATE+. The following are some common search criteria:
1595
1885
  #
@@ -1656,8 +1946,7 @@ module Net
1656
1946
  # +attr+ is a list of attributes to fetch; see the documentation
1657
1947
  # for FetchData for a list of valid attributes.
1658
1948
  #
1659
- # The return value is an array of FetchData or nil
1660
- # (instead of an empty array) if there is no matching message.
1949
+ # The return value is an array of FetchData.
1661
1950
  #
1662
1951
  # Related: #uid_search, FetchData
1663
1952
  #
@@ -1702,11 +1991,11 @@ module Net
1702
1991
  # to alter data associated with messages in the mailbox, in particular their
1703
1992
  # flags. The +set+ parameter is a number, an array of numbers, or a Range
1704
1993
  # object. Each number is a message sequence number. +attr+ is the name of a
1705
- # data item to store: 'FLAGS' will replace the message's flag list with the
1706
- # provided one, '+FLAGS' will add the provided flags, and '-FLAGS' will
1707
- # remove them. +flags+ is a list of flags.
1994
+ # data item to store: <tt>"FLAGS"</tt> will replace the message's flag list
1995
+ # with the provided one, <tt>"+FLAGS"</tt> will add the provided flags, and
1996
+ # <tt>"-FLAGS"</tt> will remove them. +flags+ is a list of flags.
1708
1997
  #
1709
- # The return value is an array of FetchData
1998
+ # The return value is an array of FetchData.
1710
1999
  #
1711
2000
  # Related: #uid_store
1712
2001
  #
@@ -1884,6 +2173,87 @@ module Net
1884
2173
  return thread_internal("UID THREAD", algorithm, search_keys, charset)
1885
2174
  end
1886
2175
 
2176
+ # Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1]
2177
+ # {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1]
2178
+ # to enable the specified server +capabilities+. Each capability may be an
2179
+ # array, string, or symbol. Returns a list of the capabilities that were
2180
+ # enabled.
2181
+ #
2182
+ # The +ENABLE+ command is only valid in the _authenticated_ state, before
2183
+ # any mailbox is selected.
2184
+ #
2185
+ # Related: #capable?, #capabilities, #capability
2186
+ #
2187
+ # ===== Capabilities
2188
+ #
2189
+ # The server's capabilities must include
2190
+ # +ENABLE+ [RFC5161[https://tools.ietf.org/html/rfc5161]]
2191
+ # or +IMAP4REV2+ [RFC9051[https://tools.ietf.org/html/rfc9051]].
2192
+ #
2193
+ # Additionally, the server capabilities must include a capability matching
2194
+ # each enabled extension (usually the same name as the enabled extension).
2195
+ # The following capabilities may be enabled:
2196
+ #
2197
+ # [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
2198
+ #
2199
+ # In a future release, <tt>enable(:utf8)</tt> will enable either
2200
+ # <tt>"UTF8=ACCEPT"</tt> or <tt>"IMAP4rev2"</tt>, depending on server
2201
+ # capabilities.
2202
+ #
2203
+ # [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2204
+ #
2205
+ # The server's capabilities must include <tt>UTF8=ACCEPT</tt> _or_
2206
+ # <tt>UTF8=ONLY</tt>.
2207
+ #
2208
+ # This allows the server to send strings encoded as UTF-8 which might
2209
+ # otherwise need to use a 7-bit encoding, such as {modified
2210
+ # UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for
2211
+ # message headers.
2212
+ #
2213
+ # *Note:* <em>A future update may set string encodings slightly
2214
+ # differently</em>, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8"
2215
+ # when it is. Currently, the encoding of strings sent as "quoted" or
2216
+ # "text" will _always_ be "UTF-8", even when only ASCII characters are
2217
+ # used (e.g. "Subject: Agenda") And currently, string "literals" sent
2218
+ # by the server will always have an "ASCII-8BIT" (binary)
2219
+ # encoding, even if they generally contain UTF-8 data, if they are
2220
+ # text at all.
2221
+ #
2222
+ # [<tt>"UTF8=ONLY"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2223
+ #
2224
+ # A server that reports the <tt>UTF8=ONLY</tt> capability _requires_ that
2225
+ # the client <tt>enable("UTF8=ACCEPT")</tt> before any mailboxes may be
2226
+ # selected. For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
2227
+ # <tt>enable("UTF8=ACCEPT")</tt>.
2228
+ #
2229
+ # ===== Unsupported capabilities
2230
+ #
2231
+ # *Note:* Some extensions that use ENABLE permit the server to send syntax
2232
+ # that Net::IMAP cannot parse, which may raise an exception and disconnect.
2233
+ # Some extensions may work, but the support may be incomplete, untested, or
2234
+ # experimental.
2235
+ #
2236
+ # Until a capability is documented here as supported, enabling it may result
2237
+ # in undocumented behavior and a future release may update with incompatible
2238
+ # behavior <em>without warning or deprecation</em>.
2239
+ #
2240
+ # <em>Caution is advised.</em>
2241
+ #
2242
+ def enable(*capabilities)
2243
+ capabilities = capabilities
2244
+ .flatten
2245
+ .map {|e| ENABLE_ALIASES[e] || e }
2246
+ .uniq
2247
+ .join(' ')
2248
+ synchronize do
2249
+ send_command("ENABLE #{capabilities}")
2250
+ result = clear_responses("ENABLED").last || []
2251
+ @utf8_strings ||= result.include? "UTF8=ACCEPT"
2252
+ @utf8_strings ||= result.include? "IMAP4REV2"
2253
+ result
2254
+ end
2255
+ end
2256
+
1887
2257
  # Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3]
1888
2258
  # {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13]
1889
2259
  # that waits for notifications of new or expunged messages. Yields
@@ -1948,6 +2318,104 @@ module Net
1948
2318
  end
1949
2319
  end
1950
2320
 
2321
+ # :call-seq:
2322
+ # responses {|hash| ...} -> block result
2323
+ # responses(type) {|array| ...} -> block result
2324
+ #
2325
+ # Yields unhandled responses and returns the result of the block.
2326
+ #
2327
+ # Unhandled responses are stored in a hash, with arrays of
2328
+ # <em>non-+nil+</em> UntaggedResponse#data keyed by UntaggedResponse#name
2329
+ # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
2330
+ # yield the entire responses hash. Call with +type+ to yield only the array
2331
+ # of responses for that type.
2332
+ #
2333
+ # For example:
2334
+ #
2335
+ # imap.select("inbox")
2336
+ # p imap.responses("EXISTS", &:last)
2337
+ # #=> 2
2338
+ # p imap.responses("UIDVALIDITY", &:last)
2339
+ # #=> 968263756
2340
+ #
2341
+ # >>>
2342
+ # *Note:* Access to the responses hash is synchronized for thread-safety.
2343
+ # The receiver thread and response_handlers cannot process new responses
2344
+ # until the block completes. Accessing either the response hash or its
2345
+ # response type arrays outside of the block is unsafe.
2346
+ #
2347
+ # Calling without a block is unsafe and deprecated. Future releases will
2348
+ # raise ArgumentError unless a block is given.
2349
+ #
2350
+ # Previously unhandled responses are automatically cleared before entering a
2351
+ # mailbox with #select or #examine. Long-lived connections can receive many
2352
+ # unhandled server responses, which must be pruned or they will continually
2353
+ # consume more memory. Update or clear the responses hash or arrays inside
2354
+ # the block, or use #clear_responses.
2355
+ #
2356
+ # Only non-+nil+ data is stored. Many important response codes have no data
2357
+ # of their own, but are used as "tags" on the ResponseText object they are
2358
+ # attached to. ResponseText will be accessible by its response types:
2359
+ # "+OK+", "+NO+", "+BAD+", "+BYE+", or "+PREAUTH+".
2360
+ #
2361
+ # TaggedResponse#data is not saved to #responses, nor is any
2362
+ # ResponseCode#data on tagged responses. Although some command methods do
2363
+ # return the TaggedResponse directly, #add_response_handler must be used to
2364
+ # handle all response codes.
2365
+ #
2366
+ # Related: #clear_responses, #response_handlers, #greeting
2367
+ def responses(type = nil)
2368
+ if block_given?
2369
+ synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
2370
+ elsif type
2371
+ raise ArgumentError, "Pass a block or use #clear_responses"
2372
+ else
2373
+ # warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1)
2374
+ @responses
2375
+ end
2376
+ end
2377
+
2378
+ # :call-seq:
2379
+ # clear_responses -> hash
2380
+ # clear_responses(type) -> array
2381
+ #
2382
+ # Clears and returns the unhandled #responses hash or the unhandled
2383
+ # responses array for a single response +type+.
2384
+ #
2385
+ # Clearing responses is synchronized with other threads. The lock is
2386
+ # released before returning.
2387
+ #
2388
+ # Related: #responses, #response_handlers
2389
+ def clear_responses(type = nil)
2390
+ synchronize {
2391
+ if type
2392
+ @responses.delete(type) || []
2393
+ else
2394
+ @responses.dup.transform_values(&:freeze)
2395
+ .tap { _1.default = [].freeze }
2396
+ .tap { @responses.clear }
2397
+ end
2398
+ }
2399
+ .freeze
2400
+ end
2401
+
2402
+ # Returns all response handlers, including those that are added internally
2403
+ # by commands. Each response handler will be called with every new
2404
+ # UntaggedResponse, TaggedResponse, and ContinuationRequest.
2405
+ #
2406
+ # Response handlers are called with a mutex inside the receiver thread. New
2407
+ # responses cannot be processed and commands from other threads must wait
2408
+ # until all response_handlers return. An exception will shut-down the
2409
+ # receiver thread and close the connection.
2410
+ #
2411
+ # For thread-safety, the returned array is a frozen copy of the internal
2412
+ # array.
2413
+ #
2414
+ # Related: #add_response_handler, #remove_response_handler
2415
+ def response_handlers
2416
+ synchronize { @response_handlers.clone.freeze }
2417
+ end
2418
+
1951
2419
  # Adds a response handler. For example, to detect when
1952
2420
  # the server sends a new EXISTS response (which normally
1953
2421
  # indicates new messages being added to the mailbox),
@@ -1960,14 +2428,21 @@ module Net
1960
2428
  # end
1961
2429
  # }
1962
2430
  #
2431
+ # Related: #remove_response_handler, #response_handlers
1963
2432
  def add_response_handler(handler = nil, &block)
1964
2433
  raise ArgumentError, "two Procs are passed" if handler && block
1965
- @response_handlers.push(block || handler)
2434
+ synchronize do
2435
+ @response_handlers.push(block || handler)
2436
+ end
1966
2437
  end
1967
2438
 
1968
2439
  # Removes the response handler.
2440
+ #
2441
+ # Related: #add_response_handler, #response_handlers
1969
2442
  def remove_response_handler(handler)
1970
- @response_handlers.delete(handler)
2443
+ synchronize do
2444
+ @response_handlers.delete(handler)
2445
+ end
1971
2446
  end
1972
2447
 
1973
2448
  private
@@ -1978,93 +2453,29 @@ module Net
1978
2453
 
1979
2454
  @@debug = false
1980
2455
 
1981
- # :call-seq:
1982
- # Net::IMAP.new(host, options = {})
1983
- #
1984
- # Creates a new Net::IMAP object and connects it to the specified
1985
- # +host+.
1986
- #
1987
- # +options+ is an option hash, each key of which is a symbol.
1988
- #
1989
- # The available options are:
1990
- #
1991
- # port:: Port number (default value is 143 for imap, or 993 for imaps)
1992
- # ssl:: If +options[:ssl]+ is true, then an attempt will be made
1993
- # to use SSL (now TLS) to connect to the server.
1994
- # If +options[:ssl]+ is a hash, it's passed to
1995
- # OpenSSL::SSL::SSLContext#set_params as parameters.
1996
- # open_timeout:: Seconds to wait until a connection is opened
1997
- # idle_response_timeout:: Seconds to wait until an IDLE response is received
1998
- #
1999
- # The most common errors are:
2000
- #
2001
- # Errno::ECONNREFUSED:: Connection refused by +host+ or an intervening
2002
- # firewall.
2003
- # Errno::ETIMEDOUT:: Connection timed out (possibly due to packets
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
2456
+ def start_imap_connection
2457
+ @greeting = get_server_greeting
2458
+ @capabilities = capabilities_from_resp_code @greeting
2459
+ @receiver_thread = start_receiver_thread
2460
+ rescue Exception
2461
+ @sock.close
2462
+ raise
2463
+ end
2464
+
2465
+ def get_server_greeting
2466
+ greeting = get_response
2467
+ raise Error, "No server greeting - connection closed" unless greeting
2468
+ record_untagged_response_code greeting
2469
+ raise ByeResponseError, greeting if greeting.name == "BYE"
2470
+ greeting
2471
+ end
2472
+
2473
+ def start_receiver_thread
2474
+ Thread.start do
2475
+ receive_responses
2476
+ rescue Exception => ex
2477
+ @receiver_thread_exception = ex
2478
+ # don't exit the thread with an exception
2068
2479
  end
2069
2480
  end
2070
2481
 
@@ -2113,11 +2524,7 @@ module Net
2113
2524
  @continuation_request_arrival.signal
2114
2525
  end
2115
2526
  when UntaggedResponse
2116
- record_response(resp.name, resp.data)
2117
- if resp.data.instance_of?(ResponseText) &&
2118
- (code = resp.data.code)
2119
- record_response(code.name, code.data)
2120
- end
2527
+ record_untagged_response(resp)
2121
2528
  if resp.name == "BYE" && @logout_command_tag.nil?
2122
2529
  @sock.close
2123
2530
  @exception = ByeResponseError.new(resp)
@@ -2171,7 +2578,8 @@ module Net
2171
2578
  when /\A(?:BAD)\z/ni
2172
2579
  raise BadResponseError, resp
2173
2580
  else
2174
- raise UnknownResponseError, resp
2581
+ disconnect
2582
+ raise InvalidResponseError, "invalid tagged resp: %p" % [resp.raw.chomp]
2175
2583
  end
2176
2584
  end
2177
2585
 
@@ -2195,11 +2603,42 @@ module Net
2195
2603
  return @parser.parse(buff)
2196
2604
  end
2197
2605
 
2198
- def record_response(name, data)
2199
- unless @responses.has_key?(name)
2200
- @responses[name] = []
2606
+ #############################
2607
+ # built-in response handlers
2608
+
2609
+ # store name => [..., data]
2610
+ def record_untagged_response(resp)
2611
+ @responses[resp.name] << resp.data
2612
+ record_untagged_response_code resp
2613
+ end
2614
+
2615
+ # store code.name => [..., code.data]
2616
+ def record_untagged_response_code(resp)
2617
+ return unless resp.data.is_a?(ResponseText)
2618
+ return unless (code = resp.data.code)
2619
+ @responses[code.name] << code.data
2620
+ end
2621
+
2622
+ # NOTE: only call this for greeting, login, and authenticate
2623
+ def capabilities_from_resp_code(resp)
2624
+ return unless %w[PREAUTH OK].any? { _1.casecmp? resp.name }
2625
+ return unless (code = resp.data.code)
2626
+ return unless code.name.casecmp?("CAPABILITY")
2627
+ code.data.freeze
2628
+ end
2629
+
2630
+ #############################
2631
+
2632
+ # Calls send_command, yielding the text of each ContinuationRequest and
2633
+ # responding with each block result. Returns TaggedResponse. Raises
2634
+ # NoResponseError or BadResponseError.
2635
+ def send_command_with_continuations(cmd, *args)
2636
+ send_command(cmd, *args) do |server_response|
2637
+ if server_response.instance_of?(ContinuationRequest)
2638
+ client_response = yield server_response.data.text
2639
+ put_string(client_response + CRLF)
2640
+ end
2201
2641
  end
2202
- @responses[name].push(data)
2203
2642
  end
2204
2643
 
2205
2644
  def send_command(cmd, *args, &block)
@@ -2241,8 +2680,8 @@ module Net
2241
2680
  if @debug_output_bol
2242
2681
  $stderr.print("C: ")
2243
2682
  end
2244
- $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
2245
- if /\r\n\z/n.match(str)
2683
+ $stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " })
2684
+ if /\n\z/n.match(str)
2246
2685
  @debug_output_bol = true
2247
2686
  else
2248
2687
  @debug_output_bol = false
@@ -2262,7 +2701,7 @@ module Net
2262
2701
  else
2263
2702
  send_command(cmd, *keys)
2264
2703
  end
2265
- return @responses.delete("SEARCH")[-1]
2704
+ clear_responses("SEARCH").last || []
2266
2705
  end
2267
2706
  end
2268
2707
 
@@ -2277,13 +2716,13 @@ module Net
2277
2716
  end
2278
2717
 
2279
2718
  synchronize do
2280
- @responses.delete("FETCH")
2719
+ clear_responses("FETCH")
2281
2720
  if mod
2282
2721
  send_command(cmd, MessageSet.new(set), attr, mod)
2283
2722
  else
2284
2723
  send_command(cmd, MessageSet.new(set), attr)
2285
2724
  end
2286
- return @responses.delete("FETCH")
2725
+ clear_responses("FETCH")
2287
2726
  end
2288
2727
  end
2289
2728
 
@@ -2292,9 +2731,9 @@ module Net
2292
2731
  attr = RawData.new(attr)
2293
2732
  end
2294
2733
  synchronize do
2295
- @responses.delete("FETCH")
2734
+ clear_responses("FETCH")
2296
2735
  send_command(cmd, MessageSet.new(set), attr, flags)
2297
- return @responses.delete("FETCH")
2736
+ clear_responses("FETCH")
2298
2737
  end
2299
2738
  end
2300
2739
 
@@ -2311,7 +2750,7 @@ module Net
2311
2750
  normalize_searching_criteria(search_keys)
2312
2751
  synchronize do
2313
2752
  send_command(cmd, sort_keys, charset, *search_keys)
2314
- return @responses.delete("SORT")[-1]
2753
+ clear_responses("SORT").last || []
2315
2754
  end
2316
2755
  end
2317
2756
 
@@ -2322,8 +2761,10 @@ module Net
2322
2761
  normalize_searching_criteria(search_keys)
2323
2762
  end
2324
2763
  normalize_searching_criteria(search_keys)
2325
- send_command(cmd, algorithm, charset, *search_keys)
2326
- return @responses.delete("THREAD")[-1]
2764
+ synchronize do
2765
+ send_command(cmd, algorithm, charset, *search_keys)
2766
+ clear_responses("THREAD").last || []
2767
+ end
2327
2768
  end
2328
2769
 
2329
2770
  def normalize_searching_criteria(keys)
@@ -2337,49 +2778,49 @@ module Net
2337
2778
  end
2338
2779
  end
2339
2780
 
2340
- def create_ssl_params(certs = nil, verify = true)
2341
- params = {}
2342
- if certs
2343
- if File.file?(certs)
2344
- params[:ca_file] = certs
2345
- elsif File.directory?(certs)
2346
- params[:ca_path] = certs
2781
+ def build_ssl_ctx(ssl)
2782
+ if ssl
2783
+ params = (Hash.try_convert(ssl) || {}).freeze
2784
+ context = SSLContext.new
2785
+ context.set_params(params)
2786
+ if defined?(VerifyCallbackProc)
2787
+ context.verify_callback = VerifyCallbackProc
2347
2788
  end
2348
- end
2349
- if verify
2350
- params[:verify_mode] = VERIFY_PEER
2789
+ context.freeze
2790
+ [params, context]
2351
2791
  else
2352
- params[:verify_mode] = VERIFY_NONE
2792
+ false
2353
2793
  end
2354
- return params
2355
2794
  end
2356
2795
 
2357
- def start_tls_session(params = {})
2358
- unless defined?(OpenSSL::SSL)
2359
- raise "SSL extension not installed"
2360
- end
2361
- if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
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)
2796
+ def start_tls_session
2797
+ raise "SSL extension not installed" unless defined?(OpenSSL::SSL)
2798
+ raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
2799
+ raise "cannot start TLS without SSLContext" unless ssl_ctx
2800
+ @sock = SSLSocket.new(@sock, ssl_ctx)
2375
2801
  @sock.sync_close = true
2376
2802
  @sock.hostname = @host if @sock.respond_to? :hostname=
2377
2803
  ssl_socket_connect(@sock, @open_timeout)
2378
- if context.verify_mode != VERIFY_NONE
2804
+ if ssl_ctx.verify_mode != VERIFY_NONE
2379
2805
  @sock.post_connection_check(@host)
2806
+ @tls_verified = true
2380
2807
  end
2381
2808
  end
2382
2809
 
2810
+ def sasl_adapter
2811
+ SASLAdapter.new(self, &method(:send_command_with_continuations))
2812
+ end
2813
+
2814
+ #--
2815
+ # We could get the saslprep method by extending the SASLprep module
2816
+ # directly. It's done indirectly, so SASLprep can be lazily autoloaded,
2817
+ # because most users won't need it.
2818
+ #++
2819
+ # Delegates to Net::IMAP::StringPrep::SASLprep#saslprep.
2820
+ def self.saslprep(string, **opts)
2821
+ Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
2822
+ end
2823
+
2383
2824
  end
2384
2825
  end
2385
2826
 
@@ -2390,4 +2831,6 @@ require_relative "imap/flags"
2390
2831
  require_relative "imap/response_data"
2391
2832
  require_relative "imap/response_parser"
2392
2833
  require_relative "imap/authenticators"
2393
- require_relative "imap/sasl"
2834
+
2835
+ require_relative "imap/deprecated_client_options"
2836
+ Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions