net-imap 0.3.4 → 0.4.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pages.yml +46 -0
  3. data/.github/workflows/test.yml +12 -12
  4. data/Gemfile +1 -0
  5. data/README.md +15 -4
  6. data/Rakefile +0 -7
  7. data/benchmarks/generate_parser_benchmarks +52 -0
  8. data/benchmarks/parser.yml +578 -0
  9. data/benchmarks/stringprep.yml +1 -1
  10. data/lib/net/imap/authenticators.rb +26 -57
  11. data/lib/net/imap/command_data.rb +13 -6
  12. data/lib/net/imap/data_encoding.rb +3 -3
  13. data/lib/net/imap/deprecated_client_options.rb +139 -0
  14. data/lib/net/imap/response_data.rb +46 -41
  15. data/lib/net/imap/response_parser/parser_utils.rb +230 -0
  16. data/lib/net/imap/response_parser.rb +665 -627
  17. data/lib/net/imap/sasl/anonymous_authenticator.rb +68 -0
  18. data/lib/net/imap/sasl/authentication_exchange.rb +107 -0
  19. data/lib/net/imap/sasl/authenticators.rb +118 -0
  20. data/lib/net/imap/sasl/client_adapter.rb +72 -0
  21. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +15 -9
  22. data/lib/net/imap/sasl/digest_md5_authenticator.rb +168 -0
  23. data/lib/net/imap/sasl/external_authenticator.rb +62 -0
  24. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  25. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +19 -14
  26. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +164 -0
  27. data/lib/net/imap/sasl/plain_authenticator.rb +93 -0
  28. data/lib/net/imap/sasl/protocol_adapters.rb +45 -0
  29. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  30. data/lib/net/imap/sasl/scram_authenticator.rb +278 -0
  31. data/lib/net/imap/sasl/stringprep.rb +6 -66
  32. data/lib/net/imap/sasl/xoauth2_authenticator.rb +88 -0
  33. data/lib/net/imap/sasl.rb +144 -43
  34. data/lib/net/imap/sasl_adapter.rb +21 -0
  35. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  36. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  37. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  38. data/lib/net/imap/stringprep/tables.rb +146 -0
  39. data/lib/net/imap/stringprep/trace.rb +85 -0
  40. data/lib/net/imap/stringprep.rb +159 -0
  41. data/lib/net/imap.rb +976 -590
  42. data/net-imap.gemspec +2 -2
  43. data/rakelib/saslprep.rake +4 -4
  44. data/rakelib/string_prep_tables_generator.rb +82 -60
  45. metadata +31 -12
  46. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  47. data/lib/net/imap/authenticators/plain.rb +0 -41
  48. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  49. data/lib/net/imap/sasl/saslprep.rb +0 -55
  50. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  51. 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+, and +UNSELECT+.
408
+ # Commands for these extensions are listed with the {Core IMAP
409
+ # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above.
410
+ #
411
+ # >>>
412
+ # <em>The following are folded into +IMAP4rev2+ but are currently
413
+ # unsupported or incompletely supported by</em> Net::IMAP<em>: RFC4466
414
+ # extensions, +ESEARCH+, +SEARCHRES+, +LIST-EXTENDED+,
415
+ # +LIST-STATUS+, +LITERAL-+, +BINARY+ fetch, and +SPECIAL-USE+. The
416
+ # following extensions are implicitly supported, but will be updated with
417
+ # more direct support: RFC5530 response codes, <tt>STATUS=SIZE</tt>, and
418
+ # <tt>STATUS=DELETED</tt>.</em>
353
419
  #
354
420
  # ==== RFC2087: +QUOTA+
355
421
  # - #getquota: returns the resource usage and limits for a quota root
@@ -358,92 +424,47 @@ 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
- # ==== RFC3516: +BINARY+
381
- # TODO...
382
- #++
383
- #
384
440
  # ==== 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].
441
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
442
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
387
443
  # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
388
444
  # without expunging any messages.
389
445
  #
390
446
  # ==== RFC4314: +ACL+
391
447
  # - #getacl: lists the authenticated user's access rights to a mailbox.
392
448
  # - #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.
449
+ # >>>
450
+ # *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
397
451
  #
398
452
  # ==== 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].
453
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
454
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
401
455
  # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
402
456
  # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode
403
457
  # - Updates #append with the +APPENDUID+ ResponseCode
404
458
  # - Updates #copy, #move with the +COPYUID+ ResponseCode
405
459
  #
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
460
  # ==== RFC4959: +SASL-IR+
428
- # TODO...
429
461
  # 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
- #++
462
+ # - Updates #authenticate with the option to send an initial response.
437
463
  #
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
- #++
464
+ # ==== RFC5161: +ENABLE+
465
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
466
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
467
+ # - #enable: Enables backwards incompatible server extensions.
447
468
  #
448
469
  # ==== RFC5256: +SORT+
449
470
  # - #sort, #uid_sort: An alternate version of #search or #uid_search which
@@ -453,77 +474,20 @@ module Net
453
474
  # which arranges the results into ordered groups or threads according to a
454
475
  # chosen algorithm.
455
476
  #
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
477
  # ==== +XLIST+ (non-standard, deprecated)
475
478
  # - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses.
476
479
  #
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
- #++
483
- #
484
480
  # ==== 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].
481
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
482
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
487
483
  # - #move, #uid_move: Moves the specified messages to the end of the
488
484
  # specified destination mailbox, expunging them from the current mailbox.
489
485
  #
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
- #++
509
- #
510
- # === Handling server responses
511
- #
512
- # - #greeting: The server's initial untagged response, which can indicate a
513
- # pre-authenticated connection.
514
- # - #responses: The untagged responses, as a hash. Keys are the untagged
515
- # response type (e.g. "OK", "FETCH", "FLAGS") and response code (e.g.
516
- # "ALERT", "UIDVALIDITY", "UIDNEXT", "TRYCREATE", etc). Values are arrays
517
- # of UntaggedResponse or ResponseCode.
518
- # - #add_response_handler: Add a block to be called inside the receiver thread
519
- # with every server response.
520
- # - #remove_response_handler: Remove a previously added response handler.
486
+ # ==== RFC6855: <tt>UTF8=ACCEPT</tt>, <tt>UTF8=ONLY</tt>
521
487
  #
488
+ # - See #enable for information about support for UTF-8 string encoding.
522
489
  #
523
490
  # == References
524
- #--
525
- # TODO: Consider moving references list to REFERENCES.md or REFERENCES.rdoc.
526
- #++
527
491
  #
528
492
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
529
493
  # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
@@ -624,27 +588,21 @@ module Net
624
588
  # RFC 1864, DOI 10.17487/RFC1864, October 1995,
625
589
  # <https://www.rfc-editor.org/info/rfc1864>.
626
590
  #
627
- #--
628
- # TODO: Document IMAP keywords.
629
- #
630
- # [RFC3503[https://tools.ietf.org/html/rfc3503]]
631
- # Melnikov, A., "Message Disposition Notification (MDN)
632
- # profile for Internet Message Access Protocol (IMAP)",
633
- # RFC 3503, DOI 10.17487/RFC3503, March 2003,
634
- # <https://www.rfc-editor.org/info/rfc3503>.
635
- #++
591
+ # [RFC3503[https://tools.ietf.org/html/rfc3503]]::
592
+ # Melnikov, A., "Message Disposition Notification (MDN)
593
+ # profile for Internet Message Access Protocol (IMAP)",
594
+ # RFC 3503, DOI 10.17487/RFC3503, March 2003,
595
+ # <https://www.rfc-editor.org/info/rfc3503>.
636
596
  #
637
- # === Supported \IMAP Extensions
597
+ # === \IMAP Extensions
638
598
  #
639
- # [QUOTA[https://tools.ietf.org/html/rfc2087]]::
640
- # Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
641
- # January 1997, <https://www.rfc-editor.org/info/rfc2087>.
642
- #--
643
- # TODO: test compatibility with updated QUOTA extension:
644
599
  # [QUOTA[https://tools.ietf.org/html/rfc9208]]::
645
600
  # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
646
601
  # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
647
- #++
602
+ #
603
+ # <em>Note: obsoletes</em>
604
+ # RFC-2087[https://tools.ietf.org/html/rfc2087]<em> (January 1997)</em>.
605
+ # <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
648
606
  # [IDLE[https://tools.ietf.org/html/rfc2177]]::
649
607
  # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
650
608
  # June 1997, <https://www.rfc-editor.org/info/rfc2177>.
@@ -677,31 +635,44 @@ module Net
677
635
  # Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
678
636
  # (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
679
637
  # <https://www.rfc-editor.org/info/rfc6851>.
638
+ # [UTF8=ACCEPT[https://tools.ietf.org/html/rfc6855]]::
639
+ # [UTF8=ONLY[https://tools.ietf.org/html/rfc6855]]::
640
+ # Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed.,
641
+ # "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013,
642
+ # <https://www.rfc-editor.org/info/rfc6855>.
680
643
  #
681
644
  # === IANA registries
682
- #
683
645
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
684
646
  # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
685
647
  # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
686
648
  # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
687
649
  # * {IMAP Threading Algorithms}[https://www.iana.org/assignments/imap-threading-algorithms/imap-threading-algorithms.xhtml]
688
- #--
689
- # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
690
- # * [{LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
691
- # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
692
- # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
693
- # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
694
- # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
695
- #++
696
650
  # * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
697
651
  # * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]:
698
652
  # +imap+: tcp/143, +imaps+: tcp/993
699
653
  # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
700
654
  # +imap+
701
655
  # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
656
+ # ===== For currently unsupported features:
657
+ # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
658
+ # * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
659
+ # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
660
+ # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
661
+ # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
662
+ # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
702
663
  #
703
664
  class IMAP < Protocol
704
- VERSION = "0.3.4"
665
+ VERSION = "0.4.1"
666
+
667
+ # Aliases for supported capabilities, to be used with the #enable command.
668
+ ENABLE_ALIASES = {
669
+ utf8: "UTF8=ACCEPT",
670
+ "UTF8=ONLY" => "UTF8=ACCEPT",
671
+ }.freeze
672
+
673
+ autoload :SASL, File.expand_path("imap/sasl", __dir__)
674
+ autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
675
+ autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
705
676
 
706
677
  include MonitorMixin
707
678
  if defined?(OpenSSL::SSL)
@@ -709,33 +680,6 @@ module Net
709
680
  include SSL
710
681
  end
711
682
 
712
- # Returns the initial greeting the server, an UntaggedResponse.
713
- attr_reader :greeting
714
-
715
- # Returns recorded untagged responses.
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
683
  # Returns the debug mode.
740
684
  def self.debug
741
685
  return @@debug
@@ -762,9 +706,175 @@ module Net
762
706
  alias default_ssl_port default_tls_port
763
707
  end
764
708
 
709
+ # Returns the initial greeting the server, an UntaggedResponse.
710
+ attr_reader :greeting
711
+
712
+ # Seconds to wait until a connection is opened.
713
+ # If the IMAP object cannot open a connection within this time,
714
+ # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
715
+ attr_reader :open_timeout
716
+
717
+ # Seconds to wait until an IDLE response is received.
718
+ attr_reader :idle_response_timeout
719
+
720
+ # The hostname this client connected to
721
+ attr_reader :host
722
+
723
+ # The port this client connected to
724
+ attr_reader :port
725
+
726
+ # Returns the
727
+ # {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]
728
+ # used by the SSLSocket when TLS is attempted, even when the TLS handshake
729
+ # is unsuccessful. The context object will be frozen.
730
+ #
731
+ # Returns +nil+ for a plaintext connection.
732
+ attr_reader :ssl_ctx
733
+
734
+ # Returns the parameters that were sent to #ssl_ctx
735
+ # {set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]
736
+ # when the connection tries to use TLS (even when unsuccessful).
737
+ #
738
+ # Returns +false+ for a plaintext connection.
739
+ attr_reader :ssl_ctx_params
740
+
741
+ # Creates a new Net::IMAP object and connects it to the specified
742
+ # +host+.
743
+ #
744
+ # ==== Options
745
+ #
746
+ # Accepts the following options:
747
+ #
748
+ # [port]
749
+ # Port number. Defaults to 993 when +ssl+ is truthy, and 143 otherwise.
750
+ #
751
+ # [ssl]
752
+ # If +true+, the connection will use TLS with the default params set by
753
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params].
754
+ # If +ssl+ is a hash, it's passed to
755
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
756
+ # the keys are names of attribute assignment methods on
757
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
758
+ #
759
+ # [open_timeout]
760
+ # Seconds to wait until a connection is opened
761
+ # [idle_response_timeout]
762
+ # Seconds to wait until an IDLE response is received
763
+ #
764
+ # See DeprecatedClientOptions.new for deprecated arguments.
765
+ #
766
+ # ==== Examples
767
+ #
768
+ # Connect to cleartext port 143 at mail.example.com and recieve the server greeting:
769
+ # imap = Net::IMAP.new('mail.example.com', ssl: false) # => #<Net::IMAP:0x00007f79b0872bd0>
770
+ # imap.port => 143
771
+ # imap.tls_verified? => false
772
+ # imap.greeting => name: ("OK" | "PREAUTH") => status
773
+ # status # => "OK"
774
+ # # The client is connected in the "Not Authenticated" state.
775
+ #
776
+ # Connect with TLS to port 993
777
+ # imap = Net::IMAP.new('mail.example.com', ssl: true) # => #<Net::IMAP:0x00007f79b0872bd0>
778
+ # imap.port => 993
779
+ # imap.tls_verified? => true
780
+ # imap.greeting => name: (/OK/i | /PREAUTH/i) => status
781
+ # case status
782
+ # in /OK/i
783
+ # # The client is connected in the "Not Authenticated" state.
784
+ # imap.authenticate("PLAIN", "joe_user", "joes_password")
785
+ # in /PREAUTH/i
786
+ # # The client is connected in the "Authenticated" state.
787
+ # end
788
+ #
789
+ # Connect with prior authentication, for example using an SSL certificate:
790
+ # ssl_ctx_params = {
791
+ # cert: OpenSSL::X509::Certificate.new(File.read("client.crt")),
792
+ # key: OpenSSL::PKey::EC.new(File.read('client.key')),
793
+ # extra_chain_cert: [
794
+ # OpenSSL::X509::Certificate.new(File.read("intermediate.crt")),
795
+ # ],
796
+ # }
797
+ # imap = Net::IMAP.new('mail.example.com', ssl: ssl_ctx_params)
798
+ # imap.port => 993
799
+ # imap.tls_verified? => true
800
+ # imap.greeting => name: "PREAUTH"
801
+ # # The client is connected in the "Authenticated" state.
802
+ #
803
+ # ==== Exceptions
804
+ #
805
+ # The most common errors are:
806
+ #
807
+ # [Errno::ECONNREFUSED]
808
+ # Connection refused by +host+ or an intervening firewall.
809
+ # [Errno::ETIMEDOUT]
810
+ # Connection timed out (possibly due to packets being dropped by an
811
+ # intervening firewall).
812
+ # [Errno::ENETUNREACH]
813
+ # There is no route to that network.
814
+ # [SocketError]
815
+ # Hostname not known or other socket error.
816
+ # [Net::IMAP::ByeResponseError]
817
+ # Connected to the host successfully, but it immediately said goodbye.
818
+ #
819
+ def initialize(host, port: nil, ssl: nil,
820
+ open_timeout: 30, idle_response_timeout: 5)
821
+ super()
822
+ # Config options
823
+ @host = host
824
+ @port = port || (ssl ? SSL_PORT : PORT)
825
+ @open_timeout = Integer(open_timeout)
826
+ @idle_response_timeout = Integer(idle_response_timeout)
827
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
828
+
829
+ # Basic Client State
830
+ @utf8_strings = false
831
+ @debug_output_bol = true
832
+ @exception = nil
833
+ @greeting = nil
834
+ @capabilities = nil
835
+
836
+ # Client Protocol Reciever
837
+ @parser = ResponseParser.new
838
+ @responses = Hash.new {|h, k| h[k] = [] }
839
+ @response_handlers = []
840
+ @receiver_thread = nil
841
+ @receiver_thread_exception = nil
842
+ @receiver_thread_terminating = false
843
+
844
+ # Client Protocol Sender (including state for currently running commands)
845
+ @tag_prefix = "RUBY"
846
+ @tagno = 0
847
+ @tagged_responses = {}
848
+ @tagged_response_arrival = new_cond
849
+ @continued_command_tag = nil
850
+ @continuation_request_arrival = new_cond
851
+ @continuation_request_exception = nil
852
+ @idle_done_cond = nil
853
+ @logout_command_tag = nil
854
+
855
+ # Connection
856
+ @tls_verified = false
857
+ @sock = tcp_socket(@host, @port)
858
+ start_tls_session if ssl_ctx
859
+ start_imap_connection
860
+
861
+ # DEPRECATED: to remove in next version
862
+ @client_thread = Thread.current
863
+ end
864
+
865
+ # Returns true after the TLS negotiation has completed and the remote
866
+ # hostname has been verified. Returns false when TLS has been established
867
+ # but peer verification was disabled.
868
+ def tls_verified?; @tls_verified end
869
+
870
+ def client_thread # :nodoc:
871
+ warn "Net::IMAP#client_thread is deprecated and will be removed soon."
872
+ @client_thread
873
+ end
874
+
765
875
  # Disconnects from the server.
766
876
  #
767
- # Related: #logout
877
+ # Related: #logout, #logout!
768
878
  def disconnect
769
879
  return if disconnected?
770
880
  begin
@@ -794,62 +904,123 @@ module Net
794
904
  return @sock.closed?
795
905
  end
796
906
 
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.
907
+ # Returns whether the server supports a given +capability+. When available,
908
+ # cached #capabilities are used without sending a new #capability command to
909
+ # the server.
800
910
  #
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.
911
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
912
+ # behaviour according to the server's advertised #capabilities.</em>
804
913
  #
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>
914
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
811
915
  #
812
- # Capability requirements—other than +IMAP4rev1+—are listed in the
813
- # documentation for each command method.
916
+ # Related: #auth_capable?, #capabilities, #capability, #enable
917
+ def capable?(capability) capabilities.include? capability.to_s.upcase end
918
+ alias capability? capable?
919
+
920
+ # Returns the server capabilities. When available, cached capabilities are
921
+ # used without sending a new #capability command to the server.
814
922
  #
815
- # ===== Basic IMAP4rev1 capabilities
923
+ # To ensure a case-insensitive comparison, #capable? can be used instead.
816
924
  #
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.
925
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
926
+ # behaviour according to the server's advertised #capabilities.</em>
927
+ #
928
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
929
+ #
930
+ # Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable
931
+ def capabilities
932
+ @capabilities || capability
933
+ end
934
+
935
+ # Returns the #authenticate mechanisms that the server claims to support.
936
+ # These are derived from the #capabilities with an <tt>AUTH=</tt> prefix.
822
937
  #
823
- # ===== Using IMAP4rev1 extensions
938
+ # This may be different when the connection is cleartext or using TLS. Most
939
+ # servers will drop all <tt>AUTH=</tt> mechanisms from #capabilities after
940
+ # the connection has authenticated.
824
941
  #
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.
942
+ # imap = Net::IMAP.new(hostname, ssl: false)
943
+ # imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"]
944
+ # imap.auth_mechanisms # => []
830
945
  #
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.
946
+ # imap.starttls
947
+ # imap.capabilities # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2",
948
+ # # "AUTH=OAUTHBEARER"]
949
+ # imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"]
836
950
  #
837
- # ===== Caching +CAPABILITY+ responses
951
+ # imap.authenticate("XOAUTH2", username, oauth2_access_token)
952
+ # imap.auth_mechanisms # => []
953
+ #
954
+ # Related: #authenticate, #auth_capable?, #capabilities
955
+ def auth_mechanisms
956
+ capabilities
957
+ .grep(/\AAUTH=/i)
958
+ .map { _1.delete_prefix("AUTH=") }
959
+ end
960
+
961
+ # Returns whether the server supports a given SASL +mechanism+ for use with
962
+ # the #authenticate command. The +mechanism+ is supported when
963
+ # #capabilities includes <tt>"AUTH=#{mechanism.to_s.upcase}"</tt>. When
964
+ # available, cached capabilities are used without sending a new #capability
965
+ # command to the server.
966
+ #
967
+ # imap.capable? "AUTH=PLAIN" # => true
968
+ # imap.auth_capable? "PLAIN" # => true
969
+ # imap.auth_capable? "blurdybloop" # => false
970
+ #
971
+ # Related: #authenticate, #auth_mechanisms, #capable?, #capabilities
972
+ def auth_capable?(mechanism)
973
+ capable? "AUTH=#{mechanism}"
974
+ end
975
+
976
+ # Returns whether capabilities have been cached. When true, #capable? and
977
+ # #capabilities don't require sending a #capability command to the server.
978
+ #
979
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
980
+ #
981
+ # Related: #capable?, #capability, #clear_cached_capabilities
982
+ def capabilities_cached?
983
+ !!@capabilities
984
+ end
985
+
986
+ # Clears capabilities that have been remembered by the Net::IMAP client.
987
+ # This forces a #capability command to be sent the next time a #capabilities
988
+ # query method is called.
989
+ #
990
+ # Net::IMAP automatically discards its cached capabilities when they can
991
+ # change. Explicitly calling this _should_ be unnecessary for well-behaved
992
+ # servers.
993
+ #
994
+ # Related: #capable?, #capability, #capabilities_cached?
995
+ def clear_cached_capabilities
996
+ synchronize do
997
+ clear_responses("CAPABILITY")
998
+ @capabilities = nil
999
+ end
1000
+ end
1001
+
1002
+ # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
1003
+ # and returns an array of capabilities that are supported by the server.
1004
+ # The result is stored for use by #capable? and #capabilities.
838
1005
  #
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.
1006
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
1007
+ # behaviour according to the server's advertised #capabilities.</em>
843
1008
  #
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.
1009
+ # Net::IMAP automatically stores and discards capability data according to
1010
+ # the requirements and recommendations in
1011
+ # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
1012
+ # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
1013
+ # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
1014
+ # Use #capable?, #auth_capable?, or #capabilities to this cache and avoid
1015
+ # sending the #capability command unnecessarily.
848
1016
  #
1017
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
1018
+ #
1019
+ # Related: #capable?, #auth_capable?, #capability, #enable
849
1020
  def capability
850
1021
  synchronize do
851
1022
  send_command("CAPABILITY")
852
- return @responses.delete("CAPABILITY")[-1]
1023
+ @capabilities = clear_responses("CAPABILITY").last.freeze
853
1024
  end
854
1025
  end
855
1026
 
@@ -860,8 +1031,7 @@ module Net
860
1031
  # Note that the user should first check if the server supports the ID
861
1032
  # capability. For example:
862
1033
  #
863
- # capabilities = imap.capability
864
- # if capabilities.include?("ID")
1034
+ # if capable?(:ID)
865
1035
  # id = imap.id(
866
1036
  # name: "my IMAP client (ruby)",
867
1037
  # version: MyIMAP::VERSION,
@@ -879,7 +1049,7 @@ module Net
879
1049
  def id(client_id=nil)
880
1050
  synchronize do
881
1051
  send_command("ID", ClientID.new(client_id))
882
- @responses.delete("ID")&.last
1052
+ clear_responses("ID").last
883
1053
  end
884
1054
  end
885
1055
 
@@ -903,15 +1073,43 @@ module Net
903
1073
  # to inform the command to inform the server that the client is done with
904
1074
  # the connection.
905
1075
  #
906
- # Related: #disconnect
1076
+ # Related: #disconnect, #logout!
907
1077
  def logout
908
1078
  send_command("LOGOUT")
909
1079
  end
910
1080
 
1081
+ # Calls #logout then, after receiving the TaggedResponse for the +LOGOUT+,
1082
+ # calls #disconnect. Returns the TaggedResponse from +LOGOUT+. Returns
1083
+ # +nil+ when the client is already disconnected, in contrast to #logout
1084
+ # which raises an exception.
1085
+ #
1086
+ # If #logout raises a StandardError, a warning will be printed but the
1087
+ # exception will not be re-raised.
1088
+ #
1089
+ # This is useful in situations where the connection must be dropped, for
1090
+ # example for security or after tests. If logout errors need to be handled,
1091
+ # use #logout and #disconnect instead.
1092
+ #
1093
+ # Related: #logout, #disconnect
1094
+ def logout!
1095
+ logout unless disconnected?
1096
+ rescue => ex
1097
+ warn "%s during <Net::IMAP %s:%s> logout!: %s" % [
1098
+ ex.class, host, port, ex
1099
+ ]
1100
+ ensure
1101
+ disconnect
1102
+ end
1103
+
911
1104
  # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1]
912
1105
  # to start a TLS session.
913
1106
  #
914
- # Any +options+ are forwarded to OpenSSL::SSL::SSLContext#set_params.
1107
+ # Any +options+ are forwarded directly to
1108
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
1109
+ # the keys are names of attribute assignment methods on
1110
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
1111
+ #
1112
+ # See DeprecatedClientOptions#starttls for deprecated arguments.
915
1113
  #
916
1114
  # This method returns after TLS negotiation and hostname verification are
917
1115
  # both successful. Any error indicates that the connection has not been
@@ -921,132 +1119,159 @@ module Net
921
1119
  # >>>
922
1120
  # Any #response_handlers added before STARTTLS should be aware that the
923
1121
  # TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation.
924
- # TLS negotiation starts immediately after that response.
1122
+ # TLS starts immediately _after_ that response. Any response code sent
1123
+ # with the response (e.g. CAPABILITY) is insecure and cannot be trusted.
925
1124
  #
926
1125
  # Related: Net::IMAP.new, #login, #authenticate
927
1126
  #
928
1127
  # ===== Capability
929
- #
930
- # The server's capabilities must include +STARTTLS+.
1128
+ # Clients should not call #starttls unless the server advertises the
1129
+ # +STARTTLS+ capability.
931
1130
  #
932
1131
  # Server capabilities may change after #starttls, #login, and #authenticate.
933
- # Cached capabilities _must_ be invalidated after this method completes.
1132
+ # Cached #capabilities will be cleared when this method completes.
934
1133
  #
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.
939
- #
940
- def starttls(options = {}, verify = true)
1134
+ def starttls(**options)
1135
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
941
1136
  send_command("STARTTLS") do |resp|
942
1137
  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)
1138
+ clear_cached_capabilities
1139
+ clear_responses
1140
+ start_tls_session
950
1141
  end
951
1142
  end
952
1143
  end
953
1144
 
954
1145
  # :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
1146
+ # authenticate(mechanism, *,
1147
+ # sasl_ir: true,
1148
+ # registry: Net::IMAP::SASL.authenticators,
1149
+ # **, &) -> ok_resp
960
1150
  #
961
1151
  # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
962
1152
  # to authenticate the client. If successful, the connection enters the
963
1153
  # "_authenticated_" state.
964
1154
  #
965
1155
  # +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
1156
  #
971
- # An exception Net::IMAP::NoResponseError is raised if authentication fails.
1157
+ # +sasl_ir+ allows or disallows sending an "initial response" (see the
1158
+ # +SASL-IR+ capability, below).
972
1159
  #
973
- # Related: #login, #starttls
1160
+ # All other arguments are forwarded to the registered SASL authenticator for
1161
+ # the requested mechanism. <em>The documentation for each individual
1162
+ # mechanism must be consulted for its specific parameters.</em>
974
1163
  #
975
- # ==== Supported SASL Mechanisms
1164
+ # Related: #login, #starttls, #auth_capable?, #auth_mechanisms
976
1165
  #
977
- # +PLAIN+:: See PlainAuthenticator.
978
- # Login using clear-text username and password.
1166
+ # ==== Mechanisms
979
1167
  #
980
- # +XOAUTH2+:: See XOauth2Authenticator.
981
- # Login using a username and OAuth2 access token.
982
- # Non-standard and obsoleted by +OAUTHBEARER+, but widely
983
- # supported.
1168
+ # Each mechanism has different properties and requirements. Please consult
1169
+ # the documentation for the specific mechanisms you are using:
984
1170
  #
985
- # >>>
986
- # *Deprecated:* <em>Obsolete mechanisms are available for backwards
987
- # compatibility.</em>
1171
+ # +ANONYMOUS+::
1172
+ # See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
1173
+ #
1174
+ # Allows the user to gain access to public services or resources without
1175
+ # authenticating or disclosing an identity.
1176
+ #
1177
+ # +EXTERNAL+::
1178
+ # See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
1179
+ #
1180
+ # Authenticates using already established credentials, such as a TLS
1181
+ # certificate or IPsec.
1182
+ #
1183
+ # +OAUTHBEARER+::
1184
+ # See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator].
1185
+ #
1186
+ # Login using an OAuth2 Bearer token. This is the standard mechanism
1187
+ # for using OAuth2 with \SASL, but it is not yet deployed as widely as
1188
+ # +XOAUTH2+.
1189
+ #
1190
+ # +PLAIN+::
1191
+ # See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator].
988
1192
  #
989
- # For +DIGEST-MD5+ see DigestMD5Authenticator.
1193
+ # Login using clear-text username and password.
990
1194
  #
991
- # For +LOGIN+, see LoginAuthenticator.
1195
+ # +SCRAM-SHA-1+::
1196
+ # +SCRAM-SHA-256+::
1197
+ # See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator].
992
1198
  #
993
- # For +CRAM-MD5+, see CramMD5Authenticator.
1199
+ # Login by username and password. The password is not sent to the
1200
+ # server but is used in a salted challenge/response exchange.
1201
+ # +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
1202
+ # Net::IMAP::SASL. New authenticators can easily be added for any other
1203
+ # <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
1204
+ # OpenSSL::Digest.
994
1205
  #
995
- # <em>Using a deprecated mechanism will print a warning.</em>
1206
+ # +XOAUTH2+::
1207
+ # See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator].
996
1208
  #
997
- # See Net::IMAP::Authenticators for information on plugging in
998
- # authenticators for other mechanisms. See the {SASL mechanism
1209
+ # Login using a username and an OAuth2 access token. Non-standard and
1210
+ # obsoleted by +OAUTHBEARER+, but widely supported.
1211
+ #
1212
+ # See the {SASL mechanism
999
1213
  # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
1000
- # for information on these and other SASL mechanisms.
1214
+ # for a list of all SASL mechanisms and their specifications. To register
1215
+ # new authenticators, see Authenticators.
1001
1216
  #
1002
- # ===== Capabilities
1217
+ # ===== Deprecated mechanisms
1003
1218
  #
1004
- # Clients MUST NOT attempt to authenticate with a mechanism unless
1005
- # <tt>"AUTH=#{mechanism}"</tt> for that mechanism is a server capability.
1219
+ # <em>Obsolete mechanisms should be avoided, but are still available for
1220
+ # backwards compatibility. See</em> Net::IMAP::SASL@Deprecated+mechanisms.
1221
+ # <em>Using a deprecated mechanism will print a warning.</em>
1006
1222
  #
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
1223
+ # ==== Capabilities
1224
+ #
1225
+ # <tt>"AUTH=#{mechanism}"</tt> capabilities indicate server support for
1226
+ # mechanisms. Use #auth_capable? or #auth_mechanisms to check for support
1227
+ # before using a particular mechanism.
1228
+ #
1229
+ # if imap.auth_capable? "XOAUTH2"
1230
+ # imap.authenticate "XOAUTH2", username, oauth2_access_token
1231
+ # elsif imap.auth_capable? "PLAIN"
1232
+ # imap.authenticate "PLAIN", username, password
1233
+ # elsif !imap.capability? "LOGINDISABLED"
1037
1234
  # imap.login username, password
1235
+ # else
1236
+ # raise "No acceptable authentication mechanism is available"
1038
1237
  # end
1039
1238
  #
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
1239
+ # Although servers should list all supported \SASL mechanisms, they may
1240
+ # allow authentication with an unlisted +mechanism+.
1241
+ #
1242
+ # If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported
1243
+ # and the appropriate <tt>"AUTH=#{mechanism}"</tt> capability is present,
1244
+ # an "initial response" may be sent as an argument to the +AUTHENTICATE+
1245
+ # command, saving a round-trip. The SASL exchange allows for server
1246
+ # challenges and client responses, but many mechanisms expect the client to
1247
+ # "respond" first. The initial response will only be sent for
1248
+ # "client-first" mechanisms.
1249
+ #
1250
+ # Server capabilities may change after #starttls, #login, and #authenticate.
1251
+ # Previously cached #capabilities will be cleared when this method
1252
+ # completes. If the TaggedResponse to #authenticate includes updated
1253
+ # capabilities, they will be cached.
1254
+ def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback)
1255
+ mechanism = mechanism.to_s.tr("_", "-").upcase
1256
+ authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
1257
+ cmdargs = ["AUTHENTICATE", mechanism]
1258
+ if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
1259
+ authenticator.respond_to?(:initial_response?) &&
1260
+ authenticator.initial_response?
1261
+ response = authenticator.process(nil)
1262
+ cmdargs << (response.empty? ? "=" : [response].pack("m0"))
1049
1263
  end
1264
+ result = send_command_with_continuations(*cmdargs) {|data|
1265
+ challenge = data.unpack1("m")
1266
+ response = authenticator.process challenge
1267
+ [response].pack("m0")
1268
+ }
1269
+ if authenticator.respond_to?(:done?) && !authenticator.done?
1270
+ logout!
1271
+ raise SASL::AuthenticationIncomplete, result
1272
+ end
1273
+ @capabilities = capabilities_from_resp_code result
1274
+ result
1050
1275
  end
1051
1276
 
1052
1277
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1054,16 +1279,25 @@ module Net
1054
1279
  # this +user+. If successful, the connection enters the "_authenticated_"
1055
1280
  # state.
1056
1281
  #
1057
- # Using #authenticate is generally preferred over #login. The LOGIN command
1058
- # is not the same as #authenticate with the "LOGIN" +mechanism+.
1282
+ # Using #authenticate {should be
1283
+ # preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command]
1284
+ # over #login. The LOGIN command is not the same as #authenticate with the
1285
+ # "LOGIN" +mechanism+.
1059
1286
  #
1060
1287
  # A Net::IMAP::NoResponseError is raised if authentication fails.
1061
1288
  #
1062
1289
  # Related: #authenticate, #starttls
1063
1290
  #
1064
- # ==== Capabilities
1065
- # Clients MUST NOT call #login if +LOGINDISABLED+ is listed with the
1066
- # capabilities.
1291
+ # ===== Capabilities
1292
+ #
1293
+ # An IMAP client MUST NOT call #login when the server advertises the
1294
+ # +LOGINDISABLED+ capability.
1295
+ #
1296
+ # if imap.capability? "LOGINDISABLED"
1297
+ # raise "Remote server has disabled the login command"
1298
+ # else
1299
+ # imap.login username, password
1300
+ # end
1067
1301
  #
1068
1302
  # Server capabilities may change after #starttls, #login, and #authenticate.
1069
1303
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1072,17 +1306,18 @@ module Net
1072
1306
  #
1073
1307
  def login(user, password)
1074
1308
  send_command("LOGIN", user, password)
1309
+ .tap { @capabilities = capabilities_from_resp_code _1 }
1075
1310
  end
1076
1311
 
1077
1312
  # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
1078
1313
  # to select a +mailbox+ so that messages in the +mailbox+ can be accessed.
1079
1314
  #
1080
1315
  # 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.
1316
+ # that mailbox from <tt>imap.responses("EXISTS", &:last)</tt>, and the
1317
+ # number of recent messages from <tt>imap.responses("RECENT", &:last)</tt>.
1318
+ # Note that these values can change if new messages arrive during a session
1319
+ # or when existing messages are expunged; see #add_response_handler for a
1320
+ # way to detect these events.
1086
1321
  #
1087
1322
  # A Net::IMAP::NoResponseError is raised if the mailbox does not
1088
1323
  # exist or is for some reason non-selectable.
@@ -1213,7 +1448,7 @@ module Net
1213
1448
  def list(refname, mailbox)
1214
1449
  synchronize do
1215
1450
  send_command("LIST", refname, mailbox)
1216
- return @responses.delete("LIST")
1451
+ clear_responses("LIST")
1217
1452
  end
1218
1453
  end
1219
1454
 
@@ -1251,8 +1486,7 @@ module Net
1251
1486
  #
1252
1487
  # ===== For example:
1253
1488
  #
1254
- # capabilities = imap.capability
1255
- # if capabilities.include?("NAMESPACE")
1489
+ # if capable?("NAMESPACE")
1256
1490
  # namespaces = imap.namespace
1257
1491
  # if namespace = namespaces.personal.first
1258
1492
  # prefix = namespace.prefix # e.g. "" or "INBOX."
@@ -1271,7 +1505,7 @@ module Net
1271
1505
  def namespace
1272
1506
  synchronize do
1273
1507
  send_command("NAMESPACE")
1274
- return @responses.delete("NAMESPACE")[-1]
1508
+ clear_responses("NAMESPACE").last
1275
1509
  end
1276
1510
  end
1277
1511
 
@@ -1315,7 +1549,7 @@ module Net
1315
1549
  def xlist(refname, mailbox)
1316
1550
  synchronize do
1317
1551
  send_command("XLIST", refname, mailbox)
1318
- return @responses.delete("XLIST")
1552
+ clear_responses("XLIST")
1319
1553
  end
1320
1554
  end
1321
1555
 
@@ -1334,8 +1568,8 @@ module Net
1334
1568
  synchronize do
1335
1569
  send_command("GETQUOTAROOT", mailbox)
1336
1570
  result = []
1337
- result.concat(@responses.delete("QUOTAROOT"))
1338
- result.concat(@responses.delete("QUOTA"))
1571
+ result.concat(clear_responses("QUOTAROOT"))
1572
+ result.concat(clear_responses("QUOTA"))
1339
1573
  return result
1340
1574
  end
1341
1575
  end
@@ -1354,7 +1588,7 @@ module Net
1354
1588
  def getquota(mailbox)
1355
1589
  synchronize do
1356
1590
  send_command("GETQUOTA", mailbox)
1357
- return @responses.delete("QUOTA")
1591
+ clear_responses("QUOTA")
1358
1592
  end
1359
1593
  end
1360
1594
 
@@ -1410,7 +1644,7 @@ module Net
1410
1644
  def getacl(mailbox)
1411
1645
  synchronize do
1412
1646
  send_command("GETACL", mailbox)
1413
- return @responses.delete("ACL")[-1]
1647
+ clear_responses("ACL").last
1414
1648
  end
1415
1649
  end
1416
1650
 
@@ -1425,7 +1659,7 @@ module Net
1425
1659
  def lsub(refname, mailbox)
1426
1660
  synchronize do
1427
1661
  send_command("LSUB", refname, mailbox)
1428
- return @responses.delete("LSUB")
1662
+ clear_responses("LSUB")
1429
1663
  end
1430
1664
  end
1431
1665
 
@@ -1449,7 +1683,7 @@ module Net
1449
1683
  def status(mailbox, attr)
1450
1684
  synchronize do
1451
1685
  send_command("STATUS", mailbox, attr)
1452
- return @responses.delete("STATUS")[-1].attr
1686
+ clear_responses("STATUS").last&.attr
1453
1687
  end
1454
1688
  end
1455
1689
 
@@ -1538,7 +1772,7 @@ module Net
1538
1772
  def expunge
1539
1773
  synchronize do
1540
1774
  send_command("EXPUNGE")
1541
- return @responses.delete("EXPUNGE")
1775
+ clear_responses("EXPUNGE")
1542
1776
  end
1543
1777
  end
1544
1778
 
@@ -1570,7 +1804,7 @@ module Net
1570
1804
  def uid_expunge(uid_set)
1571
1805
  synchronize do
1572
1806
  send_command("UID EXPUNGE", MessageSet.new(uid_set))
1573
- return @responses.delete("EXPUNGE")
1807
+ clear_responses("EXPUNGE")
1574
1808
  end
1575
1809
  end
1576
1810
 
@@ -1589,7 +1823,7 @@ module Net
1589
1823
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
1590
1824
  # in addition to documentation for
1591
1825
  # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
1592
- # reported by #capability which may define additional search filters, e.g:
1826
+ # reported by #capabilities which may define additional search filters, e.g:
1593
1827
  # +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
1594
1828
  # +SAVEDATE+. The following are some common search criteria:
1595
1829
  #
@@ -1656,8 +1890,7 @@ module Net
1656
1890
  # +attr+ is a list of attributes to fetch; see the documentation
1657
1891
  # for FetchData for a list of valid attributes.
1658
1892
  #
1659
- # The return value is an array of FetchData or nil
1660
- # (instead of an empty array) if there is no matching message.
1893
+ # The return value is an array of FetchData.
1661
1894
  #
1662
1895
  # Related: #uid_search, FetchData
1663
1896
  #
@@ -1884,6 +2117,87 @@ module Net
1884
2117
  return thread_internal("UID THREAD", algorithm, search_keys, charset)
1885
2118
  end
1886
2119
 
2120
+ # Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1]
2121
+ # {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1]
2122
+ # to enable the specified server +capabilities+. Each capability may be an
2123
+ # array, string, or symbol. Returns a list of the capabilities that were
2124
+ # enabled.
2125
+ #
2126
+ # The +ENABLE+ command is only valid in the _authenticated_ state, before
2127
+ # any mailbox is selected.
2128
+ #
2129
+ # Related: #capable?, #capabilities, #capability
2130
+ #
2131
+ # ===== Capabilities
2132
+ #
2133
+ # The server's capabilities must include
2134
+ # +ENABLE+ [RFC5161[https://tools.ietf.org/html/rfc5161]]
2135
+ # or +IMAP4REV2+ [RFC9051[https://tools.ietf.org/html/rfc9051]].
2136
+ #
2137
+ # Additionally, the server capabilities must include a capability matching
2138
+ # each enabled extension (usually the same name as the enabled extension).
2139
+ # The following capabilities may be enabled:
2140
+ #
2141
+ # [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
2142
+ #
2143
+ # In a future release, <tt>enable(:utf8)</tt> will enable either
2144
+ # <tt>"UTF8=ACCEPT"</tt> or <tt>"IMAP4rev2"</tt>, depending on server
2145
+ # capabilities.
2146
+ #
2147
+ # [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2148
+ #
2149
+ # The server's capabilities must include <tt>UTF8=ACCEPT</tt> _or_
2150
+ # <tt>UTF8=ONLY</tt>.
2151
+ #
2152
+ # This allows the server to send strings encoded as UTF-8 which might
2153
+ # otherwise need to use a 7-bit encoding, such as {modified
2154
+ # UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for
2155
+ # message headers.
2156
+ #
2157
+ # *Note:* <em>A future update may set string encodings slightly
2158
+ # differently</em>, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8"
2159
+ # when it is. Currently, the encoding of strings sent as "quoted" or
2160
+ # "text" will _always_ be "UTF-8", even when only ASCII characters are
2161
+ # used (e.g. "Subject: Agenda") And currently, string "literals" sent
2162
+ # by the server will always have an "ASCII-8BIT" (binary)
2163
+ # encoding, even if they generally contain UTF-8 data, if they are
2164
+ # text at all.
2165
+ #
2166
+ # [<tt>"UTF8=ONLY"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2167
+ #
2168
+ # A server that reports the <tt>UTF8=ONLY</tt> capability _requires_ that
2169
+ # the client <tt>enable("UTF8=ACCEPT")</tt> before any mailboxes may be
2170
+ # selected. For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
2171
+ # <tt>enable("UTF8=ACCEPT")</tt>.
2172
+ #
2173
+ # ===== Unsupported capabilities
2174
+ #
2175
+ # *Note:* Some extensions that use ENABLE permit the server to send syntax
2176
+ # that Net::IMAP cannot parse, which may raise an exception and disconnect.
2177
+ # Some extensions may work, but the support may be incomplete, untested, or
2178
+ # experimental.
2179
+ #
2180
+ # Until a capability is documented here as supported, enabling it may result
2181
+ # in undocumented behavior and a future release may update with incompatible
2182
+ # behavior <em>without warning or deprecation</em>.
2183
+ #
2184
+ # <em>Caution is advised.</em>
2185
+ #
2186
+ def enable(*capabilities)
2187
+ capabilities = capabilities
2188
+ .flatten
2189
+ .map {|e| ENABLE_ALIASES[e] || e }
2190
+ .uniq
2191
+ .join(' ')
2192
+ synchronize do
2193
+ send_command("ENABLE #{capabilities}")
2194
+ result = clear_responses("ENABLED").last
2195
+ @utf8_strings ||= result.include? "UTF8=ACCEPT"
2196
+ @utf8_strings ||= result.include? "IMAP4REV2"
2197
+ result
2198
+ end
2199
+ end
2200
+
1887
2201
  # Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3]
1888
2202
  # {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13]
1889
2203
  # that waits for notifications of new or expunged messages. Yields
@@ -1948,6 +2262,104 @@ module Net
1948
2262
  end
1949
2263
  end
1950
2264
 
2265
+ # :call-seq:
2266
+ # responses {|hash| ...} -> block result
2267
+ # responses(type) {|array| ...} -> block result
2268
+ #
2269
+ # Yields unhandled responses and returns the result of the block.
2270
+ #
2271
+ # Unhandled responses are stored in a hash, with arrays of
2272
+ # <em>non-+nil+</em> UntaggedResponse#data keyed by UntaggedResponse#name
2273
+ # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
2274
+ # yield the entire responses hash. Call with +type+ to yield only the array
2275
+ # of responses for that type.
2276
+ #
2277
+ # For example:
2278
+ #
2279
+ # imap.select("inbox")
2280
+ # p imap.responses("EXISTS", &:last)
2281
+ # #=> 2
2282
+ # p imap.responses("UIDVALIDITY", &:last)
2283
+ # #=> 968263756
2284
+ #
2285
+ # >>>
2286
+ # *Note:* Access to the responses hash is synchronized for thread-safety.
2287
+ # The receiver thread and response_handlers cannot process new responses
2288
+ # until the block completes. Accessing either the response hash or its
2289
+ # response type arrays outside of the block is unsafe.
2290
+ #
2291
+ # Calling without a block is unsafe and deprecated. Future releases will
2292
+ # raise ArgumentError unless a block is given.
2293
+ #
2294
+ # Previously unhandled responses are automatically cleared before entering a
2295
+ # mailbox with #select or #examine. Long-lived connections can receive many
2296
+ # unhandled server responses, which must be pruned or they will continually
2297
+ # consume more memory. Update or clear the responses hash or arrays inside
2298
+ # the block, or use #clear_responses.
2299
+ #
2300
+ # Only non-+nil+ data is stored. Many important response codes have no data
2301
+ # of their own, but are used as "tags" on the ResponseText object they are
2302
+ # attached to. ResponseText will be accessible by its response types:
2303
+ # "+OK+", "+NO+", "+BAD+", "+BYE+", or "+PREAUTH+".
2304
+ #
2305
+ # TaggedResponse#data is not saved to #responses, nor is any
2306
+ # ResponseCode#data on tagged responses. Although some command methods do
2307
+ # return the TaggedResponse directly, #add_response_handler must be used to
2308
+ # handle all response codes.
2309
+ #
2310
+ # Related: #clear_responses, #response_handlers, #greeting
2311
+ def responses(type = nil)
2312
+ if block_given?
2313
+ synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
2314
+ elsif type
2315
+ raise ArgumentError, "Pass a block or use #clear_responses"
2316
+ else
2317
+ # warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1)
2318
+ @responses
2319
+ end
2320
+ end
2321
+
2322
+ # :call-seq:
2323
+ # clear_responses -> hash
2324
+ # clear_responses(type) -> array
2325
+ #
2326
+ # Clears and returns the unhandled #responses hash or the unhandled
2327
+ # responses array for a single response +type+.
2328
+ #
2329
+ # Clearing responses is synchronized with other threads. The lock is
2330
+ # released before returning.
2331
+ #
2332
+ # Related: #responses, #response_handlers
2333
+ def clear_responses(type = nil)
2334
+ synchronize {
2335
+ if type
2336
+ @responses.delete(type) || []
2337
+ else
2338
+ @responses.dup.transform_values(&:freeze)
2339
+ .tap { _1.default = [].freeze }
2340
+ .tap { @responses.clear }
2341
+ end
2342
+ }
2343
+ .freeze
2344
+ end
2345
+
2346
+ # Returns all response handlers, including those that are added internally
2347
+ # by commands. Each response handler will be called with every new
2348
+ # UntaggedResponse, TaggedResponse, and ContinuationRequest.
2349
+ #
2350
+ # Response handlers are called with a mutex inside the receiver thread. New
2351
+ # responses cannot be processed and commands from other threads must wait
2352
+ # until all response_handlers return. An exception will shut-down the
2353
+ # receiver thread and close the connection.
2354
+ #
2355
+ # For thread-safety, the returned array is a frozen copy of the internal
2356
+ # array.
2357
+ #
2358
+ # Related: #add_response_handler, #remove_response_handler
2359
+ def response_handlers
2360
+ synchronize { @response_handlers.clone.freeze }
2361
+ end
2362
+
1951
2363
  # Adds a response handler. For example, to detect when
1952
2364
  # the server sends a new EXISTS response (which normally
1953
2365
  # indicates new messages being added to the mailbox),
@@ -1960,14 +2372,21 @@ module Net
1960
2372
  # end
1961
2373
  # }
1962
2374
  #
2375
+ # Related: #remove_response_handler, #response_handlers
1963
2376
  def add_response_handler(handler = nil, &block)
1964
2377
  raise ArgumentError, "two Procs are passed" if handler && block
1965
- @response_handlers.push(block || handler)
2378
+ synchronize do
2379
+ @response_handlers.push(block || handler)
2380
+ end
1966
2381
  end
1967
2382
 
1968
2383
  # Removes the response handler.
2384
+ #
2385
+ # Related: #add_response_handler, #response_handlers
1969
2386
  def remove_response_handler(handler)
1970
- @response_handlers.delete(handler)
2387
+ synchronize do
2388
+ @response_handlers.delete(handler)
2389
+ end
1971
2390
  end
1972
2391
 
1973
2392
  private
@@ -1978,93 +2397,29 @@ module Net
1978
2397
 
1979
2398
  @@debug = false
1980
2399
 
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
2400
+ def start_imap_connection
2401
+ @greeting = get_server_greeting
2402
+ @capabilities = capabilities_from_resp_code @greeting
2403
+ @receiver_thread = start_receiver_thread
2404
+ rescue Exception
2405
+ @sock.close
2406
+ raise
2407
+ end
2408
+
2409
+ def get_server_greeting
2410
+ greeting = get_response
2411
+ raise Error, "No server greeting - connection closed" unless greeting
2412
+ record_untagged_response_code greeting
2413
+ raise ByeResponseError, greeting if greeting.name == "BYE"
2414
+ greeting
2415
+ end
2416
+
2417
+ def start_receiver_thread
2418
+ Thread.start do
2419
+ receive_responses
2420
+ rescue Exception => ex
2421
+ @receiver_thread_exception = ex
2422
+ # don't exit the thread with an exception
2068
2423
  end
2069
2424
  end
2070
2425
 
@@ -2113,11 +2468,7 @@ module Net
2113
2468
  @continuation_request_arrival.signal
2114
2469
  end
2115
2470
  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
2471
+ record_untagged_response(resp)
2121
2472
  if resp.name == "BYE" && @logout_command_tag.nil?
2122
2473
  @sock.close
2123
2474
  @exception = ByeResponseError.new(resp)
@@ -2195,11 +2546,42 @@ module Net
2195
2546
  return @parser.parse(buff)
2196
2547
  end
2197
2548
 
2198
- def record_response(name, data)
2199
- unless @responses.has_key?(name)
2200
- @responses[name] = []
2549
+ #############################
2550
+ # built-in response handlers
2551
+
2552
+ # store name => [..., data]
2553
+ def record_untagged_response(resp)
2554
+ @responses[resp.name] << resp.data
2555
+ record_untagged_response_code resp
2556
+ end
2557
+
2558
+ # store code.name => [..., code.data]
2559
+ def record_untagged_response_code(resp)
2560
+ return unless resp.data.is_a?(ResponseText)
2561
+ return unless (code = resp.data.code)
2562
+ @responses[code.name] << code.data
2563
+ end
2564
+
2565
+ # NOTE: only call this for greeting, login, and authenticate
2566
+ def capabilities_from_resp_code(resp)
2567
+ return unless %w[PREAUTH OK].any? { _1.casecmp? resp.name }
2568
+ return unless (code = resp.data.code)
2569
+ return unless code.name.casecmp?("CAPABILITY")
2570
+ code.data.freeze
2571
+ end
2572
+
2573
+ #############################
2574
+
2575
+ # Calls send_command, yielding the text of each ContinuationRequest and
2576
+ # responding with each block result. Returns TaggedResponse. Raises
2577
+ # NoResponseError or BadResponseError.
2578
+ def send_command_with_continuations(cmd, *args)
2579
+ send_command(cmd, *args) do |server_response|
2580
+ if server_response.instance_of?(ContinuationRequest)
2581
+ client_response = yield server_response.data.text
2582
+ put_string(client_response + CRLF)
2583
+ end
2201
2584
  end
2202
- @responses[name].push(data)
2203
2585
  end
2204
2586
 
2205
2587
  def send_command(cmd, *args, &block)
@@ -2241,8 +2623,8 @@ module Net
2241
2623
  if @debug_output_bol
2242
2624
  $stderr.print("C: ")
2243
2625
  end
2244
- $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
2245
- if /\r\n\z/n.match(str)
2626
+ $stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " })
2627
+ if /\n\z/n.match(str)
2246
2628
  @debug_output_bol = true
2247
2629
  else
2248
2630
  @debug_output_bol = false
@@ -2262,7 +2644,7 @@ module Net
2262
2644
  else
2263
2645
  send_command(cmd, *keys)
2264
2646
  end
2265
- return @responses.delete("SEARCH")[-1]
2647
+ clear_responses("SEARCH").last
2266
2648
  end
2267
2649
  end
2268
2650
 
@@ -2277,13 +2659,13 @@ module Net
2277
2659
  end
2278
2660
 
2279
2661
  synchronize do
2280
- @responses.delete("FETCH")
2662
+ clear_responses("FETCH")
2281
2663
  if mod
2282
2664
  send_command(cmd, MessageSet.new(set), attr, mod)
2283
2665
  else
2284
2666
  send_command(cmd, MessageSet.new(set), attr)
2285
2667
  end
2286
- return @responses.delete("FETCH")
2668
+ clear_responses("FETCH")
2287
2669
  end
2288
2670
  end
2289
2671
 
@@ -2292,9 +2674,9 @@ module Net
2292
2674
  attr = RawData.new(attr)
2293
2675
  end
2294
2676
  synchronize do
2295
- @responses.delete("FETCH")
2677
+ clear_responses("FETCH")
2296
2678
  send_command(cmd, MessageSet.new(set), attr, flags)
2297
- return @responses.delete("FETCH")
2679
+ clear_responses("FETCH")
2298
2680
  end
2299
2681
  end
2300
2682
 
@@ -2311,7 +2693,7 @@ module Net
2311
2693
  normalize_searching_criteria(search_keys)
2312
2694
  synchronize do
2313
2695
  send_command(cmd, sort_keys, charset, *search_keys)
2314
- return @responses.delete("SORT")[-1]
2696
+ clear_responses("SORT").last
2315
2697
  end
2316
2698
  end
2317
2699
 
@@ -2322,8 +2704,10 @@ module Net
2322
2704
  normalize_searching_criteria(search_keys)
2323
2705
  end
2324
2706
  normalize_searching_criteria(search_keys)
2325
- send_command(cmd, algorithm, charset, *search_keys)
2326
- return @responses.delete("THREAD")[-1]
2707
+ synchronize do
2708
+ send_command(cmd, algorithm, charset, *search_keys)
2709
+ clear_responses("THREAD").last
2710
+ end
2327
2711
  end
2328
2712
 
2329
2713
  def normalize_searching_criteria(keys)
@@ -2337,49 +2721,49 @@ module Net
2337
2721
  end
2338
2722
  end
2339
2723
 
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
2724
+ def build_ssl_ctx(ssl)
2725
+ if ssl
2726
+ params = (Hash.try_convert(ssl) || {}).freeze
2727
+ context = SSLContext.new
2728
+ context.set_params(params)
2729
+ if defined?(VerifyCallbackProc)
2730
+ context.verify_callback = VerifyCallbackProc
2347
2731
  end
2348
- end
2349
- if verify
2350
- params[:verify_mode] = VERIFY_PEER
2732
+ context.freeze
2733
+ [params, context]
2351
2734
  else
2352
- params[:verify_mode] = VERIFY_NONE
2735
+ false
2353
2736
  end
2354
- return params
2355
2737
  end
2356
2738
 
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)
2739
+ def start_tls_session
2740
+ raise "SSL extension not installed" unless defined?(OpenSSL::SSL)
2741
+ raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
2742
+ raise "cannot start TLS without SSLContext" unless ssl_ctx
2743
+ @sock = SSLSocket.new(@sock, ssl_ctx)
2375
2744
  @sock.sync_close = true
2376
2745
  @sock.hostname = @host if @sock.respond_to? :hostname=
2377
2746
  ssl_socket_connect(@sock, @open_timeout)
2378
- if context.verify_mode != VERIFY_NONE
2747
+ if ssl_ctx.verify_mode != VERIFY_NONE
2379
2748
  @sock.post_connection_check(@host)
2749
+ @tls_verified = true
2380
2750
  end
2381
2751
  end
2382
2752
 
2753
+ def sasl_adapter
2754
+ SASLAdapter.new(self, &method(:send_command_with_continuations))
2755
+ end
2756
+
2757
+ #--
2758
+ # We could get the saslprep method by extending the SASLprep module
2759
+ # directly. It's done indirectly, so SASLprep can be lazily autoloaded,
2760
+ # because most users won't need it.
2761
+ #++
2762
+ # Delegates to Net::IMAP::StringPrep::SASLprep#saslprep.
2763
+ def self.saslprep(string, **opts)
2764
+ Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
2765
+ end
2766
+
2383
2767
  end
2384
2768
  end
2385
2769
 
@@ -2390,4 +2774,6 @@ require_relative "imap/flags"
2390
2774
  require_relative "imap/response_data"
2391
2775
  require_relative "imap/response_parser"
2392
2776
  require_relative "imap/authenticators"
2393
- require_relative "imap/sasl"
2777
+
2778
+ require_relative "imap/deprecated_client_options"
2779
+ Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions