net-imap 0.3.7 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) 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 +665 -627
  16. data/lib/net/imap/sasl/anonymous_authenticator.rb +68 -0
  17. data/lib/net/imap/sasl/authentication_exchange.rb +107 -0
  18. data/lib/net/imap/sasl/authenticators.rb +118 -0
  19. data/lib/net/imap/sasl/client_adapter.rb +72 -0
  20. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +15 -9
  21. data/lib/net/imap/{authenticators/digest_md5.rb → sasl/digest_md5_authenticator.rb} +74 -21
  22. data/lib/net/imap/sasl/external_authenticator.rb +62 -0
  23. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  24. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +19 -14
  25. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +164 -0
  26. data/lib/net/imap/sasl/plain_authenticator.rb +93 -0
  27. data/lib/net/imap/sasl/protocol_adapters.rb +45 -0
  28. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  29. data/lib/net/imap/sasl/scram_authenticator.rb +278 -0
  30. data/lib/net/imap/sasl/stringprep.rb +6 -66
  31. data/lib/net/imap/sasl/xoauth2_authenticator.rb +88 -0
  32. data/lib/net/imap/sasl.rb +144 -43
  33. data/lib/net/imap/sasl_adapter.rb +21 -0
  34. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  35. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  36. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  37. data/lib/net/imap/stringprep/tables.rb +146 -0
  38. data/lib/net/imap/stringprep/trace.rb +85 -0
  39. data/lib/net/imap/stringprep.rb +159 -0
  40. data/lib/net/imap.rb +976 -590
  41. data/net-imap.gemspec +1 -1
  42. data/rakelib/saslprep.rake +4 -4
  43. data/rakelib/string_prep_tables_generator.rb +82 -60
  44. metadata +30 -11
  45. data/lib/net/imap/authenticators/plain.rb +0 -41
  46. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  47. data/lib/net/imap/sasl/saslprep.rb +0 -55
  48. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  49. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
data/lib/net/imap.rb CHANGED
@@ -24,11 +24,9 @@ end
24
24
  module Net
25
25
 
26
26
  # Net::IMAP implements Internet Message Access Protocol (\IMAP) client
27
- # functionality. The protocol is described in
28
- # [IMAP4rev1[https://tools.ietf.org/html/rfc3501]].
29
- #--
30
- # TODO: and [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
31
- #++
27
+ # functionality. The protocol is described
28
+ # in {IMAP4rev1 [RFC3501]}[https://tools.ietf.org/html/rfc3501]
29
+ # and {IMAP4rev2 [RFC9051]}[https://tools.ietf.org/html/rfc9051].
32
30
  #
33
31
  # == \IMAP Overview
34
32
  #
@@ -77,31 +75,22 @@ module Net
77
75
  # UIDs have to be reassigned. An \IMAP client thus cannot
78
76
  # rearrange message orders.
79
77
  #
80
- # === Server capabilities and protocol extensions
78
+ # === Examples of Usage
81
79
  #
82
- # Net::IMAP <em>does not modify its behavior</em> according to server
83
- # #capability. Users of the class must check for required capabilities before
84
- # issuing commands. Special care should be taken to follow all #capability
85
- # requirements for #starttls, #login, and #authenticate.
86
- #
87
- # See the #capability method for more information.
88
- #
89
- # == Examples of Usage
90
- #
91
- # === List sender and subject of all recent messages in the default mailbox
80
+ # ==== List sender and subject of all recent messages in the default mailbox
92
81
  #
93
82
  # imap = Net::IMAP.new('mail.example.com')
94
- # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
83
+ # imap.authenticate('PLAIN', 'joe_user', 'joes_password')
95
84
  # imap.examine('INBOX')
96
85
  # imap.search(["RECENT"]).each do |message_id|
97
86
  # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
98
87
  # puts "#{envelope.from[0].name}: \t#{envelope.subject}"
99
88
  # end
100
89
  #
101
- # === Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
90
+ # ==== Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
102
91
  #
103
92
  # imap = Net::IMAP.new('mail.example.com')
104
- # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
93
+ # imap.authenticate('PLAIN', 'joe_user', 'joes_password')
105
94
  # imap.select('Mail/sent-mail')
106
95
  # if not imap.list('Mail/', 'sent-apr03')
107
96
  # imap.create('Mail/sent-apr03')
@@ -112,12 +101,96 @@ module Net
112
101
  # end
113
102
  # imap.expunge
114
103
  #
104
+ # == Capabilities
105
+ #
106
+ # Most Net::IMAP methods do not _currently_ modify their behaviour according
107
+ # to the server's advertised #capabilities. Users of this class must check
108
+ # that the server is capable of extension commands or command arguments before
109
+ # sending them. Special care should be taken to follow the #capabilities
110
+ # requirements for #starttls, #login, and #authenticate.
111
+ #
112
+ # See #capable?, #auth_capable?, #capabilities, #auth_mechanisms to discover
113
+ # server capabilities. For relevant capability requirements, see the
114
+ # documentation on each \IMAP command.
115
+ #
116
+ # imap = Net::IMAP.new("mail.example.com")
117
+ # imap.capable?(:IMAP4rev1) or raise "Not an IMAP4rev1 server"
118
+ # imap.capable?(:starttls) or raise "Cannot start TLS"
119
+ # imap.starttls
120
+ #
121
+ # if imap.auth_capable?("PLAIN")
122
+ # imap.authenticate "PLAIN", username, password
123
+ # elsif !imap.capability?("LOGINDISABLED")
124
+ # imap.login username, password
125
+ # else
126
+ # raise "No acceptable authentication mechanisms"
127
+ # end
128
+ #
129
+ # # Support for "UTF8=ACCEPT" implies support for "ENABLE"
130
+ # imap.enable :utf8 if imap.capable?("UTF8=ACCEPT")
131
+ #
132
+ # namespaces = imap.namespace if imap.capable?(:namespace)
133
+ # mbox_prefix = namespaces&.personal&.first&.prefix || ""
134
+ # mbox_delim = namespaces&.personal&.first&.delim || "/"
135
+ # mbox_path = prefix + %w[path to my mailbox].join(delim)
136
+ # imap.create mbox_path
137
+ #
138
+ # === Basic IMAP4rev1 capabilities
139
+ #
140
+ # IMAP4rev1 servers must advertise +IMAP4rev1+ in their capabilities list.
141
+ # IMAP4rev1 servers must _implement_ the +STARTTLS+, <tt>AUTH=PLAIN</tt>,
142
+ # and +LOGINDISABLED+ capabilities. See #starttls, #login, and #authenticate
143
+ # for the implications of these capabilities.
144
+ #
145
+ # === Caching +CAPABILITY+ responses
146
+ #
147
+ # Net::IMAP automatically stores and discards capability data according to the
148
+ # the requirements and recommendations in
149
+ # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
150
+ # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
151
+ # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
152
+ # Use #capable?, #auth_capable?, or #capabilities to use this cache and avoid
153
+ # sending the #capability command unnecessarily.
154
+ #
155
+ # The server may advertise its initial capabilities using the +CAPABILITY+
156
+ # ResponseCode in a +PREAUTH+ or +OK+ #greeting. When TLS has started
157
+ # (#starttls) and after authentication (#login or #authenticate), the server's
158
+ # capabilities may change and cached capabilities are discarded. The server
159
+ # may send updated capabilities with an +OK+ TaggedResponse to #login or
160
+ # #authenticate, and these will be cached by Net::IMAP. But the
161
+ # TaggedResponse to #starttls MUST be ignored--it is sent before TLS starts
162
+ # and is unprotected.
163
+ #
164
+ # When storing capability values to variables, be careful that they are
165
+ # discarded or reset appropriately, especially following #starttls.
166
+ #
167
+ # === Using IMAP4rev1 extensions
168
+ #
169
+ # See the {IANA IMAP4 capabilities
170
+ # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list of
171
+ # all standard capabilities, and their reference RFCs.
172
+ #
173
+ # IMAP4rev1 servers must not activate behavior that is incompatible with the
174
+ # base specification until an explicit client action invokes a capability,
175
+ # e.g. sending a command or command argument specific to that capability.
176
+ # Servers may send data with backward compatible behavior, such as response
177
+ # codes or mailbox attributes, at any time without client action.
178
+ #
179
+ # Invoking capabilities which are unknown to Net::IMAP may cause unexpected
180
+ # behavior and errors. For example, ResponseParseError is raised when
181
+ # unknown response syntax is received. Invoking commands or command
182
+ # parameters that are unsupported by the server may raise NoResponseError,
183
+ # BadResponseError, or cause other unexpected behavior.
184
+ #
185
+ # Some capabilities must be explicitly activated using the #enable command.
186
+ # See #enable for details.
187
+ #
115
188
  # == Thread Safety
116
189
  #
117
190
  # Net::IMAP supports concurrent threads. For example,
118
191
  #
119
192
  # imap = Net::IMAP.new("imap.foo.net", "imap2")
120
- # imap.authenticate("cram-md5", "bar", "password")
193
+ # imap.authenticate("scram-md5", "bar", "password")
121
194
  # imap.select("inbox")
122
195
  # fetch_thread = Thread.start { imap.fetch(1..-1, "UID") }
123
196
  # search_result = imap.search(["BODY", "hello"])
@@ -173,24 +246,54 @@ module Net
173
246
  # == What's here?
174
247
  #
175
248
  # * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods]
176
- # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
177
- # * {...for any state}[rdoc-ref:Net::IMAP@IMAP+commands+for+any+state]
178
- # * {...for the "not authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Not+Authenticated-22+state]
179
- # * {...for the "authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Authenticated-22+state]
180
- # * {...for the "selected" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Selected-22+state]
181
- # * {...for the "logout" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Logout-22+state]
182
- # * {Supported IMAP extensions}[rdoc-ref:Net::IMAP@Supported+IMAP+extensions]
249
+ # * {Server capabilities}[rdoc-ref:Net::IMAP@Server+capabilities]
183
250
  # * {Handling server responses}[rdoc-ref:Net::IMAP@Handling+server+responses]
251
+ # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
252
+ # * {for any state}[rdoc-ref:Net::IMAP@Any+state]
253
+ # * {for the "not authenticated" state}[rdoc-ref:Net::IMAP@Not+Authenticated+state]
254
+ # * {for the "authenticated" state}[rdoc-ref:Net::IMAP@Authenticated+state]
255
+ # * {for the "selected" state}[rdoc-ref:Net::IMAP@Selected+state]
256
+ # * {for the "logout" state}[rdoc-ref:Net::IMAP@Logout+state]
257
+ # * {IMAP extension support}[rdoc-ref:Net::IMAP@IMAP+extension+support]
184
258
  #
185
259
  # === Connection control methods
186
260
  #
187
- # - Net::IMAP.new: A new client connects immediately and waits for a
188
- # successful server greeting before returning the new client object.
261
+ # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
262
+ # waits for a successful server greeting before the method returns.
189
263
  # - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
190
264
  # - #logout: Tells the server to end the session. Enters the "_logout_" state.
191
265
  # - #disconnect: Disconnects the connection (without sending #logout first).
192
266
  # - #disconnected?: True if the connection has been closed.
193
267
  #
268
+ # === Server capabilities
269
+ #
270
+ # - #capable?: Returns whether the server supports a given capability.
271
+ # - #capabilities: Returns the server's capabilities as an array of strings.
272
+ # - #auth_capable?: Returns whether the server advertises support for a given
273
+ # SASL mechanism, for use with #authenticate.
274
+ # - #auth_mechanisms: Returns the #authenticate SASL mechanisms which
275
+ # the server claims to support as an array of strings.
276
+ # - #clear_cached_capabilities: Clears cached capabilities.
277
+ #
278
+ # <em>The capabilities cache is automatically cleared after completing
279
+ # #starttls, #login, or #authenticate.</em>
280
+ # - #capability: Sends the +CAPABILITY+ command and returns the #capabilities.
281
+ #
282
+ # <em>In general, #capable? should be used rather than explicitly sending a
283
+ # +CAPABILITY+ command to the server.</em>
284
+ #
285
+ # === Handling server responses
286
+ #
287
+ # - #greeting: The server's initial untagged response, which can indicate a
288
+ # pre-authenticated connection.
289
+ # - #responses: Yields unhandled UntaggedResponse#data and <em>non-+nil+</em>
290
+ # ResponseCode#data.
291
+ # - #clear_responses: Deletes unhandled data from #responses and returns it.
292
+ # - #add_response_handler: Add a block to be called inside the receiver thread
293
+ # with every server response.
294
+ # - #response_handlers: Returns the list of response handlers.
295
+ # - #remove_response_handler: Remove a previously added response handler.
296
+ #
194
297
  # === Core \IMAP commands
195
298
  #
196
299
  # The following commands are defined either by
@@ -199,69 +302,48 @@ module Net
199
302
  # [IDLE[https://tools.ietf.org/html/rfc2177]],
200
303
  # [NAMESPACE[https://tools.ietf.org/html/rfc2342]],
201
304
  # [UNSELECT[https://tools.ietf.org/html/rfc3691]],
202
- #--
203
- # TODO: [ENABLE[https://tools.ietf.org/html/rfc5161]],
204
- # TODO: [LIST-EXTENDED[https://tools.ietf.org/html/rfc5258]],
205
- # TODO: [LIST-STATUS[https://tools.ietf.org/html/rfc5819]],
206
- #++
305
+ # [ENABLE[https://tools.ietf.org/html/rfc5161]],
207
306
  # [MOVE[https://tools.ietf.org/html/rfc6851]].
208
307
  # These extensions are widely supported by modern IMAP4rev1 servers and have
209
308
  # all been integrated into [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
210
- # <em>Note: Net::IMAP doesn't fully support IMAP4rev2 yet.</em>
211
- #
212
- #--
213
- # TODO: When IMAP4rev2 is supported, add the following to the each of the
214
- # appropriate commands below.
215
- # Note:: CHECK has been removed from IMAP4rev2.
216
- # Note:: LSUB is obsoleted by +LIST-EXTENDED and has been removed from IMAP4rev2.
217
- # <em>Some arguments require the +LIST-EXTENDED+ or +IMAP4rev2+ capability.</em>
218
- # <em>Requires either the +ENABLE+ or +IMAP4rev2+ capability.</em>
219
- # <em>Requires either the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
220
- # <em>Requires either the +IDLE+ or +IMAP4rev2+ capability.</em>
221
- # <em>Requires either the +UNSELECT+ or +IMAP4rev2+ capability.</em>
222
- # <em>Requires either the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
223
- # <em>Requires either the +MOVE+ or +IMAP4rev2+ capability.</em>
224
- #++
225
- #
226
- # ==== \IMAP commands for any state
309
+ # <em>*NOTE:* Net::IMAP doesn't support IMAP4rev2 yet.</em>
310
+ #
311
+ # ==== Any state
227
312
  #
228
313
  # - #capability: Returns the server's capabilities as an array of strings.
229
314
  #
230
- # <em>Capabilities may change after</em> #starttls, #authenticate, or #login
231
- # <em>and cached capabilities must be reloaded.</em>
315
+ # <em>In general, #capable? should be used rather than explicitly sending a
316
+ # +CAPABILITY+ command to the server.</em>
232
317
  # - #noop: Allows the server to send unsolicited untagged #responses.
233
318
  # - #logout: Tells the server to end the session. Enters the "_logout_" state.
234
319
  #
235
- # ==== \IMAP commands for the "Not Authenticated" state
320
+ # ==== Not Authenticated state
236
321
  #
237
- # In addition to the universal commands, the following commands are valid in
238
- # the "<em>not authenticated</em>" state:
322
+ # In addition to the commands for any state, the following commands are valid
323
+ # in the "<em>not authenticated</em>" state:
239
324
  #
240
325
  # - #starttls: Upgrades a clear-text connection to use TLS.
241
326
  #
242
327
  # <em>Requires the +STARTTLS+ capability.</em>
243
- # - #authenticate: Identifies the client to the server using a {SASL
244
- # mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml].
245
- # Enters the "_authenticated_" state.
328
+ # - #authenticate: Identifies the client to the server using the given
329
+ # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
330
+ # and credentials. Enters the "_authenticated_" state.
246
331
  #
247
- # <em>Requires the <tt>AUTH=#{mechanism}</tt> capability for the chosen
248
- # mechanism.</em>
332
+ # <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
333
+ # supported mechanisms.</em>
249
334
  # - #login: Identifies the client to the server using a plain text password.
250
335
  # Using #authenticate is generally preferred. Enters the "_authenticated_"
251
336
  # state.
252
337
  #
253
338
  # <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
254
339
  #
255
- # ==== \IMAP commands for the "Authenticated" state
256
- #
257
- # In addition to the universal commands, the following commands are valid in
258
- # the "_authenticated_" state:
340
+ # ==== Authenticated state
259
341
  #
260
- #--
261
- # - #enable: <em>Not implemented by Net::IMAP, yet.</em>
342
+ # In addition to the commands for any state, the following commands are valid
343
+ # in the "_authenticated_" state:
262
344
  #
263
- # <em>Requires the +ENABLE+ capability.</em>
264
- #++
345
+ # - #enable: Enables backwards incompatible server extensions.
346
+ # <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
265
347
  # - #select: Open a mailbox and enter the "_selected_" state.
266
348
  # - #examine: Open a mailbox read-only, and enter the "_selected_" state.
267
349
  # - #create: Creates a new mailbox.
@@ -271,37 +353,31 @@ module Net
271
353
  # - #unsubscribe: Removes a mailbox from the "subscribed" set.
272
354
  # - #list: Returns names and attributes of mailboxes matching a given pattern.
273
355
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
274
- #
275
- # <em>Requires the +NAMESPACE+ capability.</em>
356
+ # <em>Requires the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
276
357
  # - #status: Returns mailbox information, e.g. message count, unseen message
277
358
  # count, +UIDVALIDITY+ and +UIDNEXT+.
278
359
  # - #append: Appends a message to the end of a mailbox.
279
360
  # - #idle: Allows the server to send updates to the client, without the client
280
361
  # needing to poll using #noop.
362
+ # <em>Requires the +IDLE+ or +IMAP4rev2+ capability.</em>
363
+ # - *Obsolete* #lsub: <em>Replaced by <tt>LIST-EXTENDED</tt> and removed from
364
+ # +IMAP4rev2+.</em> Lists mailboxes in the "subscribed" set.
281
365
  #
282
- # <em>Requires the +IDLE+ capability.</em>
283
- # - #lsub: Lists mailboxes the user has declared "active" or "subscribed".
284
- #--
285
- # <em>Replaced by</em> <tt>LIST-EXTENDED</tt> <em>and removed from</em>
286
- # +IMAP4rev2+. <em>However, Net::IMAP hasn't implemented</em>
287
- # <tt>LIST-EXTENDED</tt> _yet_.
288
- #++
366
+ # <em>*Note:* Net::IMAP hasn't implemented <tt>LIST-EXTENDED</tt> yet.</em>
289
367
  #
290
- # ==== \IMAP commands for the "Selected" state
368
+ # ==== Selected state
291
369
  #
292
- # In addition to the universal commands and the "authenticated" commands, the
293
- # following commands are valid in the "_selected_" state:
370
+ # In addition to the commands for any state and the "_authenticated_"
371
+ # commands, the following commands are valid in the "_selected_" state:
294
372
  #
295
373
  # - #close: Closes the mailbox and returns to the "_authenticated_" state,
296
374
  # expunging deleted messages, unless the mailbox was opened as read-only.
297
375
  # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
298
376
  # without expunging any messages.
299
- #
300
- # <em>Requires the +UNSELECT+ capability.</em>
377
+ # <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
301
378
  # - #expunge: Permanently removes messages which have the Deleted flag set.
302
- # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
303
- #
304
- # <em>Requires the +UIDPLUS+ capability.</em>
379
+ # - #uid_expunge: Restricts expunge to only remove the specified UIDs.
380
+ # <em>Requires the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
305
381
  # - #search, #uid_search: Returns sequence numbers or UIDs of messages that
306
382
  # match the given searching criteria.
307
383
  # - #fetch, #uid_fetch: Returns data associated with a set of messages,
@@ -311,45 +387,35 @@ module Net
311
387
  # specified destination mailbox.
312
388
  # - #move, #uid_move: Moves the specified messages to the end of the
313
389
  # specified destination mailbox, expunging them from the current mailbox.
390
+ # <em>Requires the +MOVE+ or +IMAP4rev2+ capability.</em>
391
+ # - #check: <em>*Obsolete:* removed from +IMAP4rev2+.</em>
392
+ # Can be replaced with #noop or #idle.
314
393
  #
315
- # <em>Requires the +MOVE+ capability.</em>
316
- # - #check: Mostly obsolete. Can be replaced with #noop or #idle.
317
- #--
318
- # <em>Removed from IMAP4rev2.</em>
319
- #++
320
- #
321
- # ==== \IMAP commands for the "Logout" state
394
+ # ==== Logout state
322
395
  #
323
- # No \IMAP commands are valid in the +logout+ state. If the socket is still
396
+ # No \IMAP commands are valid in the "_logout_" state. If the socket is still
324
397
  # open, Net::IMAP will close it after receiving server confirmation.
325
398
  # Exceptions will be raised by \IMAP commands that have already started and
326
399
  # are waiting for a response, as well as any that are called after logout.
327
400
  #
328
- # === Supported \IMAP extensions
401
+ # === \IMAP extension support
329
402
  #
330
403
  # ==== RFC9051: +IMAP4rev2+
331
404
  #
332
- # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is <em>not supported
333
- # yet</em>, Net::IMAP supports several extensions that have been folded into
334
- # it: +IDLE+, +MOVE+, +NAMESPACE+, +UIDPLUS+, and +UNSELECT+.
335
- #--
336
- # TODO: RFC4466, ABNF extensions (automatic support for other extensions)
337
- # TODO: +ESEARCH+, ExtendedSearchData
338
- # TODO: +SEARCHRES+,
339
- # TODO: +ENABLE+,
340
- # TODO: +SASL-IR+,
341
- # TODO: +LIST-EXTENDED+,
342
- # TODO: +LIST-STATUS+,
343
- # TODO: +LITERAL-+,
344
- # TODO: +BINARY+ (only the FETCH side)
345
- # TODO: +SPECIAL-USE+
346
- # implicitly supported, but we can do better: Response codes: RFC5530, etc
347
- # implicitly supported, but we can do better: <tt>STATUS=SIZE</tt>
348
- # implicitly supported, but we can do better: <tt>STATUS DELETED</tt>
349
- #++
350
- # Commands for these extensions are included with the {Core IMAP
351
- # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above. Other supported
352
- # extensons are listed below.
405
+ # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is not supported
406
+ # yet, Net::IMAP supports several extensions that have been folded into it:
407
+ # +ENABLE+, +IDLE+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, and +UNSELECT+.
408
+ # Commands for these extensions are listed with the {Core IMAP
409
+ # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above.
410
+ #
411
+ # >>>
412
+ # <em>The following are folded into +IMAP4rev2+ but are currently
413
+ # unsupported or incompletely supported by</em> Net::IMAP<em>: RFC4466
414
+ # extensions, +ESEARCH+, +SEARCHRES+, +LIST-EXTENDED+,
415
+ # +LIST-STATUS+, +LITERAL-+, +BINARY+ fetch, and +SPECIAL-USE+. The
416
+ # following extensions are implicitly supported, but will be updated with
417
+ # more direct support: RFC5530 response codes, <tt>STATUS=SIZE</tt>, and
418
+ # <tt>STATUS=DELETED</tt>.</em>
353
419
  #
354
420
  # ==== RFC2087: +QUOTA+
355
421
  # - #getquota: returns the resource usage and limits for a quota root
@@ -358,92 +424,47 @@ module Net
358
424
  # - #setquota: sets the resource limits for a given quota root.
359
425
  #
360
426
  # ==== RFC2177: +IDLE+
361
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
362
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
427
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
428
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
363
429
  # - #idle: Allows the server to send updates to the client, without the client
364
430
  # needing to poll using #noop.
365
431
  #
366
432
  # ==== RFC2342: +NAMESPACE+
367
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
368
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
433
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
434
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
369
435
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
370
436
  #
371
437
  # ==== RFC2971: +ID+
372
438
  # - #id: exchanges client and server implementation information.
373
439
  #
374
- #--
375
- # ==== RFC3502: +MULTIAPPEND+
376
- # TODO...
377
- #++
378
- #
379
- #--
380
- # ==== RFC3516: +BINARY+
381
- # TODO...
382
- #++
383
- #
384
440
  # ==== RFC3691: +UNSELECT+
385
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
386
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
441
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
442
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
387
443
  # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
388
444
  # without expunging any messages.
389
445
  #
390
446
  # ==== RFC4314: +ACL+
391
447
  # - #getacl: lists the authenticated user's access rights to a mailbox.
392
448
  # - #setacl: sets the access rights for a user on a mailbox
393
- #--
394
- # TODO: #deleteacl, #listrights, #myrights
395
- #++
396
- # - *_Note:_* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
449
+ # >>>
450
+ # *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
397
451
  #
398
452
  # ==== RFC4315: +UIDPLUS+
399
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
400
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
453
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
454
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
401
455
  # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
402
456
  # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode
403
457
  # - Updates #append with the +APPENDUID+ ResponseCode
404
458
  # - Updates #copy, #move with the +COPYUID+ ResponseCode
405
459
  #
406
- #--
407
- # ==== RFC4466: Collected Extensions to IMAP4 ABNF
408
- # TODO...
409
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this RFC updates
410
- # the protocol to enable new optional parameters to many commands: #select,
411
- # #examine, #create, #rename, #fetch, #uid_fetch, #store, #uid_store, #search,
412
- # #uid_search, and #append. However, specific parameters are not defined.
413
- # Extensions to these commands use this syntax whenever possible. Net::IMAP
414
- # may be partially compatible with extensions to these commands, even without
415
- # any explicit support.
416
- #++
417
- #
418
- #--
419
- # ==== RFC4731 +ESEARCH+
420
- # TODO...
421
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
422
- # - Updates #search, #uid_search to accept result options: +MIN+, +MAX+,
423
- # +ALL+, +COUNT+, and to return ExtendedSearchData.
424
- #++
425
- #
426
- #--
427
460
  # ==== RFC4959: +SASL-IR+
428
- # TODO...
429
461
  # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
430
- # - Updates #authenticate to reduce round-trips for supporting mechanisms.
431
- #++
432
- #
433
- #--
434
- # ==== RFC4978: COMPRESS=DEFLATE
435
- # TODO...
436
- #++
462
+ # - Updates #authenticate with the option to send an initial response.
437
463
  #
438
- #--
439
- # ==== RFC5182 +SEARCHRES+
440
- # TODO...
441
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
442
- # - Updates #search, #uid_search with the +SAVE+ result option.
443
- # - Updates #copy, #uid_copy, #fetch, #uid_fetch, #move, #uid_move, #search,
444
- # #uid_search, #store, #uid_store, and #uid_expunge with ability to
445
- # reference the saved result of a previous #search or #uid_search command.
446
- #++
464
+ # ==== RFC5161: +ENABLE+
465
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
466
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
467
+ # - #enable: Enables backwards incompatible server extensions.
447
468
  #
448
469
  # ==== RFC5256: +SORT+
449
470
  # - #sort, #uid_sort: An alternate version of #search or #uid_search which
@@ -453,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,44 @@ 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.7"
665
+ VERSION = "0.4.1"
666
+
667
+ # Aliases for supported capabilities, to be used with the #enable command.
668
+ ENABLE_ALIASES = {
669
+ utf8: "UTF8=ACCEPT",
670
+ "UTF8=ONLY" => "UTF8=ACCEPT",
671
+ }.freeze
672
+
673
+ autoload :SASL, File.expand_path("imap/sasl", __dir__)
674
+ autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
675
+ autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
703
676
 
704
677
  include MonitorMixin
705
678
  if defined?(OpenSSL::SSL)
@@ -707,35 +680,6 @@ module Net
707
680
  include SSL
708
681
  end
709
682
 
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
683
  # Returns the debug mode.
740
684
  def self.debug
741
685
  return @@debug
@@ -762,9 +706,175 @@ module Net
762
706
  alias default_ssl_port default_tls_port
763
707
  end
764
708
 
709
+ # Returns the initial greeting the server, an UntaggedResponse.
710
+ attr_reader :greeting
711
+
712
+ # Seconds to wait until a connection is opened.
713
+ # If the IMAP object cannot open a connection within this time,
714
+ # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
715
+ attr_reader :open_timeout
716
+
717
+ # Seconds to wait until an IDLE response is received.
718
+ attr_reader :idle_response_timeout
719
+
720
+ # The hostname this client connected to
721
+ attr_reader :host
722
+
723
+ # The port this client connected to
724
+ attr_reader :port
725
+
726
+ # Returns the
727
+ # {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]
728
+ # used by the SSLSocket when TLS is attempted, even when the TLS handshake
729
+ # is unsuccessful. The context object will be frozen.
730
+ #
731
+ # Returns +nil+ for a plaintext connection.
732
+ attr_reader :ssl_ctx
733
+
734
+ # Returns the parameters that were sent to #ssl_ctx
735
+ # {set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]
736
+ # when the connection tries to use TLS (even when unsuccessful).
737
+ #
738
+ # Returns +false+ for a plaintext connection.
739
+ attr_reader :ssl_ctx_params
740
+
741
+ # Creates a new Net::IMAP object and connects it to the specified
742
+ # +host+.
743
+ #
744
+ # ==== Options
745
+ #
746
+ # Accepts the following options:
747
+ #
748
+ # [port]
749
+ # Port number. Defaults to 993 when +ssl+ is truthy, and 143 otherwise.
750
+ #
751
+ # [ssl]
752
+ # If +true+, the connection will use TLS with the default params set by
753
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params].
754
+ # If +ssl+ is a hash, it's passed to
755
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
756
+ # the keys are names of attribute assignment methods on
757
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
758
+ #
759
+ # [open_timeout]
760
+ # Seconds to wait until a connection is opened
761
+ # [idle_response_timeout]
762
+ # Seconds to wait until an IDLE response is received
763
+ #
764
+ # See DeprecatedClientOptions.new for deprecated arguments.
765
+ #
766
+ # ==== Examples
767
+ #
768
+ # Connect to cleartext port 143 at mail.example.com and recieve the server greeting:
769
+ # imap = Net::IMAP.new('mail.example.com', ssl: false) # => #<Net::IMAP:0x00007f79b0872bd0>
770
+ # imap.port => 143
771
+ # imap.tls_verified? => false
772
+ # imap.greeting => name: ("OK" | "PREAUTH") => status
773
+ # status # => "OK"
774
+ # # The client is connected in the "Not Authenticated" state.
775
+ #
776
+ # Connect with TLS to port 993
777
+ # imap = Net::IMAP.new('mail.example.com', ssl: true) # => #<Net::IMAP:0x00007f79b0872bd0>
778
+ # imap.port => 993
779
+ # imap.tls_verified? => true
780
+ # imap.greeting => name: (/OK/i | /PREAUTH/i) => status
781
+ # case status
782
+ # in /OK/i
783
+ # # The client is connected in the "Not Authenticated" state.
784
+ # imap.authenticate("PLAIN", "joe_user", "joes_password")
785
+ # in /PREAUTH/i
786
+ # # The client is connected in the "Authenticated" state.
787
+ # end
788
+ #
789
+ # Connect with prior authentication, for example using an SSL certificate:
790
+ # ssl_ctx_params = {
791
+ # cert: OpenSSL::X509::Certificate.new(File.read("client.crt")),
792
+ # key: OpenSSL::PKey::EC.new(File.read('client.key')),
793
+ # extra_chain_cert: [
794
+ # OpenSSL::X509::Certificate.new(File.read("intermediate.crt")),
795
+ # ],
796
+ # }
797
+ # imap = Net::IMAP.new('mail.example.com', ssl: ssl_ctx_params)
798
+ # imap.port => 993
799
+ # imap.tls_verified? => true
800
+ # imap.greeting => name: "PREAUTH"
801
+ # # The client is connected in the "Authenticated" state.
802
+ #
803
+ # ==== Exceptions
804
+ #
805
+ # The most common errors are:
806
+ #
807
+ # [Errno::ECONNREFUSED]
808
+ # Connection refused by +host+ or an intervening firewall.
809
+ # [Errno::ETIMEDOUT]
810
+ # Connection timed out (possibly due to packets being dropped by an
811
+ # intervening firewall).
812
+ # [Errno::ENETUNREACH]
813
+ # There is no route to that network.
814
+ # [SocketError]
815
+ # Hostname not known or other socket error.
816
+ # [Net::IMAP::ByeResponseError]
817
+ # Connected to the host successfully, but it immediately said goodbye.
818
+ #
819
+ def initialize(host, port: nil, ssl: nil,
820
+ open_timeout: 30, idle_response_timeout: 5)
821
+ super()
822
+ # Config options
823
+ @host = host
824
+ @port = port || (ssl ? SSL_PORT : PORT)
825
+ @open_timeout = Integer(open_timeout)
826
+ @idle_response_timeout = Integer(idle_response_timeout)
827
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
828
+
829
+ # Basic Client State
830
+ @utf8_strings = false
831
+ @debug_output_bol = true
832
+ @exception = nil
833
+ @greeting = nil
834
+ @capabilities = nil
835
+
836
+ # Client Protocol Reciever
837
+ @parser = ResponseParser.new
838
+ @responses = Hash.new {|h, k| h[k] = [] }
839
+ @response_handlers = []
840
+ @receiver_thread = nil
841
+ @receiver_thread_exception = nil
842
+ @receiver_thread_terminating = false
843
+
844
+ # Client Protocol Sender (including state for currently running commands)
845
+ @tag_prefix = "RUBY"
846
+ @tagno = 0
847
+ @tagged_responses = {}
848
+ @tagged_response_arrival = new_cond
849
+ @continued_command_tag = nil
850
+ @continuation_request_arrival = new_cond
851
+ @continuation_request_exception = nil
852
+ @idle_done_cond = nil
853
+ @logout_command_tag = nil
854
+
855
+ # Connection
856
+ @tls_verified = false
857
+ @sock = tcp_socket(@host, @port)
858
+ start_tls_session if ssl_ctx
859
+ start_imap_connection
860
+
861
+ # DEPRECATED: to remove in next version
862
+ @client_thread = Thread.current
863
+ end
864
+
865
+ # Returns true after the TLS negotiation has completed and the remote
866
+ # hostname has been verified. Returns false when TLS has been established
867
+ # but peer verification was disabled.
868
+ def tls_verified?; @tls_verified end
869
+
870
+ def client_thread # :nodoc:
871
+ warn "Net::IMAP#client_thread is deprecated and will be removed soon."
872
+ @client_thread
873
+ end
874
+
765
875
  # Disconnects from the server.
766
876
  #
767
- # Related: #logout
877
+ # Related: #logout, #logout!
768
878
  def disconnect
769
879
  return if disconnected?
770
880
  begin
@@ -794,62 +904,123 @@ module Net
794
904
  return @sock.closed?
795
905
  end
796
906
 
797
- # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
798
- # and returns an array of capabilities that the server supports. Each
799
- # capability is a string.
907
+ # Returns whether the server supports a given +capability+. When available,
908
+ # cached #capabilities are used without sending a new #capability command to
909
+ # the server.
800
910
  #
801
- # See the {IANA IMAP4 capabilities
802
- # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list
803
- # of all standard capabilities, and their reference RFCs.
911
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
912
+ # behaviour according to the server's advertised #capabilities.</em>
804
913
  #
805
- # >>>
806
- # <em>*Note* that Net::IMAP does not currently modify its
807
- # behaviour according to the capabilities of the server;
808
- # it is up to the user of the class to ensure that
809
- # a certain capability is supported by a server before
810
- # using it.</em>
914
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
811
915
  #
812
- # Capability requirements—other than +IMAP4rev1+—are listed in the
813
- # documentation for each command method.
916
+ # Related: #auth_capable?, #capabilities, #capability, #enable
917
+ def capable?(capability) capabilities.include? capability.to_s.upcase end
918
+ alias capability? capable?
919
+
920
+ # Returns the server capabilities. When available, cached capabilities are
921
+ # used without sending a new #capability command to the server.
814
922
  #
815
- # ===== Basic IMAP4rev1 capabilities
923
+ # To ensure a case-insensitive comparison, #capable? can be used instead.
816
924
  #
817
- # All IMAP4rev1 servers must include +IMAP4rev1+ in their capabilities list.
818
- # All IMAP4rev1 servers must _implement_ the +STARTTLS+,
819
- # <tt>AUTH=PLAIN</tt>, and +LOGINDISABLED+ capabilities, and clients must
820
- # respect their presence or absence. See the capabilites requirements on
821
- # #starttls, #login, and #authenticate.
925
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
926
+ # behaviour according to the server's advertised #capabilities.</em>
927
+ #
928
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
929
+ #
930
+ # Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable
931
+ def capabilities
932
+ @capabilities || capability
933
+ end
934
+
935
+ # Returns the #authenticate mechanisms that the server claims to support.
936
+ # These are derived from the #capabilities with an <tt>AUTH=</tt> prefix.
822
937
  #
823
- # ===== Using IMAP4rev1 extensions
938
+ # This may be different when the connection is cleartext or using TLS. Most
939
+ # servers will drop all <tt>AUTH=</tt> mechanisms from #capabilities after
940
+ # the connection has authenticated.
824
941
  #
825
- # IMAP4rev1 servers must not activate incompatible behavior until an
826
- # explicit client action invokes a capability, e.g. sending a command or
827
- # command argument specific to that capability. Extensions with backward
828
- # compatible behavior, such as response codes or mailbox attributes, may
829
- # be sent at any time.
942
+ # imap = Net::IMAP.new(hostname, ssl: false)
943
+ # imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"]
944
+ # imap.auth_mechanisms # => []
830
945
  #
831
- # Invoking capabilities which are unknown to Net::IMAP may cause unexpected
832
- # behavior and errors, for example ResponseParseError is raised when unknown
833
- # response syntax is received. Invoking commands or command parameters that
834
- # are unsupported by the server may raise NoResponseError, BadResponseError,
835
- # or cause other unexpected behavior.
946
+ # imap.starttls
947
+ # imap.capabilities # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2",
948
+ # # "AUTH=OAUTHBEARER"]
949
+ # imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"]
836
950
  #
837
- # ===== Caching +CAPABILITY+ responses
951
+ # imap.authenticate("XOAUTH2", username, oauth2_access_token)
952
+ # imap.auth_mechanisms # => []
953
+ #
954
+ # Related: #authenticate, #auth_capable?, #capabilities
955
+ def auth_mechanisms
956
+ capabilities
957
+ .grep(/\AAUTH=/i)
958
+ .map { _1.delete_prefix("AUTH=") }
959
+ end
960
+
961
+ # Returns whether the server supports a given SASL +mechanism+ for use with
962
+ # the #authenticate command. The +mechanism+ is supported when
963
+ # #capabilities includes <tt>"AUTH=#{mechanism.to_s.upcase}"</tt>. When
964
+ # available, cached capabilities are used without sending a new #capability
965
+ # command to the server.
966
+ #
967
+ # imap.capable? "AUTH=PLAIN" # => true
968
+ # imap.auth_capable? "PLAIN" # => true
969
+ # imap.auth_capable? "blurdybloop" # => false
970
+ #
971
+ # Related: #authenticate, #auth_mechanisms, #capable?, #capabilities
972
+ def auth_capable?(mechanism)
973
+ capable? "AUTH=#{mechanism}"
974
+ end
975
+
976
+ # Returns whether capabilities have been cached. When true, #capable? and
977
+ # #capabilities don't require sending a #capability command to the server.
978
+ #
979
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
980
+ #
981
+ # Related: #capable?, #capability, #clear_cached_capabilities
982
+ def capabilities_cached?
983
+ !!@capabilities
984
+ end
985
+
986
+ # Clears capabilities that have been remembered by the Net::IMAP client.
987
+ # This forces a #capability command to be sent the next time a #capabilities
988
+ # query method is called.
989
+ #
990
+ # Net::IMAP automatically discards its cached capabilities when they can
991
+ # change. Explicitly calling this _should_ be unnecessary for well-behaved
992
+ # servers.
993
+ #
994
+ # Related: #capable?, #capability, #capabilities_cached?
995
+ def clear_cached_capabilities
996
+ synchronize do
997
+ clear_responses("CAPABILITY")
998
+ @capabilities = nil
999
+ end
1000
+ end
1001
+
1002
+ # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
1003
+ # and returns an array of capabilities that are supported by the server.
1004
+ # The result is stored for use by #capable? and #capabilities.
838
1005
  #
839
- # Servers may send their capability list, unsolicited, using the
840
- # +CAPABILITY+ response code or an untagged +CAPABILITY+ response. These
841
- # responses can be retrieved and cached using #responses or
842
- # #add_response_handler.
1006
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
1007
+ # behaviour according to the server's advertised #capabilities.</em>
843
1008
  #
844
- # But cached capabilities _must_ be discarded after #starttls, #login, or
845
- # #authenticate. The OK TaggedResponse to #login and #authenticate may
846
- # include +CAPABILITY+ response code data, but the TaggedResponse for
847
- # #starttls is sent clear-text and cannot be trusted.
1009
+ # Net::IMAP automatically stores and discards capability data according to
1010
+ # the requirements and recommendations in
1011
+ # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
1012
+ # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
1013
+ # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
1014
+ # Use #capable?, #auth_capable?, or #capabilities to this cache and avoid
1015
+ # sending the #capability command unnecessarily.
848
1016
  #
1017
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
1018
+ #
1019
+ # Related: #capable?, #auth_capable?, #capability, #enable
849
1020
  def capability
850
1021
  synchronize do
851
1022
  send_command("CAPABILITY")
852
- return @responses.delete("CAPABILITY")[-1]
1023
+ @capabilities = clear_responses("CAPABILITY").last.freeze
853
1024
  end
854
1025
  end
855
1026
 
@@ -860,8 +1031,7 @@ module Net
860
1031
  # Note that the user should first check if the server supports the ID
861
1032
  # capability. For example:
862
1033
  #
863
- # capabilities = imap.capability
864
- # if capabilities.include?("ID")
1034
+ # if capable?(:ID)
865
1035
  # id = imap.id(
866
1036
  # name: "my IMAP client (ruby)",
867
1037
  # version: MyIMAP::VERSION,
@@ -879,7 +1049,7 @@ module Net
879
1049
  def id(client_id=nil)
880
1050
  synchronize do
881
1051
  send_command("ID", ClientID.new(client_id))
882
- @responses.delete("ID")&.last
1052
+ clear_responses("ID").last
883
1053
  end
884
1054
  end
885
1055
 
@@ -903,15 +1073,43 @@ module Net
903
1073
  # to inform the command to inform the server that the client is done with
904
1074
  # the connection.
905
1075
  #
906
- # Related: #disconnect
1076
+ # Related: #disconnect, #logout!
907
1077
  def logout
908
1078
  send_command("LOGOUT")
909
1079
  end
910
1080
 
1081
+ # Calls #logout then, after receiving the TaggedResponse for the +LOGOUT+,
1082
+ # calls #disconnect. Returns the TaggedResponse from +LOGOUT+. Returns
1083
+ # +nil+ when the client is already disconnected, in contrast to #logout
1084
+ # which raises an exception.
1085
+ #
1086
+ # If #logout raises a StandardError, a warning will be printed but the
1087
+ # exception will not be re-raised.
1088
+ #
1089
+ # This is useful in situations where the connection must be dropped, for
1090
+ # example for security or after tests. If logout errors need to be handled,
1091
+ # use #logout and #disconnect instead.
1092
+ #
1093
+ # Related: #logout, #disconnect
1094
+ def logout!
1095
+ logout unless disconnected?
1096
+ rescue => ex
1097
+ warn "%s during <Net::IMAP %s:%s> logout!: %s" % [
1098
+ ex.class, host, port, ex
1099
+ ]
1100
+ ensure
1101
+ disconnect
1102
+ end
1103
+
911
1104
  # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1]
912
1105
  # to start a TLS session.
913
1106
  #
914
- # Any +options+ are forwarded to OpenSSL::SSL::SSLContext#set_params.
1107
+ # Any +options+ are forwarded directly to
1108
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
1109
+ # the keys are names of attribute assignment methods on
1110
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
1111
+ #
1112
+ # See DeprecatedClientOptions#starttls for deprecated arguments.
915
1113
  #
916
1114
  # This method returns after TLS negotiation and hostname verification are
917
1115
  # both successful. Any error indicates that the connection has not been
@@ -921,132 +1119,159 @@ module Net
921
1119
  # >>>
922
1120
  # Any #response_handlers added before STARTTLS should be aware that the
923
1121
  # TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation.
924
- # TLS negotiation starts immediately after that response.
1122
+ # TLS starts immediately _after_ that response. Any response code sent
1123
+ # with the response (e.g. CAPABILITY) is insecure and cannot be trusted.
925
1124
  #
926
1125
  # Related: Net::IMAP.new, #login, #authenticate
927
1126
  #
928
1127
  # ===== Capability
929
- #
930
- # The server's capabilities must include +STARTTLS+.
1128
+ # Clients should not call #starttls unless the server advertises the
1129
+ # +STARTTLS+ capability.
931
1130
  #
932
1131
  # Server capabilities may change after #starttls, #login, and #authenticate.
933
- # Cached capabilities _must_ be invalidated after this method completes.
1132
+ # Cached #capabilities will be cleared when this method completes.
934
1133
  #
935
- # The TaggedResponse to #starttls is sent clear-text, so the server <em>must
936
- # *not*</em> send capabilities in the #starttls response and clients <em>must
937
- # not</em> use them if they are sent. Servers will generally send an
938
- # unsolicited untagged response immeditely _after_ #starttls completes.
939
- #
940
- def starttls(options = {}, verify = true)
1134
+ def starttls(**options)
1135
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
941
1136
  send_command("STARTTLS") do |resp|
942
1137
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
943
- begin
944
- # for backward compatibility
945
- certs = options.to_str
946
- options = create_ssl_params(certs, verify)
947
- rescue NoMethodError
948
- end
949
- start_tls_session(options)
1138
+ clear_cached_capabilities
1139
+ clear_responses
1140
+ start_tls_session
950
1141
  end
951
1142
  end
952
1143
  end
953
1144
 
954
1145
  # :call-seq:
955
- # authenticate(mechanism, ...) -> ok_resp
956
- # authenticate(mech, *creds, **props) {|prop, auth| val } -> ok_resp
957
- # authenticate(mechanism, authnid, credentials, authzid=nil) -> ok_resp
958
- # authenticate(mechanism, **properties) -> ok_resp
959
- # authenticate(mechanism) {|propname, authctx| prop_value } -> ok_resp
1146
+ # authenticate(mechanism, *,
1147
+ # sasl_ir: true,
1148
+ # registry: Net::IMAP::SASL.authenticators,
1149
+ # **, &) -> ok_resp
960
1150
  #
961
1151
  # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
962
1152
  # to authenticate the client. If successful, the connection enters the
963
1153
  # "_authenticated_" state.
964
1154
  #
965
1155
  # +mechanism+ is the name of the \SASL authentication mechanism to be used.
966
- # All other arguments are forwarded to the authenticator for the requested
967
- # mechanism. The listed call signatures are suggestions. <em>The
968
- # documentation for each individual mechanism must be consulted for its
969
- # specific parameters.</em>
970
1156
  #
971
- # An exception Net::IMAP::NoResponseError is raised if authentication fails.
1157
+ # +sasl_ir+ allows or disallows sending an "initial response" (see the
1158
+ # +SASL-IR+ capability, below).
972
1159
  #
973
- # Related: #login, #starttls
1160
+ # All other arguments are forwarded to the registered SASL authenticator for
1161
+ # the requested mechanism. <em>The documentation for each individual
1162
+ # mechanism must be consulted for its specific parameters.</em>
974
1163
  #
975
- # ==== Supported SASL Mechanisms
1164
+ # Related: #login, #starttls, #auth_capable?, #auth_mechanisms
976
1165
  #
977
- # +PLAIN+:: See PlainAuthenticator.
978
- # Login using clear-text username and password.
1166
+ # ==== Mechanisms
979
1167
  #
980
- # +XOAUTH2+:: See XOauth2Authenticator.
981
- # Login using a username and OAuth2 access token.
982
- # Non-standard and obsoleted by +OAUTHBEARER+, but widely
983
- # supported.
1168
+ # Each mechanism has different properties and requirements. Please consult
1169
+ # the documentation for the specific mechanisms you are using:
984
1170
  #
985
- # >>>
986
- # *Deprecated:* <em>Obsolete mechanisms are available for backwards
987
- # compatibility.</em>
1171
+ # +ANONYMOUS+::
1172
+ # See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
1173
+ #
1174
+ # Allows the user to gain access to public services or resources without
1175
+ # authenticating or disclosing an identity.
1176
+ #
1177
+ # +EXTERNAL+::
1178
+ # See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
1179
+ #
1180
+ # Authenticates using already established credentials, such as a TLS
1181
+ # certificate or IPsec.
1182
+ #
1183
+ # +OAUTHBEARER+::
1184
+ # See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator].
1185
+ #
1186
+ # Login using an OAuth2 Bearer token. This is the standard mechanism
1187
+ # for using OAuth2 with \SASL, but it is not yet deployed as widely as
1188
+ # +XOAUTH2+.
1189
+ #
1190
+ # +PLAIN+::
1191
+ # See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator].
988
1192
  #
989
- # For +DIGEST-MD5+ see DigestMD5Authenticator.
1193
+ # Login using clear-text username and password.
990
1194
  #
991
- # For +LOGIN+, see LoginAuthenticator.
1195
+ # +SCRAM-SHA-1+::
1196
+ # +SCRAM-SHA-256+::
1197
+ # See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator].
992
1198
  #
993
- # For +CRAM-MD5+, see CramMD5Authenticator.
1199
+ # Login by username and password. The password is not sent to the
1200
+ # server but is used in a salted challenge/response exchange.
1201
+ # +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
1202
+ # Net::IMAP::SASL. New authenticators can easily be added for any other
1203
+ # <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
1204
+ # OpenSSL::Digest.
994
1205
  #
995
- # <em>Using a deprecated mechanism will print a warning.</em>
1206
+ # +XOAUTH2+::
1207
+ # See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator].
996
1208
  #
997
- # See Net::IMAP::Authenticators for information on plugging in
998
- # authenticators for other mechanisms. See the {SASL mechanism
1209
+ # Login using a username and an OAuth2 access token. Non-standard and
1210
+ # obsoleted by +OAUTHBEARER+, but widely supported.
1211
+ #
1212
+ # See the {SASL mechanism
999
1213
  # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
1000
- # for information on these and other SASL mechanisms.
1214
+ # for a list of all SASL mechanisms and their specifications. To register
1215
+ # new authenticators, see Authenticators.
1001
1216
  #
1002
- # ===== Capabilities
1217
+ # ===== Deprecated mechanisms
1003
1218
  #
1004
- # Clients MUST NOT attempt to authenticate with a mechanism unless
1005
- # <tt>"AUTH=#{mechanism}"</tt> for that mechanism is a server capability.
1219
+ # <em>Obsolete mechanisms should be avoided, but are still available for
1220
+ # backwards compatibility. See</em> Net::IMAP::SASL@Deprecated+mechanisms.
1221
+ # <em>Using a deprecated mechanism will print a warning.</em>
1006
1222
  #
1007
- # Server capabilities may change after #starttls, #login, and #authenticate.
1008
- # Cached capabilities _must_ be invalidated after this method completes.
1009
- # The TaggedResponse to #authenticate may include updated capabilities in
1010
- # its ResponseCode.
1011
- #
1012
- # ===== Example
1013
- # If the authenticators ignore unhandled keyword arguments, the same config
1014
- # can be used for multiple mechanisms:
1015
- #
1016
- # password = nil # saved locally, so we don't ask more than once
1017
- # accesstok = nil # saved locally...
1018
- # creds = {
1019
- # authcid: username,
1020
- # password: proc { password ||= ui.prompt_for_password },
1021
- # oauth2_token: proc { accesstok ||= kms.fresh_access_token },
1022
- # }
1023
- # capa = imap.capability
1024
- # if capa.include? "AUTH=OAUTHBEARER"
1025
- # imap.authenticate "OAUTHBEARER", **creds # authcid, oauth2_token
1026
- # elsif capa.include? "AUTH=XOAUTH2"
1027
- # imap.authenticate "XOAUTH2", **creds # authcid, oauth2_token
1028
- # elsif capa.include? "AUTH=SCRAM-SHA-256"
1029
- # imap.authenticate "SCRAM-SHA-256", **creds # authcid, password
1030
- # elsif capa.include? "AUTH=PLAIN"
1031
- # imap.authenticate "PLAIN", **creds # authcid, password
1032
- # elsif capa.include? "AUTH=DIGEST-MD5"
1033
- # imap.authenticate "DIGEST-MD5", **creds # authcid, password
1034
- # elsif capa.include? "LOGINDISABLED"
1035
- # raise "the server has disabled login"
1036
- # else
1223
+ # ==== Capabilities
1224
+ #
1225
+ # <tt>"AUTH=#{mechanism}"</tt> capabilities indicate server support for
1226
+ # mechanisms. Use #auth_capable? or #auth_mechanisms to check for support
1227
+ # before using a particular mechanism.
1228
+ #
1229
+ # if imap.auth_capable? "XOAUTH2"
1230
+ # imap.authenticate "XOAUTH2", username, oauth2_access_token
1231
+ # elsif imap.auth_capable? "PLAIN"
1232
+ # imap.authenticate "PLAIN", username, password
1233
+ # elsif !imap.capability? "LOGINDISABLED"
1037
1234
  # imap.login username, password
1235
+ # else
1236
+ # raise "No acceptable authentication mechanism is available"
1038
1237
  # end
1039
1238
  #
1040
- def authenticate(mechanism, *args, **props, &cb)
1041
- authenticator = self.class.authenticator(mechanism, *args, **props, &cb)
1042
- send_command("AUTHENTICATE", mechanism) do |resp|
1043
- if resp.instance_of?(ContinuationRequest)
1044
- data = authenticator.process(resp.data.text.unpack("m")[0])
1045
- s = [data].pack("m0")
1046
- send_string_data(s)
1047
- put_string(CRLF)
1048
- end
1239
+ # Although servers should list all supported \SASL mechanisms, they may
1240
+ # allow authentication with an unlisted +mechanism+.
1241
+ #
1242
+ # If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported
1243
+ # and the appropriate <tt>"AUTH=#{mechanism}"</tt> capability is present,
1244
+ # an "initial response" may be sent as an argument to the +AUTHENTICATE+
1245
+ # command, saving a round-trip. The SASL exchange allows for server
1246
+ # challenges and client responses, but many mechanisms expect the client to
1247
+ # "respond" first. The initial response will only be sent for
1248
+ # "client-first" mechanisms.
1249
+ #
1250
+ # Server capabilities may change after #starttls, #login, and #authenticate.
1251
+ # Previously cached #capabilities will be cleared when this method
1252
+ # completes. If the TaggedResponse to #authenticate includes updated
1253
+ # capabilities, they will be cached.
1254
+ def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback)
1255
+ mechanism = mechanism.to_s.tr("_", "-").upcase
1256
+ authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
1257
+ cmdargs = ["AUTHENTICATE", mechanism]
1258
+ if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
1259
+ authenticator.respond_to?(:initial_response?) &&
1260
+ authenticator.initial_response?
1261
+ response = authenticator.process(nil)
1262
+ cmdargs << (response.empty? ? "=" : [response].pack("m0"))
1049
1263
  end
1264
+ result = send_command_with_continuations(*cmdargs) {|data|
1265
+ challenge = data.unpack1("m")
1266
+ response = authenticator.process challenge
1267
+ [response].pack("m0")
1268
+ }
1269
+ if authenticator.respond_to?(:done?) && !authenticator.done?
1270
+ logout!
1271
+ raise SASL::AuthenticationIncomplete, result
1272
+ end
1273
+ @capabilities = capabilities_from_resp_code result
1274
+ result
1050
1275
  end
1051
1276
 
1052
1277
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1054,16 +1279,25 @@ module Net
1054
1279
  # this +user+. If successful, the connection enters the "_authenticated_"
1055
1280
  # state.
1056
1281
  #
1057
- # Using #authenticate is generally preferred over #login. The LOGIN command
1058
- # is not the same as #authenticate with the "LOGIN" +mechanism+.
1282
+ # Using #authenticate {should be
1283
+ # preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command]
1284
+ # over #login. The LOGIN command is not the same as #authenticate with the
1285
+ # "LOGIN" +mechanism+.
1059
1286
  #
1060
1287
  # A Net::IMAP::NoResponseError is raised if authentication fails.
1061
1288
  #
1062
1289
  # Related: #authenticate, #starttls
1063
1290
  #
1064
- # ==== Capabilities
1065
- # Clients MUST NOT call #login if +LOGINDISABLED+ is listed with the
1066
- # capabilities.
1291
+ # ===== Capabilities
1292
+ #
1293
+ # An IMAP client MUST NOT call #login when the server advertises the
1294
+ # +LOGINDISABLED+ capability.
1295
+ #
1296
+ # if imap.capability? "LOGINDISABLED"
1297
+ # raise "Remote server has disabled the login command"
1298
+ # else
1299
+ # imap.login username, password
1300
+ # end
1067
1301
  #
1068
1302
  # Server capabilities may change after #starttls, #login, and #authenticate.
1069
1303
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1072,17 +1306,18 @@ module Net
1072
1306
  #
1073
1307
  def login(user, password)
1074
1308
  send_command("LOGIN", user, password)
1309
+ .tap { @capabilities = capabilities_from_resp_code _1 }
1075
1310
  end
1076
1311
 
1077
1312
  # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
1078
1313
  # to select a +mailbox+ so that messages in the +mailbox+ can be accessed.
1079
1314
  #
1080
1315
  # After you have selected a mailbox, you may retrieve the number of items in
1081
- # that mailbox from <tt>imap.responses["EXISTS"][-1]</tt>, and the number of
1082
- # recent messages from <tt>imap.responses["RECENT"][-1]</tt>. Note that
1083
- # these values can change if new messages arrive during a session or when
1084
- # existing messages are expunged; see #add_response_handler for a way to
1085
- # detect these events.
1316
+ # that mailbox from <tt>imap.responses("EXISTS", &:last)</tt>, and the
1317
+ # number of recent messages from <tt>imap.responses("RECENT", &:last)</tt>.
1318
+ # Note that these values can change if new messages arrive during a session
1319
+ # or when existing messages are expunged; see #add_response_handler for a
1320
+ # way to detect these events.
1086
1321
  #
1087
1322
  # A Net::IMAP::NoResponseError is raised if the mailbox does not
1088
1323
  # exist or is for some reason non-selectable.
@@ -1213,7 +1448,7 @@ module Net
1213
1448
  def list(refname, mailbox)
1214
1449
  synchronize do
1215
1450
  send_command("LIST", refname, mailbox)
1216
- return @responses.delete("LIST")
1451
+ clear_responses("LIST")
1217
1452
  end
1218
1453
  end
1219
1454
 
@@ -1251,8 +1486,7 @@ module Net
1251
1486
  #
1252
1487
  # ===== For example:
1253
1488
  #
1254
- # capabilities = imap.capability
1255
- # if capabilities.include?("NAMESPACE")
1489
+ # if capable?("NAMESPACE")
1256
1490
  # namespaces = imap.namespace
1257
1491
  # if namespace = namespaces.personal.first
1258
1492
  # prefix = namespace.prefix # e.g. "" or "INBOX."
@@ -1271,7 +1505,7 @@ module Net
1271
1505
  def namespace
1272
1506
  synchronize do
1273
1507
  send_command("NAMESPACE")
1274
- return @responses.delete("NAMESPACE")[-1]
1508
+ clear_responses("NAMESPACE").last
1275
1509
  end
1276
1510
  end
1277
1511
 
@@ -1315,7 +1549,7 @@ module Net
1315
1549
  def xlist(refname, mailbox)
1316
1550
  synchronize do
1317
1551
  send_command("XLIST", refname, mailbox)
1318
- return @responses.delete("XLIST")
1552
+ clear_responses("XLIST")
1319
1553
  end
1320
1554
  end
1321
1555
 
@@ -1334,8 +1568,8 @@ module Net
1334
1568
  synchronize do
1335
1569
  send_command("GETQUOTAROOT", mailbox)
1336
1570
  result = []
1337
- result.concat(@responses.delete("QUOTAROOT"))
1338
- result.concat(@responses.delete("QUOTA"))
1571
+ result.concat(clear_responses("QUOTAROOT"))
1572
+ result.concat(clear_responses("QUOTA"))
1339
1573
  return result
1340
1574
  end
1341
1575
  end
@@ -1354,7 +1588,7 @@ module Net
1354
1588
  def getquota(mailbox)
1355
1589
  synchronize do
1356
1590
  send_command("GETQUOTA", mailbox)
1357
- return @responses.delete("QUOTA")
1591
+ clear_responses("QUOTA")
1358
1592
  end
1359
1593
  end
1360
1594
 
@@ -1410,7 +1644,7 @@ module Net
1410
1644
  def getacl(mailbox)
1411
1645
  synchronize do
1412
1646
  send_command("GETACL", mailbox)
1413
- return @responses.delete("ACL")[-1]
1647
+ clear_responses("ACL").last
1414
1648
  end
1415
1649
  end
1416
1650
 
@@ -1425,7 +1659,7 @@ module Net
1425
1659
  def lsub(refname, mailbox)
1426
1660
  synchronize do
1427
1661
  send_command("LSUB", refname, mailbox)
1428
- return @responses.delete("LSUB")
1662
+ clear_responses("LSUB")
1429
1663
  end
1430
1664
  end
1431
1665
 
@@ -1449,7 +1683,7 @@ module Net
1449
1683
  def status(mailbox, attr)
1450
1684
  synchronize do
1451
1685
  send_command("STATUS", mailbox, attr)
1452
- return @responses.delete("STATUS")[-1].attr
1686
+ clear_responses("STATUS").last&.attr
1453
1687
  end
1454
1688
  end
1455
1689
 
@@ -1538,7 +1772,7 @@ module Net
1538
1772
  def expunge
1539
1773
  synchronize do
1540
1774
  send_command("EXPUNGE")
1541
- return @responses.delete("EXPUNGE")
1775
+ clear_responses("EXPUNGE")
1542
1776
  end
1543
1777
  end
1544
1778
 
@@ -1570,7 +1804,7 @@ module Net
1570
1804
  def uid_expunge(uid_set)
1571
1805
  synchronize do
1572
1806
  send_command("UID EXPUNGE", MessageSet.new(uid_set))
1573
- return @responses.delete("EXPUNGE")
1807
+ clear_responses("EXPUNGE")
1574
1808
  end
1575
1809
  end
1576
1810
 
@@ -1589,7 +1823,7 @@ module Net
1589
1823
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
1590
1824
  # in addition to documentation for
1591
1825
  # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
1592
- # reported by #capability which may define additional search filters, e.g:
1826
+ # reported by #capabilities which may define additional search filters, e.g:
1593
1827
  # +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
1594
1828
  # +SAVEDATE+. The following are some common search criteria:
1595
1829
  #
@@ -1656,8 +1890,7 @@ module Net
1656
1890
  # +attr+ is a list of attributes to fetch; see the documentation
1657
1891
  # for FetchData for a list of valid attributes.
1658
1892
  #
1659
- # The return value is an array of FetchData or nil
1660
- # (instead of an empty array) if there is no matching message.
1893
+ # The return value is an array of FetchData.
1661
1894
  #
1662
1895
  # Related: #uid_search, FetchData
1663
1896
  #
@@ -1884,6 +2117,87 @@ module Net
1884
2117
  return thread_internal("UID THREAD", algorithm, search_keys, charset)
1885
2118
  end
1886
2119
 
2120
+ # Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1]
2121
+ # {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1]
2122
+ # to enable the specified server +capabilities+. Each capability may be an
2123
+ # array, string, or symbol. Returns a list of the capabilities that were
2124
+ # enabled.
2125
+ #
2126
+ # The +ENABLE+ command is only valid in the _authenticated_ state, before
2127
+ # any mailbox is selected.
2128
+ #
2129
+ # Related: #capable?, #capabilities, #capability
2130
+ #
2131
+ # ===== Capabilities
2132
+ #
2133
+ # The server's capabilities must include
2134
+ # +ENABLE+ [RFC5161[https://tools.ietf.org/html/rfc5161]]
2135
+ # or +IMAP4REV2+ [RFC9051[https://tools.ietf.org/html/rfc9051]].
2136
+ #
2137
+ # Additionally, the server capabilities must include a capability matching
2138
+ # each enabled extension (usually the same name as the enabled extension).
2139
+ # The following capabilities may be enabled:
2140
+ #
2141
+ # [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
2142
+ #
2143
+ # In a future release, <tt>enable(:utf8)</tt> will enable either
2144
+ # <tt>"UTF8=ACCEPT"</tt> or <tt>"IMAP4rev2"</tt>, depending on server
2145
+ # capabilities.
2146
+ #
2147
+ # [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2148
+ #
2149
+ # The server's capabilities must include <tt>UTF8=ACCEPT</tt> _or_
2150
+ # <tt>UTF8=ONLY</tt>.
2151
+ #
2152
+ # This allows the server to send strings encoded as UTF-8 which might
2153
+ # otherwise need to use a 7-bit encoding, such as {modified
2154
+ # UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for
2155
+ # message headers.
2156
+ #
2157
+ # *Note:* <em>A future update may set string encodings slightly
2158
+ # differently</em>, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8"
2159
+ # when it is. Currently, the encoding of strings sent as "quoted" or
2160
+ # "text" will _always_ be "UTF-8", even when only ASCII characters are
2161
+ # used (e.g. "Subject: Agenda") And currently, string "literals" sent
2162
+ # by the server will always have an "ASCII-8BIT" (binary)
2163
+ # encoding, even if they generally contain UTF-8 data, if they are
2164
+ # text at all.
2165
+ #
2166
+ # [<tt>"UTF8=ONLY"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2167
+ #
2168
+ # A server that reports the <tt>UTF8=ONLY</tt> capability _requires_ that
2169
+ # the client <tt>enable("UTF8=ACCEPT")</tt> before any mailboxes may be
2170
+ # selected. For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
2171
+ # <tt>enable("UTF8=ACCEPT")</tt>.
2172
+ #
2173
+ # ===== Unsupported capabilities
2174
+ #
2175
+ # *Note:* Some extensions that use ENABLE permit the server to send syntax
2176
+ # that Net::IMAP cannot parse, which may raise an exception and disconnect.
2177
+ # Some extensions may work, but the support may be incomplete, untested, or
2178
+ # experimental.
2179
+ #
2180
+ # Until a capability is documented here as supported, enabling it may result
2181
+ # in undocumented behavior and a future release may update with incompatible
2182
+ # behavior <em>without warning or deprecation</em>.
2183
+ #
2184
+ # <em>Caution is advised.</em>
2185
+ #
2186
+ def enable(*capabilities)
2187
+ capabilities = capabilities
2188
+ .flatten
2189
+ .map {|e| ENABLE_ALIASES[e] || e }
2190
+ .uniq
2191
+ .join(' ')
2192
+ synchronize do
2193
+ send_command("ENABLE #{capabilities}")
2194
+ result = clear_responses("ENABLED").last
2195
+ @utf8_strings ||= result.include? "UTF8=ACCEPT"
2196
+ @utf8_strings ||= result.include? "IMAP4REV2"
2197
+ result
2198
+ end
2199
+ end
2200
+
1887
2201
  # Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3]
1888
2202
  # {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13]
1889
2203
  # that waits for notifications of new or expunged messages. Yields
@@ -1948,6 +2262,104 @@ module Net
1948
2262
  end
1949
2263
  end
1950
2264
 
2265
+ # :call-seq:
2266
+ # responses {|hash| ...} -> block result
2267
+ # responses(type) {|array| ...} -> block result
2268
+ #
2269
+ # Yields unhandled responses and returns the result of the block.
2270
+ #
2271
+ # Unhandled responses are stored in a hash, with arrays of
2272
+ # <em>non-+nil+</em> UntaggedResponse#data keyed by UntaggedResponse#name
2273
+ # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
2274
+ # yield the entire responses hash. Call with +type+ to yield only the array
2275
+ # of responses for that type.
2276
+ #
2277
+ # For example:
2278
+ #
2279
+ # imap.select("inbox")
2280
+ # p imap.responses("EXISTS", &:last)
2281
+ # #=> 2
2282
+ # p imap.responses("UIDVALIDITY", &:last)
2283
+ # #=> 968263756
2284
+ #
2285
+ # >>>
2286
+ # *Note:* Access to the responses hash is synchronized for thread-safety.
2287
+ # The receiver thread and response_handlers cannot process new responses
2288
+ # until the block completes. Accessing either the response hash or its
2289
+ # response type arrays outside of the block is unsafe.
2290
+ #
2291
+ # Calling without a block is unsafe and deprecated. Future releases will
2292
+ # raise ArgumentError unless a block is given.
2293
+ #
2294
+ # Previously unhandled responses are automatically cleared before entering a
2295
+ # mailbox with #select or #examine. Long-lived connections can receive many
2296
+ # unhandled server responses, which must be pruned or they will continually
2297
+ # consume more memory. Update or clear the responses hash or arrays inside
2298
+ # the block, or use #clear_responses.
2299
+ #
2300
+ # Only non-+nil+ data is stored. Many important response codes have no data
2301
+ # of their own, but are used as "tags" on the ResponseText object they are
2302
+ # attached to. ResponseText will be accessible by its response types:
2303
+ # "+OK+", "+NO+", "+BAD+", "+BYE+", or "+PREAUTH+".
2304
+ #
2305
+ # TaggedResponse#data is not saved to #responses, nor is any
2306
+ # ResponseCode#data on tagged responses. Although some command methods do
2307
+ # return the TaggedResponse directly, #add_response_handler must be used to
2308
+ # handle all response codes.
2309
+ #
2310
+ # Related: #clear_responses, #response_handlers, #greeting
2311
+ def responses(type = nil)
2312
+ if block_given?
2313
+ synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
2314
+ elsif type
2315
+ raise ArgumentError, "Pass a block or use #clear_responses"
2316
+ else
2317
+ # warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1)
2318
+ @responses
2319
+ end
2320
+ end
2321
+
2322
+ # :call-seq:
2323
+ # clear_responses -> hash
2324
+ # clear_responses(type) -> array
2325
+ #
2326
+ # Clears and returns the unhandled #responses hash or the unhandled
2327
+ # responses array for a single response +type+.
2328
+ #
2329
+ # Clearing responses is synchronized with other threads. The lock is
2330
+ # released before returning.
2331
+ #
2332
+ # Related: #responses, #response_handlers
2333
+ def clear_responses(type = nil)
2334
+ synchronize {
2335
+ if type
2336
+ @responses.delete(type) || []
2337
+ else
2338
+ @responses.dup.transform_values(&:freeze)
2339
+ .tap { _1.default = [].freeze }
2340
+ .tap { @responses.clear }
2341
+ end
2342
+ }
2343
+ .freeze
2344
+ end
2345
+
2346
+ # Returns all response handlers, including those that are added internally
2347
+ # by commands. Each response handler will be called with every new
2348
+ # UntaggedResponse, TaggedResponse, and ContinuationRequest.
2349
+ #
2350
+ # Response handlers are called with a mutex inside the receiver thread. New
2351
+ # responses cannot be processed and commands from other threads must wait
2352
+ # until all response_handlers return. An exception will shut-down the
2353
+ # receiver thread and close the connection.
2354
+ #
2355
+ # For thread-safety, the returned array is a frozen copy of the internal
2356
+ # array.
2357
+ #
2358
+ # Related: #add_response_handler, #remove_response_handler
2359
+ def response_handlers
2360
+ synchronize { @response_handlers.clone.freeze }
2361
+ end
2362
+
1951
2363
  # Adds a response handler. For example, to detect when
1952
2364
  # the server sends a new EXISTS response (which normally
1953
2365
  # indicates new messages being added to the mailbox),
@@ -1960,14 +2372,21 @@ module Net
1960
2372
  # end
1961
2373
  # }
1962
2374
  #
2375
+ # Related: #remove_response_handler, #response_handlers
1963
2376
  def add_response_handler(handler = nil, &block)
1964
2377
  raise ArgumentError, "two Procs are passed" if handler && block
1965
- @response_handlers.push(block || handler)
2378
+ synchronize do
2379
+ @response_handlers.push(block || handler)
2380
+ end
1966
2381
  end
1967
2382
 
1968
2383
  # Removes the response handler.
2384
+ #
2385
+ # Related: #add_response_handler, #response_handlers
1969
2386
  def remove_response_handler(handler)
1970
- @response_handlers.delete(handler)
2387
+ synchronize do
2388
+ @response_handlers.delete(handler)
2389
+ end
1971
2390
  end
1972
2391
 
1973
2392
  private
@@ -1978,93 +2397,29 @@ module Net
1978
2397
 
1979
2398
  @@debug = false
1980
2399
 
1981
- # :call-seq:
1982
- # Net::IMAP.new(host, options = {})
1983
- #
1984
- # Creates a new Net::IMAP object and connects it to the specified
1985
- # +host+.
1986
- #
1987
- # +options+ is an option hash, each key of which is a symbol.
1988
- #
1989
- # The available options are:
1990
- #
1991
- # port:: Port number (default value is 143 for imap, or 993 for imaps)
1992
- # ssl:: If +options[:ssl]+ is true, then an attempt will be made
1993
- # to use SSL (now TLS) to connect to the server.
1994
- # If +options[:ssl]+ is a hash, it's passed to
1995
- # OpenSSL::SSL::SSLContext#set_params as parameters.
1996
- # open_timeout:: Seconds to wait until a connection is opened
1997
- # idle_response_timeout:: Seconds to wait until an IDLE response is received
1998
- #
1999
- # The most common errors are:
2000
- #
2001
- # Errno::ECONNREFUSED:: Connection refused by +host+ or an intervening
2002
- # firewall.
2003
- # Errno::ETIMEDOUT:: Connection timed out (possibly due to packets
2004
- # being dropped by an intervening firewall).
2005
- # Errno::ENETUNREACH:: There is no route to that network.
2006
- # SocketError:: Hostname not known or other socket error.
2007
- # Net::IMAP::ByeResponseError:: The connected to the host was successful, but
2008
- # it immediately said goodbye.
2009
- def initialize(host, port_or_options = {},
2010
- usessl = false, certs = nil, verify = true)
2011
- super()
2012
- @host = host
2013
- begin
2014
- options = port_or_options.to_hash
2015
- rescue NoMethodError
2016
- # for backward compatibility
2017
- options = {}
2018
- options[:port] = port_or_options
2019
- if usessl
2020
- options[:ssl] = create_ssl_params(certs, verify)
2021
- end
2022
- end
2023
- @port = options[:port] || (options[:ssl] ? SSL_PORT : PORT)
2024
- @tag_prefix = "RUBY"
2025
- @tagno = 0
2026
- @open_timeout = options[:open_timeout] || 30
2027
- @idle_response_timeout = options[:idle_response_timeout] || 5
2028
- @parser = ResponseParser.new
2029
- @sock = tcp_socket(@host, @port)
2030
- begin
2031
- if options[:ssl]
2032
- start_tls_session(options[:ssl])
2033
- @usessl = true
2034
- else
2035
- @usessl = false
2036
- end
2037
- @responses = Hash.new([].freeze)
2038
- @tagged_responses = {}
2039
- @response_handlers = []
2040
- @tagged_response_arrival = new_cond
2041
- @continued_command_tag = nil
2042
- @continuation_request_arrival = new_cond
2043
- @continuation_request_exception = nil
2044
- @idle_done_cond = nil
2045
- @logout_command_tag = nil
2046
- @debug_output_bol = true
2047
- @exception = nil
2048
-
2049
- @greeting = get_response
2050
- if @greeting.nil?
2051
- raise Error, "connection closed"
2052
- end
2053
- if @greeting.name == "BYE"
2054
- raise ByeResponseError, @greeting
2055
- end
2056
-
2057
- @client_thread = Thread.current
2058
- @receiver_thread = Thread.start {
2059
- begin
2060
- receive_responses
2061
- rescue Exception
2062
- end
2063
- }
2064
- @receiver_thread_terminating = false
2065
- rescue Exception
2066
- @sock.close
2067
- raise
2400
+ def start_imap_connection
2401
+ @greeting = get_server_greeting
2402
+ @capabilities = capabilities_from_resp_code @greeting
2403
+ @receiver_thread = start_receiver_thread
2404
+ rescue Exception
2405
+ @sock.close
2406
+ raise
2407
+ end
2408
+
2409
+ def get_server_greeting
2410
+ greeting = get_response
2411
+ raise Error, "No server greeting - connection closed" unless greeting
2412
+ record_untagged_response_code greeting
2413
+ raise ByeResponseError, greeting if greeting.name == "BYE"
2414
+ greeting
2415
+ end
2416
+
2417
+ def start_receiver_thread
2418
+ Thread.start do
2419
+ receive_responses
2420
+ rescue Exception => ex
2421
+ @receiver_thread_exception = ex
2422
+ # don't exit the thread with an exception
2068
2423
  end
2069
2424
  end
2070
2425
 
@@ -2113,11 +2468,7 @@ module Net
2113
2468
  @continuation_request_arrival.signal
2114
2469
  end
2115
2470
  when UntaggedResponse
2116
- record_response(resp.name, resp.data)
2117
- if resp.data.instance_of?(ResponseText) &&
2118
- (code = resp.data.code)
2119
- record_response(code.name, code.data)
2120
- end
2471
+ record_untagged_response(resp)
2121
2472
  if resp.name == "BYE" && @logout_command_tag.nil?
2122
2473
  @sock.close
2123
2474
  @exception = ByeResponseError.new(resp)
@@ -2195,11 +2546,42 @@ module Net
2195
2546
  return @parser.parse(buff)
2196
2547
  end
2197
2548
 
2198
- def record_response(name, data)
2199
- unless @responses.has_key?(name)
2200
- @responses[name] = []
2549
+ #############################
2550
+ # built-in response handlers
2551
+
2552
+ # store name => [..., data]
2553
+ def record_untagged_response(resp)
2554
+ @responses[resp.name] << resp.data
2555
+ record_untagged_response_code resp
2556
+ end
2557
+
2558
+ # store code.name => [..., code.data]
2559
+ def record_untagged_response_code(resp)
2560
+ return unless resp.data.is_a?(ResponseText)
2561
+ return unless (code = resp.data.code)
2562
+ @responses[code.name] << code.data
2563
+ end
2564
+
2565
+ # NOTE: only call this for greeting, login, and authenticate
2566
+ def capabilities_from_resp_code(resp)
2567
+ return unless %w[PREAUTH OK].any? { _1.casecmp? resp.name }
2568
+ return unless (code = resp.data.code)
2569
+ return unless code.name.casecmp?("CAPABILITY")
2570
+ code.data.freeze
2571
+ end
2572
+
2573
+ #############################
2574
+
2575
+ # Calls send_command, yielding the text of each ContinuationRequest and
2576
+ # responding with each block result. Returns TaggedResponse. Raises
2577
+ # NoResponseError or BadResponseError.
2578
+ def send_command_with_continuations(cmd, *args)
2579
+ send_command(cmd, *args) do |server_response|
2580
+ if server_response.instance_of?(ContinuationRequest)
2581
+ client_response = yield server_response.data.text
2582
+ put_string(client_response + CRLF)
2583
+ end
2201
2584
  end
2202
- @responses[name].push(data)
2203
2585
  end
2204
2586
 
2205
2587
  def send_command(cmd, *args, &block)
@@ -2241,8 +2623,8 @@ module Net
2241
2623
  if @debug_output_bol
2242
2624
  $stderr.print("C: ")
2243
2625
  end
2244
- $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
2245
- if /\r\n\z/n.match(str)
2626
+ $stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " })
2627
+ if /\n\z/n.match(str)
2246
2628
  @debug_output_bol = true
2247
2629
  else
2248
2630
  @debug_output_bol = false
@@ -2262,7 +2644,7 @@ module Net
2262
2644
  else
2263
2645
  send_command(cmd, *keys)
2264
2646
  end
2265
- return @responses.delete("SEARCH")[-1]
2647
+ clear_responses("SEARCH").last
2266
2648
  end
2267
2649
  end
2268
2650
 
@@ -2277,13 +2659,13 @@ module Net
2277
2659
  end
2278
2660
 
2279
2661
  synchronize do
2280
- @responses.delete("FETCH")
2662
+ clear_responses("FETCH")
2281
2663
  if mod
2282
2664
  send_command(cmd, MessageSet.new(set), attr, mod)
2283
2665
  else
2284
2666
  send_command(cmd, MessageSet.new(set), attr)
2285
2667
  end
2286
- return @responses.delete("FETCH")
2668
+ clear_responses("FETCH")
2287
2669
  end
2288
2670
  end
2289
2671
 
@@ -2292,9 +2674,9 @@ module Net
2292
2674
  attr = RawData.new(attr)
2293
2675
  end
2294
2676
  synchronize do
2295
- @responses.delete("FETCH")
2677
+ clear_responses("FETCH")
2296
2678
  send_command(cmd, MessageSet.new(set), attr, flags)
2297
- return @responses.delete("FETCH")
2679
+ clear_responses("FETCH")
2298
2680
  end
2299
2681
  end
2300
2682
 
@@ -2311,7 +2693,7 @@ module Net
2311
2693
  normalize_searching_criteria(search_keys)
2312
2694
  synchronize do
2313
2695
  send_command(cmd, sort_keys, charset, *search_keys)
2314
- return @responses.delete("SORT")[-1]
2696
+ clear_responses("SORT").last
2315
2697
  end
2316
2698
  end
2317
2699
 
@@ -2322,8 +2704,10 @@ module Net
2322
2704
  normalize_searching_criteria(search_keys)
2323
2705
  end
2324
2706
  normalize_searching_criteria(search_keys)
2325
- send_command(cmd, algorithm, charset, *search_keys)
2326
- return @responses.delete("THREAD")[-1]
2707
+ synchronize do
2708
+ send_command(cmd, algorithm, charset, *search_keys)
2709
+ clear_responses("THREAD").last
2710
+ end
2327
2711
  end
2328
2712
 
2329
2713
  def normalize_searching_criteria(keys)
@@ -2337,49 +2721,49 @@ module Net
2337
2721
  end
2338
2722
  end
2339
2723
 
2340
- def create_ssl_params(certs = nil, verify = true)
2341
- params = {}
2342
- if certs
2343
- if File.file?(certs)
2344
- params[:ca_file] = certs
2345
- elsif File.directory?(certs)
2346
- params[:ca_path] = certs
2724
+ def build_ssl_ctx(ssl)
2725
+ if ssl
2726
+ params = (Hash.try_convert(ssl) || {}).freeze
2727
+ context = SSLContext.new
2728
+ context.set_params(params)
2729
+ if defined?(VerifyCallbackProc)
2730
+ context.verify_callback = VerifyCallbackProc
2347
2731
  end
2348
- end
2349
- if verify
2350
- params[:verify_mode] = VERIFY_PEER
2732
+ context.freeze
2733
+ [params, context]
2351
2734
  else
2352
- params[:verify_mode] = VERIFY_NONE
2735
+ false
2353
2736
  end
2354
- return params
2355
2737
  end
2356
2738
 
2357
- def start_tls_session(params = {})
2358
- unless defined?(OpenSSL::SSL)
2359
- raise "SSL extension not installed"
2360
- end
2361
- if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
2362
- raise RuntimeError, "already using SSL"
2363
- end
2364
- begin
2365
- params = params.to_hash
2366
- rescue NoMethodError
2367
- params = {}
2368
- end
2369
- context = SSLContext.new
2370
- context.set_params(params)
2371
- if defined?(VerifyCallbackProc)
2372
- context.verify_callback = VerifyCallbackProc
2373
- end
2374
- @sock = SSLSocket.new(@sock, context)
2739
+ def start_tls_session
2740
+ raise "SSL extension not installed" unless defined?(OpenSSL::SSL)
2741
+ raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
2742
+ raise "cannot start TLS without SSLContext" unless ssl_ctx
2743
+ @sock = SSLSocket.new(@sock, ssl_ctx)
2375
2744
  @sock.sync_close = true
2376
2745
  @sock.hostname = @host if @sock.respond_to? :hostname=
2377
2746
  ssl_socket_connect(@sock, @open_timeout)
2378
- if context.verify_mode != VERIFY_NONE
2747
+ if ssl_ctx.verify_mode != VERIFY_NONE
2379
2748
  @sock.post_connection_check(@host)
2749
+ @tls_verified = true
2380
2750
  end
2381
2751
  end
2382
2752
 
2753
+ def sasl_adapter
2754
+ SASLAdapter.new(self, &method(:send_command_with_continuations))
2755
+ end
2756
+
2757
+ #--
2758
+ # We could get the saslprep method by extending the SASLprep module
2759
+ # directly. It's done indirectly, so SASLprep can be lazily autoloaded,
2760
+ # because most users won't need it.
2761
+ #++
2762
+ # Delegates to Net::IMAP::StringPrep::SASLprep#saslprep.
2763
+ def self.saslprep(string, **opts)
2764
+ Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
2765
+ end
2766
+
2383
2767
  end
2384
2768
  end
2385
2769
 
@@ -2390,4 +2774,6 @@ require_relative "imap/flags"
2390
2774
  require_relative "imap/response_data"
2391
2775
  require_relative "imap/response_parser"
2392
2776
  require_relative "imap/authenticators"
2393
- require_relative "imap/sasl"
2777
+
2778
+ require_relative "imap/deprecated_client_options"
2779
+ Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions