net-imap 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of net-imap might be problematic. Click here for more details.

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