net-imap 0.3.9 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/COPYING +56 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +3 -22
  6. data/README.md +25 -8
  7. data/Rakefile +0 -7
  8. data/docs/styles.css +72 -23
  9. data/lib/net/imap/authenticators.rb +26 -57
  10. data/lib/net/imap/command_data.rb +74 -54
  11. data/lib/net/imap/config/attr_accessors.rb +75 -0
  12. data/lib/net/imap/config/attr_inheritance.rb +90 -0
  13. data/lib/net/imap/config/attr_type_coercion.rb +62 -0
  14. data/lib/net/imap/config.rb +552 -0
  15. data/lib/net/imap/connection_state.rb +48 -0
  16. data/lib/net/imap/data_encoding.rb +18 -6
  17. data/lib/net/imap/data_lite.rb +226 -0
  18. data/lib/net/imap/deprecated_client_options.rb +142 -0
  19. data/lib/net/imap/errors.rb +28 -3
  20. data/lib/net/imap/esearch_result.rb +180 -0
  21. data/lib/net/imap/fetch_data.rb +597 -0
  22. data/lib/net/imap/flags.rb +1 -1
  23. data/lib/net/imap/response_data.rb +250 -440
  24. data/lib/net/imap/response_parser/parser_utils.rb +245 -0
  25. data/lib/net/imap/response_parser.rb +1873 -1210
  26. data/lib/net/imap/response_reader.rb +10 -12
  27. data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
  28. data/lib/net/imap/sasl/authentication_exchange.rb +139 -0
  29. data/lib/net/imap/sasl/authenticators.rb +122 -0
  30. data/lib/net/imap/sasl/client_adapter.rb +123 -0
  31. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +24 -14
  32. data/lib/net/imap/sasl/digest_md5_authenticator.rb +342 -0
  33. data/lib/net/imap/sasl/external_authenticator.rb +83 -0
  34. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  35. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +28 -18
  36. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
  37. data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
  38. data/lib/net/imap/sasl/protocol_adapters.rb +101 -0
  39. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  40. data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
  41. data/lib/net/imap/sasl/stringprep.rb +6 -66
  42. data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
  43. data/lib/net/imap/sasl.rb +148 -44
  44. data/lib/net/imap/sasl_adapter.rb +20 -0
  45. data/lib/net/imap/search_result.rb +146 -0
  46. data/lib/net/imap/sequence_set.rb +1721 -0
  47. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  48. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  49. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  50. data/lib/net/imap/stringprep/tables.rb +146 -0
  51. data/lib/net/imap/stringprep/trace.rb +85 -0
  52. data/lib/net/imap/stringprep.rb +159 -0
  53. data/lib/net/imap/uidplus_data.rb +244 -0
  54. data/lib/net/imap/vanished_data.rb +56 -0
  55. data/lib/net/imap.rb +2217 -861
  56. data/net-imap.gemspec +7 -8
  57. data/rakelib/benchmarks.rake +91 -0
  58. data/rakelib/rfcs.rake +2 -0
  59. data/rakelib/saslprep.rake +4 -4
  60. data/rakelib/string_prep_tables_generator.rb +84 -60
  61. data/sample/net-imap.rb +167 -0
  62. metadata +45 -45
  63. data/.github/dependabot.yml +0 -6
  64. data/.github/workflows/test.yml +0 -38
  65. data/.gitignore +0 -10
  66. data/benchmarks/stringprep.yml +0 -65
  67. data/benchmarks/table-regexps.yml +0 -39
  68. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  69. data/lib/net/imap/authenticators/plain.rb +0 -41
  70. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  71. data/lib/net/imap/sasl/saslprep.rb +0 -55
  72. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  73. 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://www.rfc-editor.org/rfc/rfc3501]
29
+ # and {IMAP4rev2 [RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051].
32
30
  #
33
31
  # == \IMAP Overview
34
32
  #
@@ -55,6 +53,8 @@ module Net
55
53
  # states: <tt>not authenticated</tt>, +authenticated+, +selected+, and
56
54
  # +logout+. Most commands are valid only in certain states.
57
55
  #
56
+ # See #connection_state.
57
+ #
58
58
  # === Sequence numbers and UIDs
59
59
  #
60
60
  # Messages have two sorts of identifiers: message sequence
@@ -83,31 +83,22 @@ module Net
83
83
  # UIDs have to be reassigned. An \IMAP client thus cannot
84
84
  # rearrange message orders.
85
85
  #
86
- # === Server capabilities and protocol extensions
87
- #
88
- # Net::IMAP <em>does not modify its behavior</em> according to server
89
- # #capability. Users of the class must check for required capabilities before
90
- # issuing commands. Special care should be taken to follow all #capability
91
- # requirements for #starttls, #login, and #authenticate.
92
- #
93
- # See the #capability method for more information.
86
+ # === Examples of Usage
94
87
  #
95
- # == Examples of Usage
96
- #
97
- # === List sender and subject of all recent messages in the default mailbox
88
+ # ==== List sender and subject of all recent messages in the default mailbox
98
89
  #
99
90
  # imap = Net::IMAP.new('mail.example.com')
100
- # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
91
+ # imap.authenticate('PLAIN', 'joe_user', 'joes_password')
101
92
  # imap.examine('INBOX')
102
93
  # imap.search(["RECENT"]).each do |message_id|
103
94
  # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
104
95
  # puts "#{envelope.from[0].name}: \t#{envelope.subject}"
105
96
  # end
106
97
  #
107
- # === Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
98
+ # ==== Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
108
99
  #
109
100
  # imap = Net::IMAP.new('mail.example.com')
110
- # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
101
+ # imap.authenticate('PLAIN', 'joe_user', 'joes_password')
111
102
  # imap.select('Mail/sent-mail')
112
103
  # if not imap.list('Mail/', 'sent-apr03')
113
104
  # imap.create('Mail/sent-apr03')
@@ -118,12 +109,96 @@ module Net
118
109
  # end
119
110
  # imap.expunge
120
111
  #
112
+ # == Capabilities
113
+ #
114
+ # Most Net::IMAP methods do not _currently_ modify their behaviour according
115
+ # to the server's advertised #capabilities. Users of this class must check
116
+ # that the server is capable of extension commands or command arguments before
117
+ # sending them. Special care should be taken to follow the #capabilities
118
+ # requirements for #starttls, #login, and #authenticate.
119
+ #
120
+ # See #capable?, #auth_capable?, #capabilities, #auth_mechanisms to discover
121
+ # server capabilities. For relevant capability requirements, see the
122
+ # documentation on each \IMAP command.
123
+ #
124
+ # imap = Net::IMAP.new("mail.example.com")
125
+ # imap.capable?(:IMAP4rev1) or raise "Not an IMAP4rev1 server"
126
+ # imap.capable?(:starttls) or raise "Cannot start TLS"
127
+ # imap.starttls
128
+ #
129
+ # if imap.auth_capable?("PLAIN")
130
+ # imap.authenticate "PLAIN", username, password
131
+ # elsif !imap.capability?("LOGINDISABLED")
132
+ # imap.login username, password
133
+ # else
134
+ # raise "No acceptable authentication mechanisms"
135
+ # end
136
+ #
137
+ # # Support for "UTF8=ACCEPT" implies support for "ENABLE"
138
+ # imap.enable :utf8 if imap.capable?("UTF8=ACCEPT")
139
+ #
140
+ # namespaces = imap.namespace if imap.capable?(:namespace)
141
+ # mbox_prefix = namespaces&.personal&.first&.prefix || ""
142
+ # mbox_delim = namespaces&.personal&.first&.delim || "/"
143
+ # mbox_path = prefix + %w[path to my mailbox].join(delim)
144
+ # imap.create mbox_path
145
+ #
146
+ # === Basic IMAP4rev1 capabilities
147
+ #
148
+ # IMAP4rev1 servers must advertise +IMAP4rev1+ in their capabilities list.
149
+ # IMAP4rev1 servers must _implement_ the +STARTTLS+, <tt>AUTH=PLAIN</tt>,
150
+ # and +LOGINDISABLED+ capabilities. See #starttls, #login, and #authenticate
151
+ # for the implications of these capabilities.
152
+ #
153
+ # === Caching +CAPABILITY+ responses
154
+ #
155
+ # Net::IMAP automatically stores and discards capability data according to the
156
+ # the requirements and recommendations in
157
+ # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
158
+ # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
159
+ # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
160
+ # Use #capable?, #auth_capable?, or #capabilities to use this cache and avoid
161
+ # sending the #capability command unnecessarily.
162
+ #
163
+ # The server may advertise its initial capabilities using the +CAPABILITY+
164
+ # ResponseCode in a +PREAUTH+ or +OK+ #greeting. When TLS has started
165
+ # (#starttls) and after authentication (#login or #authenticate), the server's
166
+ # capabilities may change and cached capabilities are discarded. The server
167
+ # may send updated capabilities with an +OK+ TaggedResponse to #login or
168
+ # #authenticate, and these will be cached by Net::IMAP. But the
169
+ # TaggedResponse to #starttls MUST be ignored--it is sent before TLS starts
170
+ # and is unprotected.
171
+ #
172
+ # When storing capability values to variables, be careful that they are
173
+ # discarded or reset appropriately, especially following #starttls.
174
+ #
175
+ # === Using IMAP4rev1 extensions
176
+ #
177
+ # See the {IANA IMAP4 capabilities
178
+ # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list of
179
+ # all standard capabilities, and their reference RFCs.
180
+ #
181
+ # IMAP4rev1 servers must not activate behavior that is incompatible with the
182
+ # base specification until an explicit client action invokes a capability,
183
+ # e.g. sending a command or command argument specific to that capability.
184
+ # Servers may send data with backward compatible behavior, such as response
185
+ # codes or mailbox attributes, at any time without client action.
186
+ #
187
+ # Invoking capabilities which are unknown to Net::IMAP may cause unexpected
188
+ # behavior and errors. For example, ResponseParseError is raised when
189
+ # unknown response syntax is received. Invoking commands or command
190
+ # parameters that are unsupported by the server may raise NoResponseError,
191
+ # BadResponseError, or cause other unexpected behavior.
192
+ #
193
+ # Some capabilities must be explicitly activated using the #enable command.
194
+ # See #enable for details.
195
+ #
121
196
  # == Thread Safety
122
197
  #
123
198
  # Net::IMAP supports concurrent threads. For example,
124
199
  #
125
200
  # imap = Net::IMAP.new("imap.foo.net", "imap2")
126
- # imap.authenticate("cram-md5", "bar", "password")
201
+ # imap.authenticate("scram-md5", "bar", "password")
127
202
  # imap.select("inbox")
128
203
  # fetch_thread = Thread.start { imap.fetch(1..-1, "UID") }
129
204
  # search_result = imap.search(["BODY", "hello"])
@@ -159,13 +234,14 @@ module Net
159
234
  #
160
235
  # Use paginated or limited versions of commands whenever possible.
161
236
  #
162
- # Use #max_response_size to impose a limit on incoming server responses
237
+ # Use Config#max_response_size to impose a limit on incoming server responses
163
238
  # as they are being read. <em>This is especially important for untrusted
164
239
  # servers.</em>
165
240
  #
166
241
  # Use #add_response_handler to handle responses after each one is received.
167
242
  # Use the +response_handlers+ argument to ::new to assign response handlers
168
- # before the receiver thread is started.
243
+ # before the receiver thread is started. Use #extract_responses,
244
+ # #clear_responses, or #responses (with a block) to prune responses.
169
245
  #
170
246
  # == Errors
171
247
  #
@@ -214,66 +290,81 @@ module Net
214
290
  # == What's here?
215
291
  #
216
292
  # * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods]
217
- # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
218
- # * {...for any state}[rdoc-ref:Net::IMAP@IMAP+commands+for+any+state]
219
- # * {...for the "not authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Not+Authenticated-22+state]
220
- # * {...for the "authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Authenticated-22+state]
221
- # * {...for the "selected" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Selected-22+state]
222
- # * {...for the "logout" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Logout-22+state]
223
- # * {Supported IMAP extensions}[rdoc-ref:Net::IMAP@Supported+IMAP+extensions]
293
+ # * {Server capabilities}[rdoc-ref:Net::IMAP@Server+capabilities]
224
294
  # * {Handling server responses}[rdoc-ref:Net::IMAP@Handling+server+responses]
295
+ # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
296
+ # * {for any state}[rdoc-ref:Net::IMAP@Any+state]
297
+ # * {for the "not authenticated" state}[rdoc-ref:Net::IMAP@Not+Authenticated+state]
298
+ # * {for the "authenticated" state}[rdoc-ref:Net::IMAP@Authenticated+state]
299
+ # * {for the "selected" state}[rdoc-ref:Net::IMAP@Selected+state]
300
+ # * {for the "logout" state}[rdoc-ref:Net::IMAP@Logout+state]
301
+ # * {IMAP extension support}[rdoc-ref:Net::IMAP@IMAP+extension+support]
225
302
  #
226
303
  # === Connection control methods
227
304
  #
228
- # - Net::IMAP.new: A new client connects immediately and waits for a
229
- # successful server greeting before returning the new client object.
305
+ # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
306
+ # waits for a successful server greeting before the method returns.
307
+ # - #connection_state: Returns the connection state.
230
308
  # - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
231
309
  # - #logout: Tells the server to end the session. Enters the +logout+ state.
232
310
  # - #disconnect: Disconnects the connection (without sending #logout first).
233
311
  # - #disconnected?: True if the connection has been closed.
234
312
  #
313
+ # === Server capabilities
314
+ #
315
+ # - #capable?: Returns whether the server supports a given capability.
316
+ # - #capabilities: Returns the server's capabilities as an array of strings.
317
+ # - #auth_capable?: Returns whether the server advertises support for a given
318
+ # SASL mechanism, for use with #authenticate.
319
+ # - #auth_mechanisms: Returns the #authenticate SASL mechanisms which
320
+ # the server claims to support as an array of strings.
321
+ # - #clear_cached_capabilities: Clears cached capabilities.
322
+ #
323
+ # <em>The capabilities cache is automatically cleared after completing
324
+ # #starttls, #login, or #authenticate.</em>
325
+ # - #capability: Sends the +CAPABILITY+ command and returns the #capabilities.
326
+ #
327
+ # <em>In general, #capable? should be used rather than explicitly sending a
328
+ # +CAPABILITY+ command to the server.</em>
329
+ #
330
+ # === Handling server responses
331
+ #
332
+ # - #greeting: The server's initial untagged response, which can indicate a
333
+ # pre-authenticated connection.
334
+ # - #responses: Yields unhandled UntaggedResponse#data and <em>non-+nil+</em>
335
+ # ResponseCode#data.
336
+ # - #extract_responses: Removes and returns the responses for which the block
337
+ # returns a true value.
338
+ # - #clear_responses: Deletes unhandled data from #responses and returns it.
339
+ # - #add_response_handler: Add a block to be called inside the receiver thread
340
+ # with every server response.
341
+ # - #response_handlers: Returns the list of response handlers.
342
+ # - #remove_response_handler: Remove a previously added response handler.
343
+ #
235
344
  # === Core \IMAP commands
236
345
  #
237
346
  # The following commands are defined either by
238
- # the [IMAP4rev1[https://tools.ietf.org/html/rfc3501]] base specification, or
347
+ # the [IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501]] base specification, or
239
348
  # by one of the following extensions:
240
- # [IDLE[https://tools.ietf.org/html/rfc2177]],
241
- # [NAMESPACE[https://tools.ietf.org/html/rfc2342]],
242
- # [UNSELECT[https://tools.ietf.org/html/rfc3691]],
243
- #--
244
- # TODO: [ENABLE[https://tools.ietf.org/html/rfc5161]],
245
- # TODO: [LIST-EXTENDED[https://tools.ietf.org/html/rfc5258]],
246
- # TODO: [LIST-STATUS[https://tools.ietf.org/html/rfc5819]],
247
- #++
248
- # [MOVE[https://tools.ietf.org/html/rfc6851]].
349
+ # [IDLE[https://www.rfc-editor.org/rfc/rfc2177]],
350
+ # [NAMESPACE[https://www.rfc-editor.org/rfc/rfc2342]],
351
+ # [UNSELECT[https://www.rfc-editor.org/rfc/rfc3691]],
352
+ # [ENABLE[https://www.rfc-editor.org/rfc/rfc5161]],
353
+ # [MOVE[https://www.rfc-editor.org/rfc/rfc6851]].
249
354
  # These extensions are widely supported by modern IMAP4rev1 servers and have
250
- # all been integrated into [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
251
- # <em>Note: Net::IMAP doesn't fully support IMAP4rev2 yet.</em>
252
- #
253
- #--
254
- # TODO: When IMAP4rev2 is supported, add the following to the each of the
255
- # appropriate commands below.
256
- # Note:: CHECK has been removed from IMAP4rev2.
257
- # Note:: LSUB is obsoleted by +LIST-EXTENDED and has been removed from IMAP4rev2.
258
- # <em>Some arguments require the +LIST-EXTENDED+ or +IMAP4rev2+ capability.</em>
259
- # <em>Requires either the +ENABLE+ or +IMAP4rev2+ capability.</em>
260
- # <em>Requires either the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
261
- # <em>Requires either the +IDLE+ or +IMAP4rev2+ capability.</em>
262
- # <em>Requires either the +UNSELECT+ or +IMAP4rev2+ capability.</em>
263
- # <em>Requires either the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
264
- # <em>Requires either the +MOVE+ or +IMAP4rev2+ capability.</em>
265
- #++
266
- #
267
- # ==== \IMAP commands for any state
355
+ # all been integrated into [IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]].
356
+ # <em>*NOTE:* Net::IMAP doesn't support IMAP4rev2 yet.</em>
357
+ #
358
+ # ==== Any state
268
359
  #
269
360
  # - #capability: Returns the server's capabilities as an array of strings.
270
361
  #
271
- # <em>Capabilities may change after</em> #starttls, #authenticate, or #login
272
- # <em>and cached capabilities must be reloaded.</em>
362
+ # <em>In general, #capable? should be used rather than explicitly sending a
363
+ # +CAPABILITY+ command to the server.</em>
273
364
  # - #noop: Allows the server to send unsolicited untagged #responses.
274
365
  # - #logout: Tells the server to end the session. Enters the +logout+ state.
275
366
  #
276
- # ==== \IMAP commands for the "Not Authenticated" state
367
+ # ==== Not Authenticated state
277
368
  #
278
369
  # In addition to the commands for any state, the following commands are valid
279
370
  # in the +not_authenticated+ state:
@@ -281,27 +372,24 @@ module Net
281
372
  # - #starttls: Upgrades a clear-text connection to use TLS.
282
373
  #
283
374
  # <em>Requires the +STARTTLS+ capability.</em>
284
- # - #authenticate: Identifies the client to the server using the given {SASL
285
- # mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
375
+ # - #authenticate: Identifies the client to the server using the given
376
+ # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
286
377
  # and credentials. Enters the +authenticated+ state.
287
378
  #
288
- # <em>Requires the <tt>AUTH=#{mechanism}</tt> capability for the chosen
289
- # mechanism.</em>
379
+ # <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
380
+ # supported mechanisms.</em>
290
381
  # - #login: Identifies the client to the server using a plain text password.
291
382
  # Using #authenticate is preferred. Enters the +authenticated+ state.
292
383
  #
293
384
  # <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
294
385
  #
295
- # ==== \IMAP commands for the "Authenticated" state
386
+ # ==== Authenticated state
296
387
  #
297
388
  # In addition to the commands for any state, the following commands are valid
298
389
  # in the +authenticated+ state:
299
390
  #
300
- #--
301
- # - #enable: <em>Not implemented by Net::IMAP, yet.</em>
302
- #
303
- # <em>Requires the +ENABLE+ capability.</em>
304
- #++
391
+ # - #enable: Enables backwards incompatible server extensions.
392
+ # <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
305
393
  # - #select: Open a mailbox and enter the +selected+ state.
306
394
  # - #examine: Open a mailbox read-only, and enter the +selected+ state.
307
395
  # - #create: Creates a new mailbox.
@@ -311,23 +399,19 @@ module Net
311
399
  # - #unsubscribe: Removes a mailbox from the "subscribed" set.
312
400
  # - #list: Returns names and attributes of mailboxes matching a given pattern.
313
401
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
314
- #
315
- # <em>Requires the +NAMESPACE+ capability.</em>
402
+ # <em>Requires the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
316
403
  # - #status: Returns mailbox information, e.g. message count, unseen message
317
404
  # count, +UIDVALIDITY+ and +UIDNEXT+.
318
405
  # - #append: Appends a message to the end of a mailbox.
319
406
  # - #idle: Allows the server to send updates to the client, without the client
320
407
  # needing to poll using #noop.
408
+ # <em>Requires the +IDLE+ or +IMAP4rev2+ capability.</em>
409
+ # - *Obsolete* #lsub: <em>Replaced by <tt>LIST-EXTENDED</tt> and removed from
410
+ # +IMAP4rev2+.</em> Lists mailboxes in the "subscribed" set.
321
411
  #
322
- # <em>Requires the +IDLE+ capability.</em>
323
- # - #lsub: Lists mailboxes the user has declared "active" or "subscribed".
324
- #--
325
- # <em>Replaced by</em> <tt>LIST-EXTENDED</tt> <em>and removed from</em>
326
- # +IMAP4rev2+. <em>However, Net::IMAP hasn't implemented</em>
327
- # <tt>LIST-EXTENDED</tt> _yet_.
328
- #++
412
+ # <em>*Note:* Net::IMAP hasn't implemented <tt>LIST-EXTENDED</tt> yet.</em>
329
413
  #
330
- # ==== \IMAP commands for the "Selected" state
414
+ # ==== Selected state
331
415
  #
332
416
  # In addition to the commands for any state and the +authenticated+
333
417
  # commands, the following commands are valid in the +selected+ state:
@@ -336,12 +420,10 @@ module Net
336
420
  # expunging deleted messages, unless the mailbox was opened as read-only.
337
421
  # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
338
422
  # without expunging any messages.
339
- #
340
- # <em>Requires the +UNSELECT+ capability.</em>
423
+ # <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
341
424
  # - #expunge: Permanently removes messages which have the Deleted flag set.
342
- # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
343
- #
344
- # <em>Requires the +UIDPLUS+ capability.</em>
425
+ # - #uid_expunge: Restricts expunge to only remove the specified UIDs.
426
+ # <em>Requires the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
345
427
  # - #search, #uid_search: Returns sequence numbers or UIDs of messages that
346
428
  # match the given searching criteria.
347
429
  # - #fetch, #uid_fetch: Returns data associated with a set of messages,
@@ -351,45 +433,33 @@ module Net
351
433
  # specified destination mailbox.
352
434
  # - #move, #uid_move: Moves the specified messages to the end of the
353
435
  # specified destination mailbox, expunging them from the current mailbox.
436
+ # <em>Requires the +MOVE+ or +IMAP4rev2+ capability.</em>
437
+ # - #check: <em>*Obsolete:* removed from +IMAP4rev2+.</em>
438
+ # Can be replaced with #noop or #idle.
354
439
  #
355
- # <em>Requires the +MOVE+ capability.</em>
356
- # - #check: Mostly obsolete. Can be replaced with #noop or #idle.
357
- #--
358
- # <em>Removed from IMAP4rev2.</em>
359
- #++
360
- #
361
- # ==== \IMAP commands for the "Logout" state
440
+ # ==== Logout state
362
441
  #
363
442
  # No \IMAP commands are valid in the +logout+ state. If the socket is still
364
443
  # open, Net::IMAP will close it after receiving server confirmation.
365
444
  # Exceptions will be raised by \IMAP commands that have already started and
366
445
  # are waiting for a response, as well as any that are called after logout.
367
446
  #
368
- # === Supported \IMAP extensions
447
+ # === \IMAP extension support
369
448
  #
370
449
  # ==== RFC9051: +IMAP4rev2+
371
450
  #
372
- # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is <em>not supported
373
- # yet</em>, Net::IMAP supports several extensions that have been folded into
374
- # it: +IDLE+, +MOVE+, +NAMESPACE+, +UIDPLUS+, and +UNSELECT+.
375
- #--
376
- # TODO: RFC4466, ABNF extensions (automatic support for other extensions)
377
- # TODO: +ESEARCH+, ExtendedSearchData
378
- # TODO: +SEARCHRES+,
379
- # TODO: +ENABLE+,
380
- # TODO: +SASL-IR+,
381
- # TODO: +LIST-EXTENDED+,
382
- # TODO: +LIST-STATUS+,
383
- # TODO: +LITERAL-+,
384
- # TODO: +BINARY+ (only the FETCH side)
385
- # TODO: +SPECIAL-USE+
386
- # implicitly supported, but we can do better: Response codes: RFC5530, etc
387
- # implicitly supported, but we can do better: <tt>STATUS=SIZE</tt>
388
- # implicitly supported, but we can do better: <tt>STATUS DELETED</tt>
389
- #++
390
- # Commands for these extensions are included with the {Core IMAP
391
- # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above. Other supported
392
- # extensons are listed below.
451
+ # Although IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] is not supported
452
+ # yet, Net::IMAP supports several extensions that have been folded into it:
453
+ # +ENABLE+, +IDLE+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, +UNSELECT+,
454
+ # <tt>STATUS=SIZE</tt>, and the fetch side of +BINARY+.
455
+ # Commands for these extensions are listed with the {Core IMAP
456
+ # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above.
457
+ #
458
+ # >>>
459
+ # <em>The following are folded into +IMAP4rev2+ but are currently
460
+ # unsupported or incompletely supported by</em> Net::IMAP<em>: RFC4466
461
+ # extensions, +SEARCHRES+, +LIST-EXTENDED+, +LIST-STATUS+,
462
+ # +LITERAL-+, and +SPECIAL-USE+.</em>
393
463
  #
394
464
  # ==== RFC2087: +QUOTA+
395
465
  # - #getquota: returns the resource usage and limits for a quota root
@@ -398,92 +468,60 @@ module Net
398
468
  # - #setquota: sets the resource limits for a given quota root.
399
469
  #
400
470
  # ==== RFC2177: +IDLE+
401
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
402
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
471
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
472
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
403
473
  # - #idle: Allows the server to send updates to the client, without the client
404
474
  # needing to poll using #noop.
405
475
  #
406
476
  # ==== RFC2342: +NAMESPACE+
407
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
408
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
477
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
478
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
409
479
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
410
480
  #
411
481
  # ==== RFC2971: +ID+
412
482
  # - #id: exchanges client and server implementation information.
413
483
  #
414
- #--
415
- # ==== RFC3502: +MULTIAPPEND+
416
- # TODO...
417
- #++
418
- #
419
- #--
420
484
  # ==== RFC3516: +BINARY+
421
- # TODO...
422
- #++
485
+ # The fetch side of +BINARY+ has been folded into
486
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].
487
+ # - Updates #fetch and #uid_fetch with the +BINARY+, +BINARY.PEEK+, and
488
+ # +BINARY.SIZE+ items. See FetchData#binary and FetchData#binary_size.
489
+ #
490
+ # >>>
491
+ # *NOTE:* The binary extension the #append command is _not_ supported yet.
423
492
  #
424
493
  # ==== RFC3691: +UNSELECT+
425
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
426
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
494
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
495
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
427
496
  # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
428
497
  # without expunging any messages.
429
498
  #
430
499
  # ==== RFC4314: +ACL+
431
500
  # - #getacl: lists the authenticated user's access rights to a mailbox.
432
501
  # - #setacl: sets the access rights for a user on a mailbox
433
- #--
434
- # TODO: #deleteacl, #listrights, #myrights
435
- #++
436
- # - *_Note:_* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
502
+ # >>>
503
+ # *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
437
504
  #
438
505
  # ==== RFC4315: +UIDPLUS+
439
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
440
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
506
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
507
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
441
508
  # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
442
509
  # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode
443
510
  # - Updates #append with the +APPENDUID+ ResponseCode
444
511
  # - Updates #copy, #move with the +COPYUID+ ResponseCode
445
512
  #
446
- #--
447
- # ==== RFC4466: Collected Extensions to IMAP4 ABNF
448
- # TODO...
449
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this RFC updates
450
- # the protocol to enable new optional parameters to many commands: #select,
451
- # #examine, #create, #rename, #fetch, #uid_fetch, #store, #uid_store, #search,
452
- # #uid_search, and #append. However, specific parameters are not defined.
453
- # Extensions to these commands use this syntax whenever possible. Net::IMAP
454
- # may be partially compatible with extensions to these commands, even without
455
- # any explicit support.
456
- #++
457
- #
458
- #--
459
- # ==== RFC4731 +ESEARCH+
460
- # TODO...
461
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
462
- # - Updates #search, #uid_search to accept result options: +MIN+, +MAX+,
463
- # +ALL+, +COUNT+, and to return ExtendedSearchData.
464
- #++
465
- #
466
- #--
513
+ # ==== RFC4731: +ESEARCH+
514
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].
515
+ # - Updates #search, #uid_search with +return+ options and ESearchResult.
516
+ #
467
517
  # ==== RFC4959: +SASL-IR+
468
- # TODO...
469
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
470
- # - Updates #authenticate to reduce round-trips for supporting mechanisms.
471
- #++
472
- #
473
- #--
474
- # ==== RFC4978: COMPRESS=DEFLATE
475
- # TODO...
476
- #++
477
- #
478
- #--
479
- # ==== RFC5182 +SEARCHRES+
480
- # TODO...
481
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
482
- # - Updates #search, #uid_search with the +SAVE+ result option.
483
- # - Updates #copy, #uid_copy, #fetch, #uid_fetch, #move, #uid_move, #search,
484
- # #uid_search, #store, #uid_store, and #uid_expunge with ability to
485
- # reference the saved result of a previous #search or #uid_search command.
486
- #++
518
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].
519
+ # - Updates #authenticate with the option to send an initial response.
520
+ #
521
+ # ==== RFC5161: +ENABLE+
522
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
523
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
524
+ # - #enable: Enables backwards incompatible server extensions.
487
525
  #
488
526
  # ==== RFC5256: +SORT+
489
527
  # - #sort, #uid_sort: An alternate version of #search or #uid_search which
@@ -493,75 +531,65 @@ module Net
493
531
  # which arranges the results into ordered groups or threads according to a
494
532
  # chosen algorithm.
495
533
  #
496
- #--
497
- # ==== RFC5258 +LIST-EXTENDED+
498
- # TODO...
499
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this updates the
500
- # protocol with new optional parameters to the #list command, adding a few of
501
- # its own. Net::IMAP may be forward-compatible with future #list extensions,
502
- # even without any explicit support.
503
- # - Updates #list to accept selection options: +SUBSCRIBED+, +REMOTE+, and
504
- # +RECURSIVEMATCH+, and return options: +SUBSCRIBED+ and +CHILDREN+.
505
- #++
506
- #
507
- #--
508
- # ==== RFC5819 +LIST-STATUS+
509
- # TODO...
510
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
511
- # - Updates #list with +STATUS+ return option.
512
- #++
513
- #
514
- # ==== +XLIST+ (non-standard, deprecated)
534
+ # ==== +X-GM-EXT-1+
535
+ # +X-GM-EXT-1+ is a non-standard Gmail extension. See {Google's
536
+ # documentation}[https://developers.google.com/gmail/imap/imap-extensions].
537
+ # - Updates #fetch and #uid_fetch with support for +X-GM-MSGID+ (unique
538
+ # message ID), +X-GM-THRID+ (thread ID), and +X-GM-LABELS+ (Gmail labels).
539
+ # - Updates #search with the +X-GM-RAW+ search attribute.
515
540
  # - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses.
516
541
  #
517
- #--
518
- # ==== RFC6154 +SPECIAL-USE+
519
- # TODO...
520
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
521
- # - Updates #list with the +SPECIAL-USE+ selection and return options.
522
- #++
542
+ # *NOTE:* The +OBJECTID+ extension should replace +X-GM-MSGID+ and
543
+ # +X-GM-THRID+, but Gmail does not support it (as of 2023-11-10).
523
544
  #
524
545
  # ==== RFC6851: +MOVE+
525
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
526
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
546
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
547
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
527
548
  # - #move, #uid_move: Moves the specified messages to the end of the
528
549
  # specified destination mailbox, expunging them from the current mailbox.
529
550
  #
530
- #--
531
- # ==== RFC6855: UTF8=ACCEPT
532
- # TODO...
533
- # ==== RFC6855: UTF8=ONLY
534
- # TODO...
535
- #++
536
- #
537
- #--
538
- # ==== RFC7888: <tt>LITERAL+</tt>, +LITERAL-+
539
- # TODO...
540
- # ==== RFC7162: +QRESYNC+
541
- # TODO...
542
- # ==== RFC7162: +CONDSTORE+
543
- # TODO...
544
- # ==== RFC8474: +OBJECTID+
545
- # TODO...
546
- # ==== RFC9208: +QUOTA+
547
- # TODO...
548
- #++
551
+ # ==== RFC6855: <tt>UTF8=ACCEPT</tt>, <tt>UTF8=ONLY</tt>
549
552
  #
550
- # === Handling server responses
553
+ # - See #enable for information about support for UTF-8 string encoding.
551
554
  #
552
- # - #greeting: The server's initial untagged response, which can indicate a
553
- # pre-authenticated connection.
554
- # - #responses: A hash with arrays of unhandled <em>non-+nil+</em>
555
- # UntaggedResponse and ResponseCode +#data+, keyed by +#name+.
556
- # - #add_response_handler: Add a block to be called inside the receiver thread
557
- # with every server response.
558
- # - #remove_response_handler: Remove a previously added response handler.
555
+ # ==== RFC7162: +CONDSTORE+
559
556
  #
557
+ # - Updates #enable with +CONDSTORE+ parameter. +CONDSTORE+ will also be
558
+ # enabled by using any of the extension's command parameters, listed below.
559
+ # - Updates #status with the +HIGHESTMODSEQ+ status attribute.
560
+ # - Updates #select and #examine with the +condstore+ modifier, and adds
561
+ # either a +HIGHESTMODSEQ+ or +NOMODSEQ+ ResponseCode to the responses.
562
+ # - Updates #search, #uid_search, #sort, and #uid_sort with the +MODSEQ+
563
+ # search criterion, and adds SearchResult#modseq to the search response.
564
+ # - Updates #thread and #uid_thread with the +MODSEQ+ search criterion
565
+ # <em>(but thread responses are unchanged)</em>.
566
+ # - Updates #fetch and #uid_fetch with the +changedsince+ modifier and
567
+ # +MODSEQ+ FetchData attribute.
568
+ # - Updates #store and #uid_store with the +unchangedsince+ modifier and adds
569
+ # the +MODIFIED+ ResponseCode to the tagged response.
570
+ #
571
+ # ==== RFC8438: <tt>STATUS=SIZE</tt>
572
+ # - Updates #status with the +SIZE+ status attribute.
573
+ #
574
+ # ==== RFC8474: +OBJECTID+
575
+ # - Adds +MAILBOXID+ ResponseCode to #create tagged response.
576
+ # - Adds +MAILBOXID+ ResponseCode to #select and #examine untagged response.
577
+ # - Updates #fetch and #uid_fetch with the +EMAILID+ and +THREADID+ items.
578
+ # See FetchData#emailid and FetchData#emailid.
579
+ # - Updates #status with support for the +MAILBOXID+ status attribute.
580
+ #
581
+ # ==== RFC9394: +PARTIAL+
582
+ # - Updates #search, #uid_search with the +PARTIAL+ return option which adds
583
+ # ESearchResult#partial return data.
584
+ # - Updates #uid_fetch with the +partial+ modifier.
585
+ #
586
+ # ==== RFC9586: +UIDONLY+
587
+ # - Updates #enable with +UIDONLY+ parameter.
588
+ # - Updates #uid_fetch and #uid_store to return +UIDFETCH+ response.
589
+ # - Updates #expunge and #uid_expunge to return +VANISHED+ response.
590
+ # - Prohibits use of message sequence numbers in responses or requests.
560
591
  #
561
592
  # == References
562
- #--
563
- # TODO: Consider moving references list to REFERENCES.md or REFERENCES.rdoc.
564
- #++
565
593
  #
566
594
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
567
595
  # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
@@ -591,57 +619,57 @@ module Net
591
619
  # Gahrns, M., "IMAP4 Multi-Accessed Mailbox Practice", RFC 2180, DOI
592
620
  # 10.17487/RFC2180, July 1997, <https://www.rfc-editor.org/info/rfc2180>.
593
621
  #
594
- # [UTF7[https://tools.ietf.org/html/rfc2152]]::
622
+ # [UTF7[https://www.rfc-editor.org/rfc/rfc2152]]::
595
623
  # Goldsmith, D. and M. Davis, "UTF-7 A Mail-Safe Transformation Format of
596
624
  # Unicode", RFC 2152, DOI 10.17487/RFC2152, May 1997,
597
625
  # <https://www.rfc-editor.org/info/rfc2152>.
598
626
  #
599
627
  # === Message envelope and body structure
600
628
  #
601
- # [RFC5322[https://tools.ietf.org/html/rfc5322]]::
629
+ # [RFC5322[https://www.rfc-editor.org/rfc/rfc5322]]::
602
630
  # Resnick, P., Ed., "Internet Message Format",
603
631
  # RFC 5322, DOI 10.17487/RFC5322, October 2008,
604
632
  # <https://www.rfc-editor.org/info/rfc5322>.
605
633
  #
606
634
  # <em>Note: obsoletes</em>
607
- # RFC-2822[https://tools.ietf.org/html/rfc2822]<em> (April 2001) and</em>
608
- # RFC-822[https://tools.ietf.org/html/rfc822]<em> (August 1982).</em>
635
+ # RFC-2822[https://www.rfc-editor.org/rfc/rfc2822]<em> (April 2001) and</em>
636
+ # RFC-822[https://www.rfc-editor.org/rfc/rfc822]<em> (August 1982).</em>
609
637
  #
610
- # [CHARSET[https://tools.ietf.org/html/rfc2978]]::
638
+ # [CHARSET[https://www.rfc-editor.org/rfc/rfc2978]]::
611
639
  # Freed, N. and J. Postel, "IANA Charset Registration Procedures", BCP 19,
612
640
  # RFC 2978, DOI 10.17487/RFC2978, October 2000,
613
641
  # <https://www.rfc-editor.org/info/rfc2978>.
614
642
  #
615
- # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]::
643
+ # [DISPOSITION[https://www.rfc-editor.org/rfc/rfc2183]]::
616
644
  # Troost, R., Dorner, S., and K. Moore, Ed., "Communicating Presentation
617
645
  # Information in Internet Messages: The Content-Disposition Header
618
646
  # Field", RFC 2183, DOI 10.17487/RFC2183, August 1997,
619
647
  # <https://www.rfc-editor.org/info/rfc2183>.
620
648
  #
621
- # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]::
649
+ # [MIME-IMB[https://www.rfc-editor.org/rfc/rfc2045]]::
622
650
  # Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions
623
651
  # (MIME) Part One: Format of Internet Message Bodies",
624
652
  # RFC 2045, DOI 10.17487/RFC2045, November 1996,
625
653
  # <https://www.rfc-editor.org/info/rfc2045>.
626
654
  #
627
- # [MIME-IMT[https://tools.ietf.org/html/rfc2046]]::
655
+ # [MIME-IMT[https://www.rfc-editor.org/rfc/rfc2046]]::
628
656
  # Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions
629
657
  # (MIME) Part Two: Media Types", RFC 2046, DOI 10.17487/RFC2046,
630
658
  # November 1996, <https://www.rfc-editor.org/info/rfc2046>.
631
659
  #
632
- # [MIME-HDRS[https://tools.ietf.org/html/rfc2047]]::
660
+ # [MIME-HDRS[https://www.rfc-editor.org/rfc/rfc2047]]::
633
661
  # Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part Three:
634
662
  # Message Header Extensions for Non-ASCII Text",
635
663
  # RFC 2047, DOI 10.17487/RFC2047, November 1996,
636
664
  # <https://www.rfc-editor.org/info/rfc2047>.
637
665
  #
638
- # [RFC2231[https://tools.ietf.org/html/rfc2231]]::
666
+ # [RFC2231[https://www.rfc-editor.org/rfc/rfc2231]]::
639
667
  # Freed, N. and K. Moore, "MIME Parameter Value and Encoded Word
640
668
  # Extensions: Character Sets, Languages, and Continuations",
641
669
  # RFC 2231, DOI 10.17487/RFC2231, November 1997,
642
670
  # <https://www.rfc-editor.org/info/rfc2231>.
643
671
  #
644
- # [I18n-HDRS[https://tools.ietf.org/html/rfc6532]]::
672
+ # [I18n-HDRS[https://www.rfc-editor.org/rfc/rfc6532]]::
645
673
  # Yang, A., Steele, S., and N. Freed, "Internationalized Email Headers",
646
674
  # RFC 6532, DOI 10.17487/RFC6532, February 2012,
647
675
  # <https://www.rfc-editor.org/info/rfc6532>.
@@ -657,42 +685,40 @@ module Net
657
685
  # RFC 2557, DOI 10.17487/RFC2557, March 1999,
658
686
  # <https://www.rfc-editor.org/info/rfc2557>.
659
687
  #
660
- # [MD5[https://tools.ietf.org/html/rfc1864]]::
688
+ # [MD5[https://www.rfc-editor.org/rfc/rfc1864]]::
661
689
  # Myers, J. and M. Rose, "The Content-MD5 Header Field",
662
690
  # RFC 1864, DOI 10.17487/RFC1864, October 1995,
663
691
  # <https://www.rfc-editor.org/info/rfc1864>.
664
692
  #
665
- #--
666
- # TODO: Document IMAP keywords.
693
+ # [RFC3503[https://www.rfc-editor.org/rfc/rfc3503]]::
694
+ # Melnikov, A., "Message Disposition Notification (MDN)
695
+ # profile for Internet Message Access Protocol (IMAP)",
696
+ # RFC 3503, DOI 10.17487/RFC3503, March 2003,
697
+ # <https://www.rfc-editor.org/info/rfc3503>.
667
698
  #
668
- # [RFC3503[https://tools.ietf.org/html/rfc3503]]
669
- # Melnikov, A., "Message Disposition Notification (MDN)
670
- # profile for Internet Message Access Protocol (IMAP)",
671
- # RFC 3503, DOI 10.17487/RFC3503, March 2003,
672
- # <https://www.rfc-editor.org/info/rfc3503>.
673
- #++
699
+ # === \IMAP Extensions
674
700
  #
675
- # === Supported \IMAP Extensions
676
- #
677
- # [QUOTA[https://tools.ietf.org/html/rfc2087]]::
678
- # Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
679
- # January 1997, <https://www.rfc-editor.org/info/rfc2087>.
680
- #--
681
- # TODO: test compatibility with updated QUOTA extension:
682
- # [QUOTA[https://tools.ietf.org/html/rfc9208]]::
701
+ # [QUOTA[https://www.rfc-editor.org/rfc/rfc9208]]::
683
702
  # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
684
703
  # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
685
- #++
686
- # [IDLE[https://tools.ietf.org/html/rfc2177]]::
704
+ #
705
+ # <em>Note: obsoletes</em>
706
+ # RFC-2087[https://www.rfc-editor.org/rfc/rfc2087]<em> (January 1997)</em>.
707
+ # <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
708
+ # [IDLE[https://www.rfc-editor.org/rfc/rfc2177]]::
687
709
  # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
688
710
  # June 1997, <https://www.rfc-editor.org/info/rfc2177>.
689
- # [NAMESPACE[https://tools.ietf.org/html/rfc2342]]::
711
+ # [NAMESPACE[https://www.rfc-editor.org/rfc/rfc2342]]::
690
712
  # Gahrns, M. and C. Newman, "IMAP4 Namespace", RFC 2342,
691
713
  # DOI 10.17487/RFC2342, May 1998, <https://www.rfc-editor.org/info/rfc2342>.
692
- # [ID[https://tools.ietf.org/html/rfc2971]]::
714
+ # [ID[https://www.rfc-editor.org/rfc/rfc2971]]::
693
715
  # Showalter, T., "IMAP4 ID extension", RFC 2971, DOI 10.17487/RFC2971,
694
716
  # October 2000, <https://www.rfc-editor.org/info/rfc2971>.
695
- # [ACL[https://tools.ietf.org/html/rfc4314]]::
717
+ # [BINARY[https://www.rfc-editor.org/rfc/rfc3516]]::
718
+ # Nerenberg, L., "IMAP4 Binary Content Extension", RFC 3516,
719
+ # DOI 10.17487/RFC3516, April 2003,
720
+ # <https://www.rfc-editor.org/info/rfc3516>.
721
+ # [ACL[https://www.rfc-editor.org/rfc/rfc4314]]::
696
722
  # Melnikov, A., "IMAP4 Access Control List (ACL) Extension", RFC 4314,
697
723
  # DOI 10.17487/RFC4314, December 2005,
698
724
  # <https://www.rfc-editor.org/info/rfc4314>.
@@ -700,48 +726,82 @@ module Net
700
726
  # Crispin, M., "Internet Message Access Protocol (\IMAP) - UIDPLUS
701
727
  # extension", RFC 4315, DOI 10.17487/RFC4315, December 2005,
702
728
  # <https://www.rfc-editor.org/info/rfc4315>.
703
- # [SORT[https://tools.ietf.org/html/rfc5256]]::
729
+ # [SORT[https://www.rfc-editor.org/rfc/rfc5256]]::
704
730
  # Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and
705
731
  # THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008,
706
732
  # <https://www.rfc-editor.org/info/rfc5256>.
707
- # [THREAD[https://tools.ietf.org/html/rfc5256]]::
733
+ # [THREAD[https://www.rfc-editor.org/rfc/rfc5256]]::
708
734
  # Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and
709
735
  # THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008,
710
736
  # <https://www.rfc-editor.org/info/rfc5256>.
711
737
  # [RFC5530[https://www.rfc-editor.org/rfc/rfc5530.html]]::
712
738
  # Gulbrandsen, A., "IMAP Response Codes", RFC 5530, DOI 10.17487/RFC5530,
713
739
  # May 2009, <https://www.rfc-editor.org/info/rfc5530>.
714
- # [MOVE[https://tools.ietf.org/html/rfc6851]]::
740
+ # [MOVE[https://www.rfc-editor.org/rfc/rfc6851]]::
715
741
  # Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
716
742
  # (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
717
743
  # <https://www.rfc-editor.org/info/rfc6851>.
744
+ # [UTF8=ACCEPT[https://www.rfc-editor.org/rfc/rfc6855]]::
745
+ # [UTF8=ONLY[https://www.rfc-editor.org/rfc/rfc6855]]::
746
+ # Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed.,
747
+ # "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013,
748
+ # <https://www.rfc-editor.org/info/rfc6855>.
749
+ # [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162]]::
750
+ # [QRESYNC[https://www.rfc-editor.org/rfc/rfc7162]]::
751
+ # Melnikov, A. and D. Cridland, "IMAP Extensions: Quick Flag Changes
752
+ # Resynchronization (CONDSTORE) and Quick Mailbox Resynchronization
753
+ # (QRESYNC)", RFC 7162, DOI 10.17487/RFC7162, May 2014,
754
+ # <https://www.rfc-editor.org/info/rfc7162>.
755
+ # [OBJECTID[https://www.rfc-editor.org/rfc/rfc8474]]::
756
+ # Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
757
+ # RFC 8474, DOI 10.17487/RFC8474, September 2018,
758
+ # <https://www.rfc-editor.org/info/rfc8474>.
759
+ # [PARTIAL[https://www.rfc-editor.org/info/rfc9394]]::
760
+ # Melnikov, A., Achuthan, A., Nagulakonda, V., and L. Alves,
761
+ # "IMAP PARTIAL Extension for Paged SEARCH and FETCH", RFC 9394,
762
+ # DOI 10.17487/RFC9394, June 2023,
763
+ # <https://www.rfc-editor.org/info/rfc9394>.
764
+ # [UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.pdf]]::
765
+ # Melnikov, A., Achuthan, A., Nagulakonda, V., Singh, A., and L. Alves,
766
+ # "\IMAP Extension for Using and Returning Unique Identifiers (UIDs) Only",
767
+ # RFC 9586, DOI 10.17487/RFC9586, May 2024,
768
+ # <https://www.rfc-editor.org/info/rfc9586>.
718
769
  #
719
770
  # === IANA registries
720
- #
721
771
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
722
772
  # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
723
773
  # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
724
774
  # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
725
775
  # * {IMAP Threading Algorithms}[https://www.iana.org/assignments/imap-threading-algorithms/imap-threading-algorithms.xhtml]
726
- #--
727
- # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
728
- # * [{LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
729
- # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
730
- # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
731
- # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
732
- # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
733
- #++
734
776
  # * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
735
777
  # * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]:
736
778
  # +imap+: tcp/143, +imaps+: tcp/993
737
779
  # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
738
780
  # +imap+
739
781
  # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
782
+ # ==== For currently unsupported features:
783
+ # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
784
+ # * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
785
+ # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
786
+ # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
787
+ # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
788
+ # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
740
789
  #
741
790
  class IMAP < Protocol
742
- VERSION = "0.3.9"
791
+ VERSION = "0.5.8"
792
+
793
+ # Aliases for supported capabilities, to be used with the #enable command.
794
+ ENABLE_ALIASES = {
795
+ utf8: "UTF8=ACCEPT",
796
+ "UTF8=ONLY" => "UTF8=ACCEPT",
797
+ }.freeze
743
798
 
744
- autoload :ResponseReader, File.expand_path("imap/response_reader", __dir__)
799
+ dir = File.expand_path("imap", __dir__)
800
+ autoload :ConnectionState, "#{dir}/connection_state"
801
+ autoload :ResponseReader, "#{dir}/response_reader"
802
+ autoload :SASL, "#{dir}/sasl"
803
+ autoload :SASLAdapter, "#{dir}/sasl_adapter"
804
+ autoload :StringPrep, "#{dir}/stringprep"
745
805
 
746
806
  include MonitorMixin
747
807
  if defined?(OpenSSL::SSL)
@@ -749,77 +809,17 @@ module Net
749
809
  include SSL
750
810
  end
751
811
 
752
- # Returns the initial greeting the server, an UntaggedResponse.
753
- attr_reader :greeting
754
-
755
- # Returns a hash with arrays of unhandled <em>non-+nil+</em>
756
- # UntaggedResponse#data keyed by UntaggedResponse#name, and
757
- # ResponseCode#data keyed by ResponseCode#name.
758
- #
759
- # For example:
760
- #
761
- # imap.select("inbox")
762
- # p imap.responses["EXISTS"][-1]
763
- # #=> 2
764
- # p imap.responses["UIDVALIDITY"][-1]
765
- # #=> 968263756
766
- attr_reader :responses
767
-
768
- # Returns all response handlers.
769
- attr_reader :response_handlers
770
-
771
- # Seconds to wait until a connection is opened.
772
- # If the IMAP object cannot open a connection within this time,
773
- # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
774
- attr_reader :open_timeout
775
-
776
- # Seconds to wait until an IDLE response is received.
777
- attr_reader :idle_response_timeout
778
-
779
- # The maximum allowed server response size. When +nil+, there is no limit
780
- # on response size.
781
- #
782
- # The default value is _unlimited_ (after +v0.5.8+, the default is 512 MiB).
783
- # A _much_ lower value should be used with untrusted servers (for example,
784
- # when connecting to a user-provided hostname). When using a lower limit,
785
- # message bodies should be fetched in chunks rather than all at once.
786
- #
787
- # <em>Please Note:</em> this only limits the size per response. It does
788
- # not prevent a flood of individual responses and it does not limit how
789
- # many unhandled responses may be stored on the responses hash. See
790
- # Net::IMAP@Unbounded+memory+use.
791
- #
792
- # Socket reads are limited to the maximum remaining bytes for the current
793
- # response: max_response_size minus the bytes that have already been read.
794
- # When the limit is reached, or reading a +literal+ _would_ go over the
795
- # limit, ResponseTooLargeError is raised and the connection is closed.
796
- # See also #socket_read_limit.
797
- #
798
- # Note that changes will not take effect immediately, because the receiver
799
- # thread may already be waiting for the next response using the previous
800
- # value. Net::IMAP#noop can force a response and enforce the new setting
801
- # immediately.
802
- #
803
- # ==== Versioned Defaults
804
- #
805
- # Net::IMAP#max_response_size <em>was added in +v0.2.5+ and +v0.3.9+ as an
806
- # attr_accessor, and in +v0.4.20+ and +v0.5.7+ as a delegator to a config
807
- # attribute.</em>
808
- #
809
- # * original: +nil+ <em>(no limit)</em>
810
- # * +0.5+: 512 MiB
811
- attr_accessor :max_response_size
812
+ # Returns the global Config object
813
+ def self.config; Config.global end
812
814
 
813
- attr_accessor :client_thread # :nodoc:
815
+ # Returns the global debug mode.
816
+ # Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
817
+ def self.debug; config.debug end
814
818
 
815
- # Returns the debug mode.
816
- def self.debug
817
- return @@debug
818
- end
819
-
820
- # Sets the debug mode.
819
+ # Sets the global debug mode.
820
+ # Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
821
821
  def self.debug=(val)
822
- return @@debug = val
822
+ config.debug = val
823
823
  end
824
824
 
825
825
  # The default port for IMAP connections, port 143
@@ -838,11 +838,286 @@ module Net
838
838
  alias default_ssl_port default_tls_port
839
839
  end
840
840
 
841
+ # Returns the initial greeting sent by the server, an UntaggedResponse.
842
+ attr_reader :greeting
843
+
844
+ # The client configuration. See Net::IMAP::Config.
845
+ #
846
+ # By default, the client's local configuration inherits from the global
847
+ # Net::IMAP.config.
848
+ attr_reader :config
849
+
850
+ ##
851
+ # :attr_reader: open_timeout
852
+ # Seconds to wait until a connection is opened. Also used by #starttls.
853
+ # Delegates to {config.open_timeout}[rdoc-ref:Config#open_timeout].
854
+
855
+ ##
856
+ # :attr_reader: idle_response_timeout
857
+ # Seconds to wait until an IDLE response is received.
858
+ # Delegates to {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout].
859
+
860
+ ##
861
+ # :attr_accessor: max_response_size
862
+ #
863
+ # The maximum allowed server response size, in bytes.
864
+ # Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size].
865
+
866
+ # :stopdoc:
867
+ def open_timeout; config.open_timeout end
868
+ def idle_response_timeout; config.idle_response_timeout end
869
+ def max_response_size; config.max_response_size end
870
+ def max_response_size=(val) config.max_response_size = val end
871
+ # :startdoc:
872
+
873
+ # The hostname this client connected to
874
+ attr_reader :host
875
+
876
+ # The port this client connected to
877
+ attr_reader :port
878
+
879
+ # Returns the
880
+ # {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]
881
+ # used by the SSLSocket when TLS is attempted, even when the TLS handshake
882
+ # is unsuccessful. The context object will be frozen.
883
+ #
884
+ # Returns +nil+ for a plaintext connection.
885
+ attr_reader :ssl_ctx
886
+
887
+ # Returns the parameters that were sent to #ssl_ctx
888
+ # {set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]
889
+ # when the connection tries to use TLS (even when unsuccessful).
890
+ #
891
+ # Returns +false+ for a plaintext connection.
892
+ attr_reader :ssl_ctx_params
893
+
894
+ # Returns the current connection state.
895
+ #
896
+ # Once an IMAP connection is established, the connection is in one of four
897
+ # states: +not_authenticated+, +authenticated+, +selected+, and +logout+.
898
+ # Most commands are valid only in certain states.
899
+ #
900
+ # The connection state object responds to +to_sym+ and +name+ with the name
901
+ # of the current connection state, as a Symbol or String. Future versions
902
+ # of +net-imap+ may store additional information on the state object.
903
+ #
904
+ # From {RFC9051}[https://www.rfc-editor.org/rfc/rfc9051#section-3]:
905
+ # +----------------------+
906
+ # |connection established|
907
+ # +----------------------+
908
+ # ||
909
+ # \/
910
+ # +--------------------------------------+
911
+ # | server greeting |
912
+ # +--------------------------------------+
913
+ # || (1) || (2) || (3)
914
+ # \/ || ||
915
+ # +-----------------+ || ||
916
+ # |Not Authenticated| || ||
917
+ # +-----------------+ || ||
918
+ # || (7) || (4) || ||
919
+ # || \/ \/ ||
920
+ # || +----------------+ ||
921
+ # || | Authenticated |<=++ ||
922
+ # || +----------------+ || ||
923
+ # || || (7) || (5) || (6) ||
924
+ # || || \/ || ||
925
+ # || || +--------+ || ||
926
+ # || || |Selected|==++ ||
927
+ # || || +--------+ ||
928
+ # || || || (7) ||
929
+ # \/ \/ \/ \/
930
+ # +--------------------------------------+
931
+ # | Logout |
932
+ # +--------------------------------------+
933
+ # ||
934
+ # \/
935
+ # +-------------------------------+
936
+ # |both sides close the connection|
937
+ # +-------------------------------+
938
+ #
939
+ # >>>
940
+ # Legend for the above diagram:
941
+ #
942
+ # 1. connection without pre-authentication (+OK+ #greeting)
943
+ # 2. pre-authenticated connection (+PREAUTH+ #greeting)
944
+ # 3. rejected connection (+BYE+ #greeting)
945
+ # 4. successful #login or #authenticate command
946
+ # 5. successful #select or #examine command
947
+ # 6. #close or #unselect command, unsolicited +CLOSED+ response code, or
948
+ # failed #select or #examine command
949
+ # 7. #logout command, server shutdown, or connection closed
950
+ #
951
+ # Before the server greeting, the state is +not_authenticated+.
952
+ # After the connection closes, the state remains +logout+.
953
+ attr_reader :connection_state
954
+
955
+ # Creates a new Net::IMAP object and connects it to the specified
956
+ # +host+.
957
+ #
958
+ # ==== Options
959
+ #
960
+ # Accepts the following options:
961
+ #
962
+ # [port]
963
+ # Port number. Defaults to 993 when +ssl+ is truthy, and 143 otherwise.
964
+ #
965
+ # [ssl]
966
+ # If +true+, the connection will use TLS with the default params set by
967
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params].
968
+ # If +ssl+ is a hash, it's passed to
969
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
970
+ # the keys are names of attribute assignment methods on
971
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]. For example:
972
+ #
973
+ # [{ca_file}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-ca_file]]
974
+ # The path to a file containing a PEM-format CA certificate.
975
+ # [{ca_path}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-ca_path]]
976
+ # The path to a directory containing CA certificates in PEM format.
977
+ # [{min_version}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-min_version-3D]]
978
+ # Sets the lower bound on the supported SSL/TLS protocol version. Set to
979
+ # an +OpenSSL+ constant such as +OpenSSL::SSL::TLS1_2_VERSION+,
980
+ # [{verify_mode}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-verify_mode]]
981
+ # SSL session verification mode. Valid modes include
982
+ # +OpenSSL::SSL::VERIFY_PEER+ and +OpenSSL::SSL::VERIFY_NONE+.
983
+ #
984
+ # See {OpenSSL::SSL::SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] for other valid SSL context params.
985
+ #
986
+ # See DeprecatedClientOptions.new for deprecated SSL arguments.
987
+ #
988
+ # [response_handlers]
989
+ # A list of response handlers to be added before the receiver thread is
990
+ # started. This ensures every server response is handled, including the
991
+ # #greeting. Note that the greeting is handled in the current thread, but
992
+ # all other responses are handled in the receiver thread.
993
+ #
994
+ # [config]
995
+ # A Net::IMAP::Config object to use as the basis for #config. By default,
996
+ # the global Net::IMAP.config is used.
997
+ #
998
+ # >>>
999
+ # *NOTE:* +config+ does not set #config directly---it sets the _parent_
1000
+ # config for inheritance. Every client creates its own unique #config.
1001
+ #
1002
+ # All other keyword arguments are forwarded to Net::IMAP::Config.new, to
1003
+ # initialize the client's #config. For example:
1004
+ #
1005
+ # [{open_timeout}[rdoc-ref:Config#open_timeout]]
1006
+ # Seconds to wait until a connection is opened
1007
+ # [{idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]]
1008
+ # Seconds to wait until an IDLE response is received
1009
+ #
1010
+ # See Net::IMAP::Config for other valid options.
1011
+ #
1012
+ # ==== Examples
1013
+ #
1014
+ # Connect to cleartext port 143 at mail.example.com and receive the server greeting:
1015
+ # imap = Net::IMAP.new('mail.example.com', ssl: false) # => #<Net::IMAP:0x00007f79b0872bd0>
1016
+ # imap.port => 143
1017
+ # imap.tls_verified? => false
1018
+ # imap.greeting => name: ("OK" | "PREAUTH") => status
1019
+ # status # => "OK"
1020
+ # # The client is connected in the "Not Authenticated" state.
1021
+ #
1022
+ # Connect with TLS to port 993
1023
+ # imap = Net::IMAP.new('mail.example.com', ssl: true) # => #<Net::IMAP:0x00007f79b0872bd0>
1024
+ # imap.port => 993
1025
+ # imap.tls_verified? => true
1026
+ # imap.greeting => name: (/OK/i | /PREAUTH/i) => status
1027
+ # case status
1028
+ # in /OK/i
1029
+ # # The client is connected in the "Not Authenticated" state.
1030
+ # imap.authenticate("PLAIN", "joe_user", "joes_password")
1031
+ # in /PREAUTH/i
1032
+ # # The client is connected in the "Authenticated" state.
1033
+ # end
1034
+ #
1035
+ # Connect with prior authentication, for example using an SSL certificate:
1036
+ # ssl_ctx_params = {
1037
+ # cert: OpenSSL::X509::Certificate.new(File.read("client.crt")),
1038
+ # key: OpenSSL::PKey::EC.new(File.read('client.key')),
1039
+ # extra_chain_cert: [
1040
+ # OpenSSL::X509::Certificate.new(File.read("intermediate.crt")),
1041
+ # ],
1042
+ # }
1043
+ # imap = Net::IMAP.new('mail.example.com', ssl: ssl_ctx_params)
1044
+ # imap.port => 993
1045
+ # imap.tls_verified? => true
1046
+ # imap.greeting => name: "PREAUTH"
1047
+ # # The client is connected in the "Authenticated" state.
1048
+ #
1049
+ # ==== Exceptions
1050
+ #
1051
+ # The most common errors are:
1052
+ #
1053
+ # [Errno::ECONNREFUSED]
1054
+ # Connection refused by +host+ or an intervening firewall.
1055
+ # [Errno::ETIMEDOUT]
1056
+ # Connection timed out (possibly due to packets being dropped by an
1057
+ # intervening firewall).
1058
+ # [Errno::ENETUNREACH]
1059
+ # There is no route to that network.
1060
+ # [SocketError]
1061
+ # Hostname not known or other socket error.
1062
+ # [Net::IMAP::ByeResponseError]
1063
+ # Connected to the host successfully, but it immediately said goodbye.
1064
+ #
1065
+ def initialize(host, port: nil, ssl: nil, response_handlers: nil,
1066
+ config: Config.global, **config_options)
1067
+ super()
1068
+ # Config options
1069
+ @host = host
1070
+ @config = Config.new(config, **config_options)
1071
+ @port = port || (ssl ? SSL_PORT : PORT)
1072
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
1073
+
1074
+ # Basic Client State
1075
+ @utf8_strings = false
1076
+ @debug_output_bol = true
1077
+ @exception = nil
1078
+ @greeting = nil
1079
+ @capabilities = nil
1080
+ @tls_verified = false
1081
+ @connection_state = ConnectionState::NotAuthenticated.new
1082
+
1083
+ # Client Protocol Receiver
1084
+ @parser = ResponseParser.new(config: @config)
1085
+ @responses = Hash.new {|h, k| h[k] = [] }
1086
+ @response_handlers = []
1087
+ @receiver_thread = nil
1088
+ @receiver_thread_exception = nil
1089
+ @receiver_thread_terminating = false
1090
+ response_handlers&.each do add_response_handler(_1) end
1091
+
1092
+ # Client Protocol Sender (including state for currently running commands)
1093
+ @tag_prefix = "RUBY"
1094
+ @tagno = 0
1095
+ @tagged_responses = {}
1096
+ @tagged_response_arrival = new_cond
1097
+ @continued_command_tag = nil
1098
+ @continuation_request_arrival = new_cond
1099
+ @continuation_request_exception = nil
1100
+ @idle_done_cond = nil
1101
+ @logout_command_tag = nil
1102
+
1103
+ # Connection
1104
+ @sock = tcp_socket(@host, @port)
1105
+ @reader = ResponseReader.new(self, @sock)
1106
+ start_tls_session if ssl_ctx
1107
+ start_imap_connection
1108
+ end
1109
+
1110
+ # Returns true after the TLS negotiation has completed and the remote
1111
+ # hostname has been verified. Returns false when TLS has been established
1112
+ # but peer verification was disabled.
1113
+ def tls_verified?; @tls_verified end
1114
+
841
1115
  # Disconnects from the server.
842
1116
  #
843
- # Related: #logout
1117
+ # Related: #logout, #logout!
844
1118
  def disconnect
845
1119
  return if disconnected?
1120
+ state_logout!
846
1121
  begin
847
1122
  begin
848
1123
  # try to call SSL::SSLSocket#io.
@@ -870,62 +1145,123 @@ module Net
870
1145
  return @sock.closed?
871
1146
  end
872
1147
 
873
- # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
874
- # and returns an array of capabilities that the server supports. Each
875
- # capability is a string.
1148
+ # Returns whether the server supports a given +capability+. When available,
1149
+ # cached #capabilities are used without sending a new #capability command to
1150
+ # the server.
876
1151
  #
877
- # See the {IANA IMAP4 capabilities
878
- # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list
879
- # of all standard capabilities, and their reference RFCs.
1152
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
1153
+ # behaviour according to the server's advertised #capabilities.</em>
880
1154
  #
881
- # >>>
882
- # <em>*Note* that Net::IMAP does not currently modify its
883
- # behaviour according to the capabilities of the server;
884
- # it is up to the user of the class to ensure that
885
- # a certain capability is supported by a server before
886
- # using it.</em>
1155
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
887
1156
  #
888
- # Capability requirements—other than +IMAP4rev1+—are listed in the
889
- # documentation for each command method.
1157
+ # Related: #auth_capable?, #capabilities, #capability, #enable
1158
+ def capable?(capability) capabilities.include? capability.to_s.upcase end
1159
+ alias capability? capable?
1160
+
1161
+ # Returns the server capabilities. When available, cached capabilities are
1162
+ # used without sending a new #capability command to the server.
890
1163
  #
891
- # ===== Basic IMAP4rev1 capabilities
1164
+ # To ensure a case-insensitive comparison, #capable? can be used instead.
892
1165
  #
893
- # All IMAP4rev1 servers must include +IMAP4rev1+ in their capabilities list.
894
- # All IMAP4rev1 servers must _implement_ the +STARTTLS+,
895
- # <tt>AUTH=PLAIN</tt>, and +LOGINDISABLED+ capabilities, and clients must
896
- # respect their presence or absence. See the capabilites requirements on
897
- # #starttls, #login, and #authenticate.
1166
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
1167
+ # behaviour according to the server's advertised #capabilities.</em>
898
1168
  #
899
- # ===== Using IMAP4rev1 extensions
1169
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
900
1170
  #
901
- # IMAP4rev1 servers must not activate incompatible behavior until an
902
- # explicit client action invokes a capability, e.g. sending a command or
903
- # command argument specific to that capability. Extensions with backward
904
- # compatible behavior, such as response codes or mailbox attributes, may
905
- # be sent at any time.
1171
+ # Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable
1172
+ def capabilities
1173
+ @capabilities || capability
1174
+ end
1175
+
1176
+ # Returns the #authenticate mechanisms that the server claims to support.
1177
+ # These are derived from the #capabilities with an <tt>AUTH=</tt> prefix.
1178
+ #
1179
+ # This may be different when the connection is cleartext or using TLS. Most
1180
+ # servers will drop all <tt>AUTH=</tt> mechanisms from #capabilities after
1181
+ # the connection has authenticated.
906
1182
  #
907
- # Invoking capabilities which are unknown to Net::IMAP may cause unexpected
908
- # behavior and errors, for example ResponseParseError is raised when unknown
909
- # response syntax is received. Invoking commands or command parameters that
910
- # are unsupported by the server may raise NoResponseError, BadResponseError,
911
- # or cause other unexpected behavior.
1183
+ # imap = Net::IMAP.new(hostname, ssl: false)
1184
+ # imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"]
1185
+ # imap.auth_mechanisms # => []
912
1186
  #
913
- # ===== Caching +CAPABILITY+ responses
1187
+ # imap.starttls
1188
+ # imap.capabilities # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2",
1189
+ # # "AUTH=OAUTHBEARER"]
1190
+ # imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"]
914
1191
  #
915
- # Servers may send their capability list, unsolicited, using the
916
- # +CAPABILITY+ response code or an untagged +CAPABILITY+ response. These
917
- # responses can be retrieved and cached using #responses or
918
- # #add_response_handler.
1192
+ # imap.authenticate("XOAUTH2", username, oauth2_access_token)
1193
+ # imap.auth_mechanisms # => []
919
1194
  #
920
- # But cached capabilities _must_ be discarded after #starttls, #login, or
921
- # #authenticate. The OK TaggedResponse to #login and #authenticate may
922
- # include +CAPABILITY+ response code data, but the TaggedResponse for
923
- # #starttls is sent clear-text and cannot be trusted.
1195
+ # Related: #authenticate, #auth_capable?, #capabilities
1196
+ def auth_mechanisms
1197
+ capabilities
1198
+ .grep(/\AAUTH=/i)
1199
+ .map { _1.delete_prefix("AUTH=") }
1200
+ end
1201
+
1202
+ # Returns whether the server supports a given SASL +mechanism+ for use with
1203
+ # the #authenticate command. The +mechanism+ is supported when
1204
+ # #capabilities includes <tt>"AUTH=#{mechanism.to_s.upcase}"</tt>. When
1205
+ # available, cached capabilities are used without sending a new #capability
1206
+ # command to the server.
924
1207
  #
1208
+ # imap.capable? "AUTH=PLAIN" # => true
1209
+ # imap.auth_capable? "PLAIN" # => true
1210
+ # imap.auth_capable? "blurdybloop" # => false
1211
+ #
1212
+ # Related: #authenticate, #auth_mechanisms, #capable?, #capabilities
1213
+ def auth_capable?(mechanism)
1214
+ capable? "AUTH=#{mechanism}"
1215
+ end
1216
+
1217
+ # Returns whether capabilities have been cached. When true, #capable? and
1218
+ # #capabilities don't require sending a #capability command to the server.
1219
+ #
1220
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
1221
+ #
1222
+ # Related: #capable?, #capability, #clear_cached_capabilities
1223
+ def capabilities_cached?
1224
+ !!@capabilities
1225
+ end
1226
+
1227
+ # Clears capabilities that have been remembered by the Net::IMAP client.
1228
+ # This forces a #capability command to be sent the next time a #capabilities
1229
+ # query method is called.
1230
+ #
1231
+ # Net::IMAP automatically discards its cached capabilities when they can
1232
+ # change. Explicitly calling this _should_ be unnecessary for well-behaved
1233
+ # servers.
1234
+ #
1235
+ # Related: #capable?, #capability, #capabilities_cached?
1236
+ def clear_cached_capabilities
1237
+ synchronize do
1238
+ clear_responses("CAPABILITY")
1239
+ @capabilities = nil
1240
+ end
1241
+ end
1242
+
1243
+ # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
1244
+ # and returns an array of capabilities that are supported by the server.
1245
+ # The result is stored for use by #capable? and #capabilities.
1246
+ #
1247
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
1248
+ # behaviour according to the server's advertised #capabilities.</em>
1249
+ #
1250
+ # Net::IMAP automatically stores and discards capability data according to
1251
+ # the requirements and recommendations in
1252
+ # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
1253
+ # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
1254
+ # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
1255
+ # Use #capable?, #auth_capable?, or #capabilities to this cache and avoid
1256
+ # sending the #capability command unnecessarily.
1257
+ #
1258
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
1259
+ #
1260
+ # Related: #capable?, #auth_capable?, #capability, #enable
925
1261
  def capability
926
1262
  synchronize do
927
1263
  send_command("CAPABILITY")
928
- return @responses.delete("CAPABILITY")[-1]
1264
+ @capabilities = clear_responses("CAPABILITY").last.freeze
929
1265
  end
930
1266
  end
931
1267
 
@@ -936,8 +1272,7 @@ module Net
936
1272
  # Note that the user should first check if the server supports the ID
937
1273
  # capability. For example:
938
1274
  #
939
- # capabilities = imap.capability
940
- # if capabilities.include?("ID")
1275
+ # if capable?(:ID)
941
1276
  # id = imap.id(
942
1277
  # name: "my IMAP client (ruby)",
943
1278
  # version: MyIMAP::VERSION,
@@ -946,16 +1281,16 @@ module Net
946
1281
  # )
947
1282
  # end
948
1283
  #
949
- # See [ID[https://tools.ietf.org/html/rfc2971]] for field definitions.
1284
+ # See [ID[https://www.rfc-editor.org/rfc/rfc2971]] for field definitions.
950
1285
  #
951
- # ===== Capabilities
1286
+ # ==== Capabilities
952
1287
  #
953
1288
  # The server's capabilities must include +ID+
954
- # [RFC2971[https://tools.ietf.org/html/rfc2971]]
1289
+ # [RFC2971[https://www.rfc-editor.org/rfc/rfc2971]].
955
1290
  def id(client_id=nil)
956
1291
  synchronize do
957
1292
  send_command("ID", ClientID.new(client_id))
958
- @responses.delete("ID")&.last
1293
+ clear_responses("ID").last
959
1294
  end
960
1295
  end
961
1296
 
@@ -964,7 +1299,7 @@ module Net
964
1299
  #
965
1300
  # This allows the server to send unsolicited untagged EXPUNGE #responses,
966
1301
  # but does not execute any client request. \IMAP servers are permitted to
967
- # send unsolicited untagged responses at any time, except for `EXPUNGE`.
1302
+ # send unsolicited untagged responses at any time, except for +EXPUNGE+:
968
1303
  #
969
1304
  # * +EXPUNGE+ can only be sent while a command is in progress.
970
1305
  # * +EXPUNGE+ must _not_ be sent during #fetch, #store, or #search.
@@ -979,150 +1314,200 @@ module Net
979
1314
  # to inform the command to inform the server that the client is done with
980
1315
  # the connection.
981
1316
  #
982
- # Related: #disconnect
1317
+ # Related: #disconnect, #logout!
983
1318
  def logout
984
1319
  send_command("LOGOUT")
985
1320
  end
986
1321
 
1322
+ # Calls #logout then, after receiving the TaggedResponse for the +LOGOUT+,
1323
+ # calls #disconnect. Returns the TaggedResponse from +LOGOUT+. Returns
1324
+ # +nil+ when the client is already disconnected, in contrast to #logout
1325
+ # which raises an exception.
1326
+ #
1327
+ # If #logout raises a StandardError, a warning will be printed but the
1328
+ # exception will not be re-raised.
1329
+ #
1330
+ # This is useful in situations where the connection must be dropped, for
1331
+ # example for security or after tests. If logout errors need to be handled,
1332
+ # use #logout and #disconnect instead.
1333
+ #
1334
+ # Related: #logout, #disconnect
1335
+ def logout!
1336
+ logout unless disconnected?
1337
+ rescue => ex
1338
+ warn "%s during <Net::IMAP %s:%s> logout!: %s" % [
1339
+ ex.class, host, port, ex
1340
+ ]
1341
+ ensure
1342
+ disconnect
1343
+ end
1344
+
987
1345
  # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1]
988
1346
  # to start a TLS session.
989
1347
  #
990
- # Any +options+ are forwarded to OpenSSL::SSL::SSLContext#set_params.
1348
+ # Any +options+ are forwarded directly to
1349
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
1350
+ # the keys are names of attribute assignment methods on
1351
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
1352
+ #
1353
+ # See DeprecatedClientOptions#starttls for deprecated arguments.
991
1354
  #
992
1355
  # This method returns after TLS negotiation and hostname verification are
993
1356
  # both successful. Any error indicates that the connection has not been
994
1357
  # secured.
995
1358
  #
1359
+ # After the server agrees to start a TLS connection, this method waits up to
1360
+ # {config.open_timeout}[rdoc-ref:Config#open_timeout] before raising
1361
+ # +Net::OpenTimeout+.
1362
+ #
996
1363
  # *Note:*
997
1364
  # >>>
998
1365
  # Any #response_handlers added before STARTTLS should be aware that the
999
1366
  # TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation.
1000
- # TLS negotiation starts immediately after that response.
1367
+ # TLS starts immediately _after_ that response. Any response code sent
1368
+ # with the response (e.g. CAPABILITY) is insecure and cannot be trusted.
1001
1369
  #
1002
1370
  # Related: Net::IMAP.new, #login, #authenticate
1003
1371
  #
1004
- # ===== Capability
1005
- #
1006
- # The server's capabilities must include +STARTTLS+.
1372
+ # ==== Capability
1373
+ # Clients should not call #starttls unless the server advertises the
1374
+ # +STARTTLS+ capability.
1007
1375
  #
1008
1376
  # Server capabilities may change after #starttls, #login, and #authenticate.
1009
- # Cached capabilities _must_ be invalidated after this method completes.
1377
+ # Cached #capabilities will be cleared when this method completes.
1010
1378
  #
1011
- # The TaggedResponse to #starttls is sent clear-text, so the server <em>must
1012
- # *not*</em> send capabilities in the #starttls response and clients <em>must
1013
- # not</em> use them if they are sent. Servers will generally send an
1014
- # unsolicited untagged response immeditely _after_ #starttls completes.
1015
- #
1016
- def starttls(options = {}, verify = true)
1017
- send_command("STARTTLS") do |resp|
1379
+ def starttls(**options)
1380
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
1381
+ error = nil
1382
+ ok = send_command("STARTTLS") do |resp|
1018
1383
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
1019
- begin
1020
- # for backward compatibility
1021
- certs = options.to_str
1022
- options = create_ssl_params(certs, verify)
1023
- rescue NoMethodError
1024
- end
1025
- start_tls_session(options)
1384
+ clear_cached_capabilities
1385
+ clear_responses
1386
+ start_tls_session
1026
1387
  end
1388
+ rescue Exception => error
1389
+ raise # note that the error backtrace is in the receiver_thread
1027
1390
  end
1391
+ if error
1392
+ disconnect
1393
+ raise error
1394
+ end
1395
+ ok
1028
1396
  end
1029
1397
 
1030
1398
  # :call-seq:
1031
- # authenticate(mechanism, ...) -> ok_resp
1032
- # authenticate(mech, *creds, **props) {|prop, auth| val } -> ok_resp
1033
- # authenticate(mechanism, authnid, credentials, authzid=nil) -> ok_resp
1034
- # authenticate(mechanism, **properties) -> ok_resp
1035
- # authenticate(mechanism) {|propname, authctx| prop_value } -> ok_resp
1399
+ # authenticate(mechanism, *, sasl_ir: config.sasl_ir, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp
1036
1400
  #
1037
1401
  # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
1038
1402
  # to authenticate the client. If successful, the connection enters the
1039
1403
  # "_authenticated_" state.
1040
1404
  #
1041
1405
  # +mechanism+ is the name of the \SASL authentication mechanism to be used.
1042
- # All other arguments are forwarded to the authenticator for the requested
1043
- # mechanism. The listed call signatures are suggestions. <em>The
1044
- # documentation for each individual mechanism must be consulted for its
1045
- # specific parameters.</em>
1046
1406
  #
1047
- # An exception Net::IMAP::NoResponseError is raised if authentication fails.
1407
+ # +sasl_ir+ allows or disallows sending an "initial response" (see the
1408
+ # +SASL-IR+ capability, below). Defaults to the #config value for
1409
+ # {sasl_ir}[rdoc-ref:Config#sasl_ir], which defaults to +true+.
1048
1410
  #
1049
- # Related: #login, #starttls
1411
+ # The +registry+ kwarg can be used to select the mechanism implementation
1412
+ # from a custom registry. See SASL.authenticator and SASL::Authenticators.
1050
1413
  #
1051
- # ==== Supported SASL Mechanisms
1414
+ # All other arguments are forwarded to the registered SASL authenticator for
1415
+ # the requested mechanism. <em>The documentation for each individual
1416
+ # mechanism must be consulted for its specific parameters.</em>
1052
1417
  #
1053
- # +PLAIN+:: See PlainAuthenticator.
1054
- # Login using clear-text username and password.
1418
+ # Related: #login, #starttls, #auth_capable?, #auth_mechanisms
1055
1419
  #
1056
- # +XOAUTH2+:: See XOauth2Authenticator.
1057
- # Login using a username and OAuth2 access token.
1058
- # Non-standard and obsoleted by +OAUTHBEARER+, but widely
1059
- # supported.
1420
+ # ==== Mechanisms
1060
1421
  #
1061
- # >>>
1062
- # *Deprecated:* <em>Obsolete mechanisms are available for backwards
1063
- # compatibility.</em>
1422
+ # Each mechanism has different properties and requirements. Please consult
1423
+ # the documentation for the specific mechanisms you are using:
1424
+ #
1425
+ # +ANONYMOUS+::
1426
+ # See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
1427
+ #
1428
+ # Allows the user to gain access to public services or resources without
1429
+ # authenticating or disclosing an identity.
1064
1430
  #
1065
- # For +DIGEST-MD5+ see DigestMD5Authenticator.
1431
+ # +EXTERNAL+::
1432
+ # See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
1066
1433
  #
1067
- # For +LOGIN+, see LoginAuthenticator.
1434
+ # Authenticates using already established credentials, such as a TLS
1435
+ # certificate or IPsec.
1068
1436
  #
1069
- # For +CRAM-MD5+, see CramMD5Authenticator.
1437
+ # +OAUTHBEARER+::
1438
+ # See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator].
1070
1439
  #
1071
- # <em>Using a deprecated mechanism will print a warning.</em>
1440
+ # Login using an OAuth2 Bearer token. This is the standard mechanism
1441
+ # for using OAuth2 with \SASL, but it is not yet deployed as widely as
1442
+ # +XOAUTH2+.
1072
1443
  #
1073
- # See Net::IMAP::Authenticators for information on plugging in
1074
- # authenticators for other mechanisms. See the {SASL mechanism
1444
+ # +PLAIN+::
1445
+ # See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator].
1446
+ #
1447
+ # Login using clear-text username and password.
1448
+ #
1449
+ # +SCRAM-SHA-1+::
1450
+ # +SCRAM-SHA-256+::
1451
+ # See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator].
1452
+ #
1453
+ # Login by username and password. The password is not sent to the
1454
+ # server but is used in a salted challenge/response exchange.
1455
+ # +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
1456
+ # Net::IMAP::SASL. New authenticators can easily be added for any other
1457
+ # <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
1458
+ # OpenSSL::Digest.
1459
+ #
1460
+ # +XOAUTH2+::
1461
+ # See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator].
1462
+ #
1463
+ # Login using a username and an OAuth2 access token. Non-standard and
1464
+ # obsoleted by +OAUTHBEARER+, but widely supported.
1465
+ #
1466
+ # See the {SASL mechanism
1075
1467
  # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
1076
- # for information on these and other SASL mechanisms.
1468
+ # for a list of all SASL mechanisms and their specifications. To register
1469
+ # new authenticators, see Authenticators.
1077
1470
  #
1078
- # ===== Capabilities
1471
+ # ===== Deprecated mechanisms
1079
1472
  #
1080
- # Clients MUST NOT attempt to authenticate with a mechanism unless
1081
- # <tt>"AUTH=#{mechanism}"</tt> for that mechanism is a server capability.
1473
+ # <em>Obsolete mechanisms should be avoided, but are still available for
1474
+ # backwards compatibility. See</em> Net::IMAP::SASL@Deprecated+mechanisms.
1475
+ # <em>Using a deprecated mechanism will print a warning.</em>
1082
1476
  #
1083
- # Server capabilities may change after #starttls, #login, and #authenticate.
1084
- # Cached capabilities _must_ be invalidated after this method completes.
1085
- # The TaggedResponse to #authenticate may include updated capabilities in
1086
- # its ResponseCode.
1087
- #
1088
- # ===== Example
1089
- # If the authenticators ignore unhandled keyword arguments, the same config
1090
- # can be used for multiple mechanisms:
1091
- #
1092
- # password = nil # saved locally, so we don't ask more than once
1093
- # accesstok = nil # saved locally...
1094
- # creds = {
1095
- # authcid: username,
1096
- # password: proc { password ||= ui.prompt_for_password },
1097
- # oauth2_token: proc { accesstok ||= kms.fresh_access_token },
1098
- # }
1099
- # capa = imap.capability
1100
- # if capa.include? "AUTH=OAUTHBEARER"
1101
- # imap.authenticate "OAUTHBEARER", **creds # authcid, oauth2_token
1102
- # elsif capa.include? "AUTH=XOAUTH2"
1103
- # imap.authenticate "XOAUTH2", **creds # authcid, oauth2_token
1104
- # elsif capa.include? "AUTH=SCRAM-SHA-256"
1105
- # imap.authenticate "SCRAM-SHA-256", **creds # authcid, password
1106
- # elsif capa.include? "AUTH=PLAIN"
1107
- # imap.authenticate "PLAIN", **creds # authcid, password
1108
- # elsif capa.include? "AUTH=DIGEST-MD5"
1109
- # imap.authenticate "DIGEST-MD5", **creds # authcid, password
1110
- # elsif capa.include? "LOGINDISABLED"
1111
- # raise "the server has disabled login"
1112
- # else
1477
+ # ==== Capabilities
1478
+ #
1479
+ # <tt>"AUTH=#{mechanism}"</tt> capabilities indicate server support for
1480
+ # mechanisms. Use #auth_capable? or #auth_mechanisms to check for support
1481
+ # before using a particular mechanism.
1482
+ #
1483
+ # if imap.auth_capable? "XOAUTH2"
1484
+ # imap.authenticate "XOAUTH2", username, oauth2_access_token
1485
+ # elsif imap.auth_capable? "PLAIN"
1486
+ # imap.authenticate "PLAIN", username, password
1487
+ # elsif !imap.capability? "LOGINDISABLED"
1113
1488
  # imap.login username, password
1489
+ # else
1490
+ # raise "No acceptable authentication mechanism is available"
1114
1491
  # end
1115
1492
  #
1116
- def authenticate(mechanism, *args, **props, &cb)
1117
- authenticator = self.class.authenticator(mechanism, *args, **props, &cb)
1118
- send_command("AUTHENTICATE", mechanism) do |resp|
1119
- if resp.instance_of?(ContinuationRequest)
1120
- data = authenticator.process(resp.data.text.unpack("m")[0])
1121
- s = [data].pack("m0")
1122
- send_string_data(s)
1123
- put_string(CRLF)
1124
- end
1125
- end
1493
+ # Although servers should list all supported \SASL mechanisms, they may
1494
+ # allow authentication with an unlisted +mechanism+.
1495
+ #
1496
+ # If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported
1497
+ # and the appropriate <tt>"AUTH=#{mechanism}"</tt> capability is present,
1498
+ # an "initial response" may be sent as an argument to the +AUTHENTICATE+
1499
+ # command, saving a round-trip. The SASL exchange allows for server
1500
+ # challenges and client responses, but many mechanisms expect the client to
1501
+ # "respond" first. The initial response will only be sent for
1502
+ # "client-first" mechanisms.
1503
+ #
1504
+ # Server capabilities may change after #starttls, #login, and #authenticate.
1505
+ # Previously cached #capabilities will be cleared when this method
1506
+ # completes. If the TaggedResponse to #authenticate includes updated
1507
+ # capabilities, they will be cached.
1508
+ def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback)
1509
+ sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback)
1510
+ .tap do state_authenticated! _1 end
1126
1511
  end
1127
1512
 
1128
1513
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1130,16 +1515,21 @@ module Net
1130
1515
  # this +user+. If successful, the connection enters the "_authenticated_"
1131
1516
  # state.
1132
1517
  #
1133
- # Using #authenticate is generally preferred over #login. The LOGIN command
1134
- # is not the same as #authenticate with the "LOGIN" +mechanism+.
1518
+ # Using #authenticate {should be
1519
+ # preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command]
1520
+ # over #login. The LOGIN command is not the same as #authenticate with the
1521
+ # "LOGIN" +mechanism+.
1135
1522
  #
1136
1523
  # A Net::IMAP::NoResponseError is raised if authentication fails.
1137
1524
  #
1138
1525
  # Related: #authenticate, #starttls
1139
1526
  #
1140
1527
  # ==== Capabilities
1141
- # Clients MUST NOT call #login if +LOGINDISABLED+ is listed with the
1142
- # capabilities.
1528
+ #
1529
+ # An IMAP client MUST NOT call #login when the server advertises the
1530
+ # +LOGINDISABLED+ capability. By default, Net::IMAP will raise a
1531
+ # LoginDisabledError when that capability is present. See
1532
+ # Config#enforce_logindisabled.
1143
1533
  #
1144
1534
  # Server capabilities may change after #starttls, #login, and #authenticate.
1145
1535
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1147,35 +1537,54 @@ module Net
1147
1537
  # ResponseCode.
1148
1538
  #
1149
1539
  def login(user, password)
1540
+ if enforce_logindisabled? && capability?("LOGINDISABLED")
1541
+ raise LoginDisabledError
1542
+ end
1150
1543
  send_command("LOGIN", user, password)
1544
+ .tap do state_authenticated! _1 end
1151
1545
  end
1152
1546
 
1153
1547
  # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
1154
1548
  # to select a +mailbox+ so that messages in the +mailbox+ can be accessed.
1155
1549
  #
1156
1550
  # After you have selected a mailbox, you may retrieve the number of items in
1157
- # that mailbox from <tt>imap.responses["EXISTS"][-1]</tt>, and the number of
1158
- # recent messages from <tt>imap.responses["RECENT"][-1]</tt>. Note that
1159
- # these values can change if new messages arrive during a session or when
1160
- # existing messages are expunged; see #add_response_handler for a way to
1161
- # detect these events.
1551
+ # that mailbox from <tt>imap.responses("EXISTS", &:last)</tt>, and the
1552
+ # number of recent messages from <tt>imap.responses("RECENT", &:last)</tt>.
1553
+ # Note that these values can change if new messages arrive during a session
1554
+ # or when existing messages are expunged; see #add_response_handler for a
1555
+ # way to detect these events.
1556
+ #
1557
+ # When the +condstore+ keyword argument is true, the server is told to
1558
+ # enable the extension. If +mailbox+ supports persistence of mod-sequences,
1559
+ # the +HIGHESTMODSEQ+ ResponseCode will be sent as an untagged response to
1560
+ # #select and all `FETCH` responses will include FetchData#modseq.
1561
+ # Otherwise, the +NOMODSEQ+ ResponseCode will be sent.
1162
1562
  #
1163
1563
  # A Net::IMAP::NoResponseError is raised if the mailbox does not
1164
1564
  # exist or is for some reason non-selectable.
1165
1565
  #
1166
1566
  # Related: #examine
1167
1567
  #
1168
- # ===== Capabilities
1568
+ # ==== Capabilities
1169
1569
  #
1170
1570
  # If [UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]] is supported,
1171
1571
  # the server may return an untagged "NO" response with a "UIDNOTSTICKY"
1172
1572
  # response code indicating that the mailstore does not support persistent
1173
1573
  # UIDs:
1174
- # @responses["NO"].last.code.name == "UIDNOTSTICKY"
1175
- def select(mailbox)
1574
+ # imap.responses("NO", &:last)&.code&.name == "UIDNOTSTICKY"
1575
+ #
1576
+ # If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported,
1577
+ # the +condstore+ keyword parameter may be used.
1578
+ # imap.select("mbox", condstore: true)
1579
+ # modseq = imap.responses("HIGHESTMODSEQ", &:last)
1580
+ def select(mailbox, condstore: false)
1581
+ args = ["SELECT", mailbox]
1582
+ args << ["CONDSTORE"] if condstore
1176
1583
  synchronize do
1584
+ state_unselected! # implicitly closes current mailbox
1177
1585
  @responses.clear
1178
- send_command("SELECT", mailbox)
1586
+ send_command(*args)
1587
+ .tap do state_selected! end
1179
1588
  end
1180
1589
  end
1181
1590
 
@@ -1188,10 +1597,14 @@ module Net
1188
1597
  # exist or is for some reason non-examinable.
1189
1598
  #
1190
1599
  # Related: #select
1191
- def examine(mailbox)
1600
+ def examine(mailbox, condstore: false)
1601
+ args = ["EXAMINE", mailbox]
1602
+ args << ["CONDSTORE"] if condstore
1192
1603
  synchronize do
1604
+ state_unselected! # implicitly closes current mailbox
1193
1605
  @responses.clear
1194
- send_command("EXAMINE", mailbox)
1606
+ send_command(*args)
1607
+ .tap do state_selected! end
1195
1608
  end
1196
1609
  end
1197
1610
 
@@ -1261,10 +1674,10 @@ module Net
1261
1674
  # to the client. +refname+ provides a context (for instance, a base
1262
1675
  # directory in a directory-based mailbox hierarchy). +mailbox+ specifies a
1263
1676
  # mailbox or (via wildcards) mailboxes under that context. Two wildcards
1264
- # may be used in +mailbox+: '*', which matches all characters *including*
1265
- # the hierarchy delimiter (for instance, '/' on a UNIX-hosted
1266
- # directory-based mailbox hierarchy); and '%', which matches all characters
1267
- # *except* the hierarchy delimiter.
1677
+ # may be used in +mailbox+: <tt>"*"</tt>, which matches all characters
1678
+ # *including* the hierarchy delimiter (for instance, "/" on a UNIX-hosted
1679
+ # directory-based mailbox hierarchy); and <tt>"%"</tt>, which matches all
1680
+ # characters *except* the hierarchy delimiter.
1268
1681
  #
1269
1682
  # If +refname+ is empty, +mailbox+ is used directly to determine
1270
1683
  # which mailboxes to match. If +mailbox+ is empty, the root
@@ -1274,7 +1687,7 @@ module Net
1274
1687
  #
1275
1688
  # Related: #lsub, MailboxList
1276
1689
  #
1277
- # ===== For example:
1690
+ # ==== For example:
1278
1691
  #
1279
1692
  # imap.create("foo/bar")
1280
1693
  # imap.create("foo/baz")
@@ -1289,7 +1702,7 @@ module Net
1289
1702
  def list(refname, mailbox)
1290
1703
  synchronize do
1291
1704
  send_command("LIST", refname, mailbox)
1292
- return @responses.delete("LIST")
1705
+ clear_responses("LIST")
1293
1706
  end
1294
1707
  end
1295
1708
 
@@ -1312,23 +1725,22 @@ module Net
1312
1725
  # servers, then folder creation (and listing, moving, etc) can lead to
1313
1726
  # errors.
1314
1727
  #
1315
- # From RFC2342:
1316
- #
1317
- # Although typically a server will support only a single Personal
1728
+ # From RFC2342[https://www.rfc-editor.org/rfc/rfc2342]:
1729
+ # >>>
1730
+ # <em>Although typically a server will support only a single Personal
1318
1731
  # Namespace, and a single Other User's Namespace, circumstances exist
1319
1732
  # where there MAY be multiples of these, and a client MUST be prepared
1320
1733
  # for them. If a client is configured such that it is required to create
1321
1734
  # a certain mailbox, there can be circumstances where it is unclear which
1322
1735
  # Personal Namespaces it should create the mailbox in. In these
1323
1736
  # situations a client SHOULD let the user select which namespaces to
1324
- # create the mailbox in.
1737
+ # create the mailbox in.</em>
1325
1738
  #
1326
1739
  # Related: #list, Namespaces, Namespace
1327
1740
  #
1328
- # ===== For example:
1741
+ # ==== For example:
1329
1742
  #
1330
- # capabilities = imap.capability
1331
- # if capabilities.include?("NAMESPACE")
1743
+ # if capable?("NAMESPACE")
1332
1744
  # namespaces = imap.namespace
1333
1745
  # if namespace = namespaces.personal.first
1334
1746
  # prefix = namespace.prefix # e.g. "" or "INBOX."
@@ -1340,14 +1752,14 @@ module Net
1340
1752
  # end
1341
1753
  # end
1342
1754
  #
1343
- # ===== Capabilities
1755
+ # ==== Capabilities
1344
1756
  #
1345
- # The server's capabilities must include +NAMESPACE+
1346
- # [RFC2342[https://tools.ietf.org/html/rfc2342]].
1757
+ # The server's capabilities must include either +IMAP4rev2+ or +NAMESPACE+
1758
+ # [RFC2342[https://www.rfc-editor.org/rfc/rfc2342]].
1347
1759
  def namespace
1348
1760
  synchronize do
1349
1761
  send_command("NAMESPACE")
1350
- return @responses.delete("NAMESPACE")[-1]
1762
+ clear_responses("NAMESPACE").last
1351
1763
  end
1352
1764
  end
1353
1765
 
@@ -1379,7 +1791,7 @@ module Net
1379
1791
  #
1380
1792
  # Related: #list, MailboxList
1381
1793
  #
1382
- # ===== Capabilities
1794
+ # ==== Capabilities
1383
1795
  #
1384
1796
  # The server's capabilities must include +XLIST+,
1385
1797
  # a deprecated Gmail extension (replaced by +SPECIAL-USE+).
@@ -1391,7 +1803,7 @@ module Net
1391
1803
  def xlist(refname, mailbox)
1392
1804
  synchronize do
1393
1805
  send_command("XLIST", refname, mailbox)
1394
- return @responses.delete("XLIST")
1806
+ clear_responses("XLIST")
1395
1807
  end
1396
1808
  end
1397
1809
 
@@ -1402,16 +1814,16 @@ module Net
1402
1814
  #
1403
1815
  # Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota
1404
1816
  #
1405
- # ===== Capabilities
1817
+ # ==== Capabilities
1406
1818
  #
1407
1819
  # The server's capabilities must include +QUOTA+
1408
- # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1820
+ # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1409
1821
  def getquotaroot(mailbox)
1410
1822
  synchronize do
1411
1823
  send_command("GETQUOTAROOT", mailbox)
1412
1824
  result = []
1413
- result.concat(@responses.delete("QUOTAROOT"))
1414
- result.concat(@responses.delete("QUOTA"))
1825
+ result.concat(clear_responses("QUOTAROOT"))
1826
+ result.concat(clear_responses("QUOTA"))
1415
1827
  return result
1416
1828
  end
1417
1829
  end
@@ -1423,14 +1835,14 @@ module Net
1423
1835
  #
1424
1836
  # Related: #getquotaroot, #setquota, MailboxQuota
1425
1837
  #
1426
- # ===== Capabilities
1838
+ # ==== Capabilities
1427
1839
  #
1428
1840
  # The server's capabilities must include +QUOTA+
1429
- # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1841
+ # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1430
1842
  def getquota(mailbox)
1431
1843
  synchronize do
1432
1844
  send_command("GETQUOTA", mailbox)
1433
- return @responses.delete("QUOTA")
1845
+ clear_responses("QUOTA")
1434
1846
  end
1435
1847
  end
1436
1848
 
@@ -1441,10 +1853,10 @@ module Net
1441
1853
  #
1442
1854
  # Related: #getquota, #getquotaroot
1443
1855
  #
1444
- # ===== Capabilities
1856
+ # ==== Capabilities
1445
1857
  #
1446
1858
  # The server's capabilities must include +QUOTA+
1447
- # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1859
+ # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1448
1860
  def setquota(mailbox, quota)
1449
1861
  if quota.nil?
1450
1862
  data = '()'
@@ -1461,10 +1873,10 @@ module Net
1461
1873
  #
1462
1874
  # Related: #getacl
1463
1875
  #
1464
- # ===== Capabilities
1876
+ # ==== Capabilities
1465
1877
  #
1466
1878
  # The server's capabilities must include +ACL+
1467
- # [RFC4314[https://tools.ietf.org/html/rfc4314]].
1879
+ # [RFC4314[https://www.rfc-editor.org/rfc/rfc4314]].
1468
1880
  def setacl(mailbox, user, rights)
1469
1881
  if rights.nil?
1470
1882
  send_command("SETACL", mailbox, user, "")
@@ -1479,14 +1891,14 @@ module Net
1479
1891
  #
1480
1892
  # Related: #setacl, MailboxACLItem
1481
1893
  #
1482
- # ===== Capabilities
1894
+ # ==== Capabilities
1483
1895
  #
1484
1896
  # The server's capabilities must include +ACL+
1485
- # [RFC4314[https://tools.ietf.org/html/rfc4314]].
1897
+ # [RFC4314[https://www.rfc-editor.org/rfc/rfc4314]].
1486
1898
  def getacl(mailbox)
1487
1899
  synchronize do
1488
1900
  send_command("GETACL", mailbox)
1489
- return @responses.delete("ACL")[-1]
1901
+ clear_responses("ACL").last
1490
1902
  end
1491
1903
  end
1492
1904
 
@@ -1501,31 +1913,74 @@ module Net
1501
1913
  def lsub(refname, mailbox)
1502
1914
  synchronize do
1503
1915
  send_command("LSUB", refname, mailbox)
1504
- return @responses.delete("LSUB")
1916
+ clear_responses("LSUB")
1505
1917
  end
1506
1918
  end
1507
1919
 
1508
- # Sends a {STATUS commands [IMAP4rev1 §6.3.10]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.10]
1920
+ # Sends a {STATUS command [IMAP4rev1 §6.3.10]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.10]
1509
1921
  # and returns the status of the indicated +mailbox+. +attr+ is a list of one
1510
- # or more attributes whose statuses are to be requested. Supported
1511
- # attributes include:
1922
+ # or more attributes whose statuses are to be requested.
1512
1923
  #
1513
- # MESSAGES:: the number of messages in the mailbox.
1514
- # RECENT:: the number of recent messages in the mailbox.
1515
- # UNSEEN:: the number of unseen messages in the mailbox.
1924
+ # The return value is a hash of attributes. Most status attributes return
1925
+ # integer values, but some return other value types (documented below).
1516
1926
  #
1517
- # The return value is a hash of attributes. For example:
1927
+ # A Net::IMAP::NoResponseError is raised if status values
1928
+ # for +mailbox+ cannot be returned; for instance, because it
1929
+ # does not exist.
1930
+ #
1931
+ # ==== Supported attributes
1932
+ #
1933
+ # +MESSAGES+:: The number of messages in the mailbox.
1934
+ #
1935
+ # +UIDNEXT+:: The next unique identifier value of the mailbox.
1936
+ #
1937
+ # +UIDVALIDITY+:: The unique identifier validity value of the mailbox.
1938
+ #
1939
+ # +UNSEEN+:: The number of messages without the <tt>\Seen</tt> flag.
1940
+ #
1941
+ # +DELETED+:: The number of messages with the <tt>\Deleted</tt> flag.
1942
+ #
1943
+ # +SIZE+::
1944
+ # The approximate size of the mailbox---must be greater than or equal to
1945
+ # the sum of all messages' +RFC822.SIZE+ fetch item values.
1946
+ #
1947
+ # +HIGHESTMODSEQ+::
1948
+ # The highest mod-sequence value of all messages in the mailbox. See
1949
+ # +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
1950
+ #
1951
+ # +MAILBOXID+::
1952
+ # A server-allocated unique _string_ identifier for the mailbox. See
1953
+ # +OBJECTID+ {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
1954
+ #
1955
+ # +RECENT+::
1956
+ # The number of messages with the <tt>\Recent</tt> flag.
1957
+ # _NOTE:_ +RECENT+ was removed from IMAP4rev2.
1958
+ #
1959
+ # Unsupported attributes may be requested. The attribute value will be
1960
+ # either an Integer or an ExtensionData object.
1961
+ #
1962
+ # ==== For example:
1518
1963
  #
1519
1964
  # p imap.status("inbox", ["MESSAGES", "RECENT"])
1520
1965
  # #=> {"RECENT"=>0, "MESSAGES"=>44}
1521
1966
  #
1522
- # A Net::IMAP::NoResponseError is raised if status values
1523
- # for +mailbox+ cannot be returned; for instance, because it
1524
- # does not exist.
1967
+ # ==== Capabilities
1968
+ #
1969
+ # +SIZE+ requires the server's capabilities to include either +IMAP4rev2+ or
1970
+ # <tt>STATUS=SIZE</tt>
1971
+ # {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html].
1972
+ #
1973
+ # +DELETED+ requires the server's capabilities to include +IMAP4rev2+.
1974
+ #
1975
+ # +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+
1976
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
1977
+ #
1978
+ # +MAILBOXID+ requires the server's capabilities to include +OBJECTID+
1979
+ # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
1525
1980
  def status(mailbox, attr)
1526
1981
  synchronize do
1527
1982
  send_command("STATUS", mailbox, attr)
1528
- return @responses.delete("STATUS")[-1].attr
1983
+ clear_responses("STATUS").last&.attr
1529
1984
  end
1530
1985
  end
1531
1986
 
@@ -1549,7 +2004,7 @@ module Net
1549
2004
  # not exist (it is not created automatically), or if the flags,
1550
2005
  # date_time, or message arguments contain errors.
1551
2006
  #
1552
- # ===== Capabilities
2007
+ # ==== Capabilities
1553
2008
  #
1554
2009
  # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
1555
2010
  # supported and the destination supports persistent UIDs, the server's
@@ -1588,6 +2043,7 @@ module Net
1588
2043
  # Related: #unselect
1589
2044
  def close
1590
2045
  send_command("CLOSE")
2046
+ .tap do state_authenticated! end
1591
2047
  end
1592
2048
 
1593
2049
  # Sends an {UNSELECT command [RFC3691 §2]}[https://www.rfc-editor.org/rfc/rfc3691#section-3]
@@ -1598,146 +2054,537 @@ module Net
1598
2054
  #
1599
2055
  # Related: #close
1600
2056
  #
1601
- # ===== Capabilities
2057
+ # ==== Capabilities
1602
2058
  #
1603
- # The server's capabilities must include +UNSELECT+
1604
- # [RFC3691[https://tools.ietf.org/html/rfc3691]].
2059
+ # The server's capabilities must include either +IMAP4rev2+ or +UNSELECT+
2060
+ # [RFC3691[https://www.rfc-editor.org/rfc/rfc3691]].
1605
2061
  def unselect
1606
2062
  send_command("UNSELECT")
2063
+ .tap do state_authenticated! end
1607
2064
  end
1608
2065
 
2066
+ # call-seq:
2067
+ # expunge -> array of message sequence numbers
2068
+ # expunge -> VanishedData of UIDs
2069
+ #
1609
2070
  # Sends an {EXPUNGE command [IMAP4rev1 §6.4.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.3]
1610
- # Sends a EXPUNGE command to permanently remove from the currently
1611
- # selected mailbox all messages that have the \Deleted flag set.
2071
+ # to permanently remove all messages with the +\Deleted+ flag from the
2072
+ # currently selected mailbox.
2073
+ #
2074
+ # Returns either an array of expunged message <em>sequence numbers</em> or
2075
+ # (when the appropriate capability is enabled) VanishedData of expunged
2076
+ # UIDs. Previously unhandled +EXPUNGE+ or +VANISHED+ responses are merged
2077
+ # with the direct response to this command. <tt>VANISHED (EARLIER)</tt>
2078
+ # responses will _not_ be merged.
2079
+ #
2080
+ # When no messages have been expunged, an empty array is returned,
2081
+ # regardless of which extensions are enabled. In a future release, an empty
2082
+ # VanishedData may be returned, based on the currently enabled extensions.
1612
2083
  #
1613
2084
  # Related: #uid_expunge
2085
+ #
2086
+ # ==== Capabilities
2087
+ #
2088
+ # When either QRESYNC[https://www.rfc-editor.org/rfc/rfc7162] or
2089
+ # UIDONLY[https://www.rfc-editor.org/rfc/rfc9586] are enabled, #expunge
2090
+ # returns VanishedData, which contains UIDs---<em>not message sequence
2091
+ # numbers</em>.
1614
2092
  def expunge
1615
- synchronize do
1616
- send_command("EXPUNGE")
1617
- return @responses.delete("EXPUNGE")
1618
- end
2093
+ expunge_internal("EXPUNGE")
1619
2094
  end
1620
2095
 
2096
+ # call-seq:
2097
+ # uid_expunge{uid_set) -> array of message sequence numbers
2098
+ # uid_expunge{uid_set) -> VanishedData of UIDs
2099
+ #
1621
2100
  # Sends a {UID EXPUNGE command [RFC4315 §2.1]}[https://www.rfc-editor.org/rfc/rfc4315#section-2.1]
1622
2101
  # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9]
1623
2102
  # to permanently remove all messages that have both the <tt>\\Deleted</tt>
1624
2103
  # flag set and a UID that is included in +uid_set+.
1625
2104
  #
2105
+ # Returns the same result type as #expunge.
2106
+ #
1626
2107
  # By using #uid_expunge instead of #expunge when resynchronizing with
1627
2108
  # the server, the client can ensure that it does not inadvertantly
1628
2109
  # remove any messages that have been marked as <tt>\\Deleted</tt> by other
1629
2110
  # clients between the time that the client was last connected and
1630
2111
  # the time the client resynchronizes.
1631
2112
  #
1632
- # *Note:*
1633
- # >>>
1634
- # Although the command takes a set of UIDs for its argument, the
1635
- # server still returns regular EXPUNGE responses, which contain
1636
- # a <em>sequence number</em>. These will be deleted from
1637
- # #responses and this method returns them as an array of
1638
- # <em>sequence number</em> integers.
1639
- #
1640
2113
  # Related: #expunge
1641
2114
  #
1642
- # ===== Capabilities
2115
+ # ==== Capabilities
1643
2116
  #
1644
- # The server's capabilities must include +UIDPLUS+
2117
+ # The server's capabilities must include either +IMAP4rev2+ or +UIDPLUS+
1645
2118
  # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]].
2119
+ #
2120
+ # Otherwise, #uid_expunge is updated by extensions in the same way as
2121
+ # #expunge.
1646
2122
  def uid_expunge(uid_set)
1647
- synchronize do
1648
- send_command("UID EXPUNGE", MessageSet.new(uid_set))
1649
- return @responses.delete("EXPUNGE")
1650
- end
2123
+ expunge_internal("UID EXPUNGE", SequenceSet.new(uid_set))
1651
2124
  end
1652
2125
 
2126
+ # :call-seq:
2127
+ # search(criteria, charset = nil) -> result
2128
+ # search(criteria, charset: nil, return: nil) -> result
2129
+ #
1653
2130
  # Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4]
1654
- # to search the mailbox for messages that match the given searching
1655
- # criteria, and returns message sequence numbers. +keys+ can either be a
1656
- # string holding the entire search string, or a single-dimension array of
1657
- # search keywords and arguments.
2131
+ # to search the mailbox for messages that match the given search +criteria+,
2132
+ # and returns either a SearchResult or an ESearchResult. SearchResult
2133
+ # inherits from Array (for backward compatibility) but adds
2134
+ # SearchResult#modseq when the +CONDSTORE+ capability has been enabled.
2135
+ # ESearchResult also implements {#to_a}[rdoc-ref:ESearchResult#to_a], for
2136
+ # compatibility with SearchResult.
2137
+ #
2138
+ # +criteria+ is one or more search keys and their arguments, which may be
2139
+ # provided as an array or a string.
2140
+ # See {"Argument translation"}[rdoc-ref:#search@Argument+translation]
2141
+ # and {"Search criteria"}[rdoc-ref:#search@Search+criteria], below.
2142
+ #
2143
+ # +return+ options control what kind of information is returned about
2144
+ # messages matching the search +criteria+. Specifying +return+ should force
2145
+ # the server to return an ESearchResult instead of a SearchResult, but some
2146
+ # servers disobey this requirement. <em>Requires an extended search
2147
+ # capability, such as +ESEARCH+ or +IMAP4rev2+.</em>
2148
+ # See {"Argument translation"}[rdoc-ref:#search@Argument+translation] and
2149
+ # {"Supported return options"}[rdoc-ref:#search@Supported+return+options],
2150
+ # below.
2151
+ #
2152
+ # +charset+ is the name of the {registered character
2153
+ # set}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
2154
+ # used by strings in the search +criteria+. When +charset+ isn't specified,
2155
+ # either <tt>"US-ASCII"</tt> or <tt>"UTF-8"</tt> is assumed, depending on
2156
+ # the server's capabilities.
2157
+ #
2158
+ # _NOTE:_ Return options and charset may be sent as part of +criteria+. Do
2159
+ # not use the +return+ or +charset+ arguments when either return options or
2160
+ # charset are embedded in +criteria+.
1658
2161
  #
1659
2162
  # Related: #uid_search
1660
2163
  #
1661
- # ===== Search criteria
2164
+ # ==== For example:
2165
+ #
2166
+ # imap.search(["SUBJECT", "hello", "NOT", "SEEN"])
2167
+ # #=> [1, 6, 7, 8]
1662
2168
  #
1663
- # For a full list of search criteria,
2169
+ # The following assumes the server supports +ESEARCH+ and +CONDSTORE+:
2170
+ #
2171
+ # result = imap.uid_search(["UID", 12345.., "MODSEQ", 620_162_338],
2172
+ # return: %w(all count min max))
2173
+ # # => #<data Net::IMAP::ESearchResult tag="RUBY0123", uid=true,
2174
+ # # data=[["ALL", Net::IMAP::SequenceSet["12346:12349,22222:22230"]],
2175
+ # # ["COUNT", 13], ["MIN", 12346], ["MAX", 22230],
2176
+ # # ["MODSEQ", 917162488]]>
2177
+ # result.to_a # => [12346, 12347, 12348, 12349, 22222, 22223, 22224,
2178
+ # # 22225, 22226, 22227, 22228, 22229, 22230]
2179
+ # result.uid? # => true
2180
+ # result.count # => 13
2181
+ # result.min # => 12346
2182
+ # result.max # => 22230
2183
+ # result.modseq # => 917162488
2184
+ #
2185
+ # Using +return+ options to limit the result to only min, max, and count:
2186
+ #
2187
+ # result = imap.uid_search(["UID", 12345..,], return: %w(count min max))
2188
+ # # => #<data Net::IMAP::ESearchResult tag="RUBY0124", uid=true,
2189
+ # # data=[["COUNT", 13], ["MIN", 12346], ["MAX", 22230]]>
2190
+ # result.to_a # => []
2191
+ # result.count # => 13
2192
+ # result.min # => 12346
2193
+ # result.max # => 22230
2194
+ #
2195
+ # Return options and charset may be sent as keyword args or embedded in the
2196
+ # +criteria+ arg, but they must be in the correct order: <tt>"RETURN (...)
2197
+ # CHARSET ... criteria..."</tt>. The following searches
2198
+ # send the exact same command to the server:
2199
+ #
2200
+ # # Return options and charset as keyword arguments (preferred)
2201
+ # imap.search(%w(OR UNSEEN FLAGGED), return: %w(MIN MAX), charset: "UTF-8")
2202
+ # # Embedding return and charset in the criteria array
2203
+ # imap.search(["RETURN", %w(MIN MAX), "CHARSET", "UTF-8", *%w(OR UNSEEN FLAGGED)])
2204
+ # # Embedding return and charset in the criteria string
2205
+ # imap.search("RETURN (MIN MAX) CHARSET UTF-8 OR UNSEEN FLAGGED")
2206
+ #
2207
+ # Sending charset as the second positional argument is supported for
2208
+ # backward compatibility. Future versions may print a deprecation warning:
2209
+ # imap.search(%w(OR UNSEEN FLAGGED), "UTF-8", return: %w(MIN MAX))
2210
+ #
2211
+ # ==== Argument translation
2212
+ #
2213
+ # [+return+ options]
2214
+ # Must be an Array. Return option names may be either strings or symbols.
2215
+ # +Range+ elements which begin and end with negative integers are encoded
2216
+ # for use with +PARTIAL+--any other ranges are converted to SequenceSet.
2217
+ # Unlike +criteria+, other return option arguments are not automatically
2218
+ # converted to SequenceSet.
2219
+ #
2220
+ # [When +criteria+ is an Array]
2221
+ # When the array begins with <tt>"RETURN"</tt> (case insensitive), the
2222
+ # second array element is translated like the +return+ parameter (as
2223
+ # described above).
2224
+ #
2225
+ # Every other member is a +SEARCH+ command argument:
2226
+ # [SequenceSet]
2227
+ # Encoded as an \IMAP +sequence-set+ with SequenceSet#valid_string.
2228
+ # [Set, Range, <tt>-1</tt>, +:*+, responds to +#to_sequence_set+]
2229
+ # Converted to SequenceSet for validation and encoding.
2230
+ # [nested sequence-set +Array+]
2231
+ # When every element in a nested array is one of the above types, a
2232
+ # positive +Integer+, a sequence-set formatted +String+, or a deeply
2233
+ # nested +Array+ of these same types, the array will be converted to
2234
+ # SequenceSet for validation and encoding.
2235
+ # [Any other nested +Array+]
2236
+ # Otherwise, a nested array is encoded as a parenthesized list, to
2237
+ # combine multiple search keys (e.g., for use with +OR+ and +NOT+).
2238
+ # [+String+]
2239
+ # Sent verbatim when it is a valid \IMAP +atom+, and encoded as an \IMAP
2240
+ # +quoted+ or +literal+ string otherwise. Every standard search key
2241
+ # name is a valid \IMAP +atom+ and every standard search key string
2242
+ # argument is an +astring+ which may be encoded as +atom+, +quoted+, or
2243
+ # +literal+.
2244
+ #
2245
+ # *Note:* <tt>*</tt> is not a valid \IMAP +atom+ character. Any string
2246
+ # containing <tt>*</tt> will be encoded as a +quoted+ string, _not_ a
2247
+ # +sequence-set+.
2248
+ # [+Integer+ (except for <tt>-1</tt>)]
2249
+ # Encoded using +#to_s+.
2250
+ # [+Date+]
2251
+ # Encoded as an \IMAP date (see ::encode_date).
2252
+ #
2253
+ # [When +criteria+ is a String]
2254
+ # +criteria+ will be sent directly to the server <em>without any
2255
+ # validation or encoding</em>.
2256
+ #
2257
+ # <em>*WARNING:* This is vulnerable to injection attacks when external
2258
+ # inputs are used.</em>
2259
+ #
2260
+ # ==== Supported return options
2261
+ #
2262
+ # For full definitions of the standard return options and return data, see
2263
+ # the relevant RFCs.
2264
+ #
2265
+ # [+ALL+]
2266
+ # Returns ESearchResult#all with a SequenceSet of all matching sequence
2267
+ # numbers or UIDs. This is the default, when return options are empty.
2268
+ #
2269
+ # For compatibility with SearchResult, ESearchResult#to_a returns an
2270
+ # Array of message sequence numbers or UIDs.
2271
+ #
2272
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2273
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2274
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2275
+ #
2276
+ # [+COUNT+]
2277
+ # Returns ESearchResult#count with the number of matching messages.
2278
+ #
2279
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2280
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2281
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2282
+ #
2283
+ # [+MAX+]
2284
+ # Returns ESearchResult#max with the highest matching sequence number or
2285
+ # UID.
2286
+ #
2287
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2288
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2289
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2290
+ #
2291
+ # [+MIN+]
2292
+ # Returns ESearchResult#min with the lowest matching sequence number or
2293
+ # UID.
2294
+ #
2295
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2296
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2297
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2298
+ #
2299
+ # [+PARTIAL+ _range_]
2300
+ # Returns ESearchResult#partial with a SequenceSet of a subset of
2301
+ # matching sequence numbers or UIDs, as selected by _range_. As with
2302
+ # sequence numbers, the first result is +1+: <tt>1..500</tt> selects the
2303
+ # first 500 search results (in mailbox order), <tt>501..1000</tt> the
2304
+ # second 500, and so on. _range_ may also be negative: <tt>-500..-1</tt>
2305
+ # selects the last 500 search results.
2306
+ #
2307
+ # <em>Requires either the <tt>CONTEXT=SEARCH</tt> or +PARTIAL+ capabability.</em>
2308
+ # {[RFC5267]}[https://rfc-editor.org/rfc/rfc5267]
2309
+ # {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394]
2310
+ #
2311
+ # ===== +MODSEQ+ return data
2312
+ #
2313
+ # ESearchResult#modseq return data does not have a corresponding return
2314
+ # option. Instead, it is returned if the +MODSEQ+ search key is used or
2315
+ # when the +CONDSTORE+ extension is enabled for the selected mailbox.
2316
+ # See [{RFC4731 §3.2}[https://www.rfc-editor.org/rfc/rfc4731#section-3.2]]
2317
+ # or [{RFC7162 §2.1.5}[https://www.rfc-editor.org/rfc/rfc7162#section-3.1.5]].
2318
+ #
2319
+ # ===== +RFC4466+ compatible extensions
2320
+ #
2321
+ # {RFC4466 §2.6}[https://www.rfc-editor.org/rfc/rfc4466.html#section-2.6]
2322
+ # defines standard syntax for search extensions. Net::IMAP allows sending
2323
+ # unsupported search return options and will parse unsupported search
2324
+ # extensions' return values into ExtensionData. Please note that this is an
2325
+ # intentionally _unstable_ API. Future releases may return different
2326
+ # (incompatible) objects, <em>without deprecation or warning</em>.
2327
+ #
2328
+ # ==== Search keys
2329
+ #
2330
+ # For full definitions of the standard search +criteria+,
1664
2331
  # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]],
1665
2332
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
1666
2333
  # in addition to documentation for
1667
- # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
1668
- # reported by #capability which may define additional search filters, e.g:
2334
+ # any #capabilities which may define additional search filters, such as
1669
2335
  # +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
1670
- # +SAVEDATE+. The following are some common search criteria:
2336
+ # +SAVEDATE+.
1671
2337
  #
1672
- # <message set>:: a set of message sequence numbers. "<tt>,</tt>" indicates
1673
- # an interval, "+:+" indicates a range. For instance,
1674
- # "<tt>2,10:12,15</tt>" means "<tt>2,10,11,12,15</tt>".
2338
+ # With the exception of <em>sequence-set</em> and <em>parenthesized
2339
+ # list</em>, all search keys are composed of prefix label with zero or more
2340
+ # arguments. The number and type of arguments is specific to each search
2341
+ # key.
1675
2342
  #
1676
- # BEFORE <date>:: messages with an internal date strictly before
1677
- # <b><date></b>. The date argument has a format similar
1678
- # to <tt>8-Aug-2002</tt>, and can be formatted using
1679
- # Net::IMAP.format_date.
2343
+ # ===== Search keys that match all messages
1680
2344
  #
1681
- # BODY <string>:: messages that contain <string> within their body.
2345
+ # [+ALL+]
2346
+ # The default initial key. Matches every message in the mailbox.
1682
2347
  #
1683
- # CC <string>:: messages containing <string> in their CC field.
2348
+ # [+SAVEDATESUPPORTED+]
2349
+ # Matches every message in the mailbox when the mailbox supports the save
2350
+ # date attribute. Otherwise, it matches no messages.
1684
2351
  #
1685
- # FROM <string>:: messages that contain <string> in their FROM field.
2352
+ # <em>Requires +SAVEDATE+ capability</em>.
2353
+ # {[RFC8514]}[https://www.rfc-editor.org/rfc/rfc8514.html#section-4.3]
1686
2354
  #
1687
- # NEW:: messages with the \Recent, but not the \Seen, flag set.
2355
+ # ===== Sequence set search keys
1688
2356
  #
1689
- # NOT <search-key>:: negate the following search key.
2357
+ # [_sequence-set_]
2358
+ # Matches messages with message sequence numbers in _sequence-set_.
1690
2359
  #
1691
- # OR <search-key> <search-key>:: "or" two search keys together.
2360
+ # _Note:_ this search key has no label.
1692
2361
  #
1693
- # ON <date>:: messages with an internal date exactly equal to <date>,
1694
- # which has a format similar to 8-Aug-2002.
2362
+ # <em>+UIDONLY+ must *not* be enabled.</em>
2363
+ # {[RFC9586]}[https://www.rfc-editor.org/rfc/rfc9586.html]
1695
2364
  #
1696
- # SINCE <date>:: messages with an internal date on or after <date>.
2365
+ # [+UID+ _sequence-set_]
2366
+ # Matches messages with a UID in _sequence-set_.
1697
2367
  #
1698
- # SUBJECT <string>:: messages with <string> in their subject.
2368
+ # ===== Compound search keys
1699
2369
  #
1700
- # TO <string>:: messages with <string> in their TO field.
2370
+ # [(_search-key_ _search-key_...)]
2371
+ # Combines one or more _search-key_ arguments to match
2372
+ # messages which match all contained search keys. Useful for +OR+, +NOT+,
2373
+ # and other search keys with _search-key_ arguments.
1701
2374
  #
1702
- # ===== For example:
2375
+ # _Note:_ this search key has no label.
1703
2376
  #
1704
- # p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
1705
- # #=> [1, 6, 7, 8]
2377
+ # [+OR+ _search-key_ _search-key_]
2378
+ # Matches messages which match either _search-key_ argument.
2379
+ #
2380
+ # [+NOT+ _search-key_]
2381
+ # Matches messages which do not match _search-key_.
2382
+ #
2383
+ # [+FUZZY+ _search-key_]
2384
+ # Uses fuzzy matching for the specified search key.
2385
+ #
2386
+ # <em>Requires <tt>SEARCH=FUZZY</tt> capability.</em>
2387
+ # {[RFC6203]}[https://www.rfc-editor.org/rfc/rfc6203.html#section-6].
2388
+ #
2389
+ # ===== Flags search keys
2390
+ #
2391
+ # [+ANSWERED+, +UNANSWERED+]
2392
+ # Matches messages with or without the <tt>\\Answered</tt> flag.
2393
+ # [+DELETED+, +UNDELETED+]
2394
+ # Matches messages with or without the <tt>\\Deleted</tt> flag.
2395
+ # [+DRAFT+, +UNDRAFT+]
2396
+ # Matches messages with or without the <tt>\\Draft</tt> flag.
2397
+ # [+FLAGGED+, +UNFLAGGED+]
2398
+ # Matches messages with or without the <tt>\\Flagged</tt> flag.
2399
+ # [+SEEN+, +UNSEEN+]
2400
+ # Matches messages with or without the <tt>\\Seen</tt> flag.
2401
+ # [+KEYWORD+ _keyword_, +UNKEYWORD+ _keyword_]
2402
+ # Matches messages with or without the specified _keyword_.
2403
+ #
2404
+ # [+RECENT+, +UNRECENT+]
2405
+ # Matches messages with or without the <tt>\\Recent</tt> flag.
2406
+ #
2407
+ # *NOTE:* The <tt>\\Recent</tt> flag has been removed from +IMAP4rev2+.
2408
+ # [+NEW+]
2409
+ # Equivalent to <tt>(RECENT UNSEEN)</tt>.
2410
+ #
2411
+ # *NOTE:* The <tt>\\Recent</tt> flag has been removed from +IMAP4rev2+.
2412
+ #
2413
+ # ===== Header field substring search keys
2414
+ #
2415
+ # [+BCC+ _substring_]
2416
+ # Matches when _substring_ is in the envelope's +BCC+ field.
2417
+ # [+CC+ _substring_]
2418
+ # Matches when _substring_ is in the envelope's +CC+ field.
2419
+ # [+FROM+ _substring_]
2420
+ # Matches when _substring_ is in the envelope's +FROM+ field.
2421
+ # [+SUBJECT+ _substring_]
2422
+ # Matches when _substring_ is in the envelope's +SUBJECT+ field.
2423
+ # [+TO+ _substring_]
2424
+ # Matches when _substring_ is in the envelope's +TO+ field.
2425
+ #
2426
+ # [+HEADER+ _field_ _substring_]
2427
+ # Matches when _substring_ is in the specified header _field_.
2428
+ #
2429
+ # ===== Body text search keys
2430
+ # [+BODY+ _string_]
2431
+ # Matches when _string_ is in the body of the message.
2432
+ # Does not match on header fields.
2433
+ #
2434
+ # The server _may_ use flexible matching, rather than simple substring
2435
+ # matches. For example, this may use stemming or match only full words.
2436
+ #
2437
+ # [+TEXT+ _string_]
2438
+ # Matches when _string_ is in the header or body of the message.
2439
+ #
2440
+ # The server _may_ use flexible matching, rather than simple substring
2441
+ # matches. For example, this may use stemming or match only full words.
1706
2442
  #
1707
- def search(keys, charset = nil)
1708
- return search_internal("SEARCH", keys, charset)
2443
+ # ===== Date/Time search keys
2444
+ #
2445
+ # [+SENTBEFORE+ _date_]
2446
+ # [+SENTON+ _date_]
2447
+ # [+SENTSINCE+ _date_]
2448
+ # Matches when the +Date+ header is earlier than, on, or later than _date_.
2449
+ #
2450
+ # [+BEFORE+ _date_]
2451
+ # [+ON+ _date_]
2452
+ # [+SINCE+ _date_]
2453
+ # Matches when the +INTERNALDATE+ is earlier than, on, or later than
2454
+ # _date_.
2455
+ #
2456
+ # [+OLDER+ _interval_]
2457
+ # [+YOUNGER+ _interval_]
2458
+ # Matches when the +INTERNALDATE+ is more/less than _interval_ seconds ago.
2459
+ #
2460
+ # <em>Requires +WITHIN+ capability</em>.
2461
+ # {[RFC5032]}[https://www.rfc-editor.org/rfc/rfc5032.html]
2462
+ #
2463
+ # [+SAVEDBEFORE+ _date_]
2464
+ # [+SAVEDON+ _date_]
2465
+ # [+SAVEDSINCE+ _date_]
2466
+ # Matches when the save date is earlier than, on, or later than _date_.
2467
+ #
2468
+ # <em>Requires +SAVEDATE+ capability.</em>
2469
+ # {[RFC8514]}[https://www.rfc-editor.org/rfc/rfc8514.html#section-4.3]
2470
+ #
2471
+ # ===== Other message attribute search keys
2472
+ #
2473
+ # [+SMALLER+ _bytes_]
2474
+ # [+LARGER+ _bytes_]
2475
+ # Matches when +RFC822.SIZE+ is smaller or larger than _bytes_.
2476
+ #
2477
+ # [+ANNOTATION+ _entry_ _attr_ _value_]
2478
+ # Matches messages that have annotations with entries matching _entry_,
2479
+ # attributes matching _attr_, and _value_ in the attribute's values.
2480
+ #
2481
+ # <em>Requires +ANNOTATE-EXPERIMENT-1+ capability</em>.
2482
+ # {[RFC5257]}[https://www.rfc-editor.org/rfc/rfc5257.html].
2483
+ #
2484
+ # [+FILTER+ _filter_]
2485
+ # References a _filter_ that is stored on the server and matches all
2486
+ # messages which would be matched by that filter's search criteria.
2487
+ #
2488
+ # <em>Requires +FILTERS+ capability</em>.
2489
+ # {[RFC5466]}[https://www.rfc-editor.org/rfc/rfc5466.html#section-3.1]
2490
+ #
2491
+ # [+MODSEQ+ _modseq_]
2492
+ # Matches when +MODSEQ+ is greater than or equal to _modseq_.
2493
+ #
2494
+ # <em>Requires +CONDSTORE+ capability</em>.
2495
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.5].
2496
+ #
2497
+ # [+MODSEQ+ _entry_ _entry-type_ _modseq_]
2498
+ # Matches when a specific metadata _entry_ has been updated since
2499
+ # _modseq_.
2500
+ #
2501
+ # For flags, the corresponding _entry_ name is
2502
+ # <tt>"/flags/#{flag_name}"</tt>, where _flag_name_ includes the
2503
+ # <tt>\\</tt> prefix. _entry-type_ can be one of <tt>"shared"</tt>,
2504
+ # <tt>"priv"</tt> (private), or <tt>"all"</tt>.
2505
+ #
2506
+ # <em>Requires +CONDSTORE+ capability</em>.
2507
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.5].
2508
+ #
2509
+ # [+EMAILID+ _objectid_]
2510
+ # [+THREADID+ _objectid_]
2511
+ # Matches when +EMAILID+/+THREADID+ is equal to _objectid_
2512
+ # (substring matches are not supported).
2513
+ #
2514
+ # <em>Requires +OBJECTID+ capability</em>.
2515
+ # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html#section-6]
2516
+ #
2517
+ # ==== Capabilities
2518
+ #
2519
+ # Return options should only be specified when the server supports
2520
+ # +IMAP4rev2+ or an extension that allows them, such as +ESEARCH+
2521
+ # [RFC4731[https://rfc-editor.org/rfc/rfc4731#section-3.1]].
2522
+ #
2523
+ # When +IMAP4rev2+ is enabled, or when the server supports +IMAP4rev2+ but
2524
+ # not +IMAP4rev1+, ESearchResult is always returned instead of SearchResult.
2525
+ #
2526
+ # If CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html] is supported
2527
+ # and enabled for the selected mailbox, a non-empty SearchResult will
2528
+ # include a +MODSEQ+ value.
2529
+ # imap.select("mbox", condstore: true)
2530
+ # result = imap.search(["SUBJECT", "hi there", "not", "new"])
2531
+ # #=> Net::IMAP::SearchResult[1, 6, 7, 8, modseq: 5594]
2532
+ # result.modseq # => 5594
2533
+ #
2534
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
2535
+ # the +SEARCH+ command is prohibited. Use #uid_search instead.
2536
+ def search(...)
2537
+ search_internal("SEARCH", ...)
1709
2538
  end
1710
2539
 
2540
+ # :call-seq:
2541
+ # uid_search(criteria, charset = nil) -> result
2542
+ # uid_search(criteria, charset: nil, return: nil) -> result
2543
+ #
1711
2544
  # Sends a {UID SEARCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
1712
2545
  # to search the mailbox for messages that match the given searching
1713
2546
  # criteria, and returns unique identifiers (<tt>UID</tt>s).
1714
2547
  #
1715
- # See #search for documentation of search criteria.
1716
- def uid_search(keys, charset = nil)
1717
- return search_internal("UID SEARCH", keys, charset)
2548
+ # Returns a SearchResult object. SearchResult inherits from Array (for
2549
+ # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
2550
+ # capability has been enabled.
2551
+ #
2552
+ # See #search for documentation of parameters.
2553
+ #
2554
+ # ==== Capabilities
2555
+ #
2556
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
2557
+ # #uid_search must be used instead of #search, and the <tt><message
2558
+ # set></tt> search criterion is prohibited. Use +ALL+ or <tt>UID
2559
+ # sequence-set</tt> instead.
2560
+ #
2561
+ # Otherwise, #uid_search is updated by extensions in the same way as
2562
+ # #search.
2563
+ def uid_search(...)
2564
+ search_internal("UID SEARCH", ...)
1718
2565
  end
1719
2566
 
2567
+ # :call-seq:
2568
+ # fetch(set, attr, changedsince: nil) -> array of FetchData
2569
+ #
1720
2570
  # Sends a {FETCH command [IMAP4rev1 §6.4.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.5]
1721
2571
  # to retrieve data associated with a message in the mailbox.
1722
2572
  #
1723
- # The +set+ parameter is a number or a range between two numbers,
1724
- # or an array of those. The number is a message sequence number,
1725
- # where -1 represents a '*' for use in range notation like 100..-1
1726
- # being interpreted as '100:*'. Beware that the +exclude_end?+
1727
- # property of a Range object is ignored, and the contents of a
1728
- # range are independent of the order of the range endpoints as per
1729
- # the protocol specification, so 1...5, 5..1 and 5...1 are all
1730
- # equivalent to 1..5.
2573
+ # +set+ is the message sequence numbers to fetch, and may be any valid input
2574
+ # to {SequenceSet[...]}[rdoc-ref:SequenceSet@Creating+sequence+sets].
2575
+ # (For UIDs, use #uid_fetch instead.)
1731
2576
  #
1732
- # +attr+ is a list of attributes to fetch; see the documentation
1733
- # for FetchData for a list of valid attributes.
2577
+ # +attr+ is a list of attributes to fetch; see FetchStruct documentation for
2578
+ # a list of supported attributes.
1734
2579
  #
1735
- # The return value is an array of FetchData or nil
1736
- # (instead of an empty array) if there is no matching message.
2580
+ # +changedsince+ is an optional integer mod-sequence. It limits results to
2581
+ # messages with a mod-sequence greater than +changedsince+.
1737
2582
  #
1738
- # Related: #uid_search, FetchData
2583
+ # The return value is an array of FetchData.
1739
2584
  #
1740
- # ===== For example:
2585
+ # Related: #uid_fetch, FetchData
2586
+ #
2587
+ # ==== For example:
1741
2588
  #
1742
2589
  # p imap.fetch(6..8, "UID")
1743
2590
  # #=> [#<Net::IMAP::FetchData seqno=6, attr={"UID"=>98}>, \\
@@ -1754,48 +2601,138 @@ module Net
1754
2601
  # #=> "12-Oct-2000 22:40:59 +0900"
1755
2602
  # p data.attr["UID"]
1756
2603
  # #=> 98
1757
- def fetch(set, attr, mod = nil)
1758
- return fetch_internal("FETCH", set, attr, mod)
2604
+ #
2605
+ # ==== Capabilities
2606
+ #
2607
+ # Many extensions define new message +attr+ names. See FetchStruct for a
2608
+ # list of supported extension fields.
2609
+ #
2610
+ # The server's capabilities must include +CONDSTORE+
2611
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162] in order to use the
2612
+ # +changedsince+ argument. Using +changedsince+ implicitly enables the
2613
+ # +CONDSTORE+ extension.
2614
+ #
2615
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
2616
+ # +FETCH+ command is prohibited. Use #uid_fetch instead.
2617
+ def fetch(...)
2618
+ fetch_internal("FETCH", ...)
1759
2619
  end
1760
2620
 
2621
+ # :call-seq:
2622
+ # uid_fetch(set, attr, changedsince: nil, partial: nil) -> array of FetchData (or UIDFetchData)
2623
+ #
1761
2624
  # Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
1762
2625
  # to retrieve data associated with a message in the mailbox.
1763
2626
  #
1764
- # Similar to #fetch, but the +set+ parameter contains unique identifiers
1765
- # instead of message sequence numbers.
2627
+ # +set+ is the message UIDs to fetch, and may be any valid input to
2628
+ # {SequenceSet[...]}[rdoc-ref:SequenceSet@Creating+sequence+sets].
2629
+ # (For message sequence numbers, use #fetch instead.)
1766
2630
  #
2631
+ # +attr+ behaves the same as with #fetch.
1767
2632
  # >>>
1768
2633
  # *Note:* Servers _MUST_ implicitly include the +UID+ message data item as
1769
2634
  # part of any +FETCH+ response caused by a +UID+ command, regardless of
1770
2635
  # whether a +UID+ was specified as a message data item to the +FETCH+.
1771
2636
  #
2637
+ # +changedsince+ (optional) behaves the same as with #fetch.
2638
+ #
2639
+ # +partial+ is an optional range to limit the number of results returned.
2640
+ # It's useful when +set+ contains an unknown number of messages.
2641
+ # <tt>1..500</tt> returns the first 500 messages in +set+ (in mailbox
2642
+ # order), <tt>501..1000</tt> the second 500, and so on. +partial+ may also
2643
+ # be negative: <tt>-500..-1</tt> selects the last 500 messages in +set+.
2644
+ # <em>Requires the +PARTIAL+ capabability.</em>
2645
+ # {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394]
2646
+ #
2647
+ # For example:
2648
+ #
2649
+ # # Without partial, the size of the results may be unknown beforehand:
2650
+ # results = imap.uid_fetch(next_uid_to_fetch.., %w(UID FLAGS))
2651
+ # # ... maybe wait for a long time ... and allocate a lot of memory ...
2652
+ # results.size # => 0..2**32-1
2653
+ # process results # may also take a long time and use a lot of memory...
2654
+ #
2655
+ # # Using partial, the results may be paginated:
2656
+ # loop do
2657
+ # results = imap.uid_fetch(next_uid_to_fetch.., %w(UID FLAGS),
2658
+ # partial: 1..500)
2659
+ # # fetch should return quickly and allocate little memory
2660
+ # results.size # => 0..500
2661
+ # break if results.empty?
2662
+ # next_uid_to_fetch = results.last.uid + 1
2663
+ # process results
2664
+ # end
2665
+ #
1772
2666
  # Related: #fetch, FetchData
1773
- def uid_fetch(set, attr, mod = nil)
1774
- return fetch_internal("UID FETCH", set, attr, mod)
2667
+ #
2668
+ # ==== Capabilities
2669
+ #
2670
+ # The server's capabilities must include +PARTIAL+
2671
+ # {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394] in order to use the
2672
+ # +partial+ argument.
2673
+ #
2674
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
2675
+ # #uid_fetch must be used instead of #fetch, and UIDFetchData will be
2676
+ # returned instead of FetchData.
2677
+ #
2678
+ # Otherwise, #uid_fetch is updated by extensions in the same way as #fetch.
2679
+ def uid_fetch(...)
2680
+ fetch_internal("UID FETCH", ...)
1775
2681
  end
1776
2682
 
2683
+ # :call-seq:
2684
+ # store(set, attr, value, unchangedsince: nil) -> array of FetchData
2685
+ #
1777
2686
  # Sends a {STORE command [IMAP4rev1 §6.4.6]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.6]
1778
2687
  # to alter data associated with messages in the mailbox, in particular their
1779
- # flags. The +set+ parameter is a number, an array of numbers, or a Range
1780
- # object. Each number is a message sequence number. +attr+ is the name of a
1781
- # data item to store: 'FLAGS' will replace the message's flag list with the
1782
- # provided one, '+FLAGS' will add the provided flags, and '-FLAGS' will
1783
- # remove them. +flags+ is a list of flags.
2688
+ # flags.
2689
+ #
2690
+ # +set+ is a number, an array of numbers, or a Range object. Each number is
2691
+ # a message sequence number.
2692
+ #
2693
+ # +attr+ is the name of a data item to store. The semantics of +value+
2694
+ # varies based on +attr+:
2695
+ # * When +attr+ is <tt>"FLAGS"</tt>, the flags in +value+ replace the
2696
+ # message's flag list.
2697
+ # * When +attr+ is <tt>"+FLAGS"</tt>, the flags in +value+ are added to
2698
+ # the flags for the message.
2699
+ # * When +attr+ is <tt>"-FLAGS"</tt>, the flags in +value+ are removed
2700
+ # from the message.
1784
2701
  #
1785
- # The return value is an array of FetchData
2702
+ # +unchangedsince+ is an optional integer mod-sequence. It prohibits any
2703
+ # changes to messages with +mod-sequence+ greater than the specified
2704
+ # +unchangedsince+ value. A SequenceSet of any messages that fail this
2705
+ # check will be returned in a +MODIFIED+ ResponseCode.
2706
+ #
2707
+ # The return value is an array of FetchData.
1786
2708
  #
1787
2709
  # Related: #uid_store
1788
2710
  #
1789
- # ===== For example:
2711
+ # ==== For example:
1790
2712
  #
1791
2713
  # p imap.store(6..8, "+FLAGS", [:Deleted])
1792
- # #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
1793
- # #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
2714
+ # #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>,
2715
+ # #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>,
1794
2716
  # #<Net::IMAP::FetchData seqno=8, attr={"FLAGS"=>[:Seen, :Deleted]}>]
1795
- def store(set, attr, flags)
1796
- return store_internal("STORE", set, attr, flags)
2717
+ #
2718
+ # ==== Capabilities
2719
+ #
2720
+ # Extensions may define new data items to be used with #store.
2721
+ #
2722
+ # The server's capabilities must include +CONDSTORE+
2723
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162] in order to use the
2724
+ # +unchangedsince+ argument. Using +unchangedsince+ implicitly enables the
2725
+ # +CONDSTORE+ extension.
2726
+ #
2727
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
2728
+ # +STORE+ command is prohibited. Use #uid_store instead.
2729
+ def store(set, attr, flags, unchangedsince: nil)
2730
+ store_internal("STORE", set, attr, flags, unchangedsince: unchangedsince)
1797
2731
  end
1798
2732
 
2733
+ # :call-seq:
2734
+ # uid_store(set, attr, value, unchangedsince: nil) -> array of FetchData (or UIDFetchData)
2735
+ #
1799
2736
  # Sends a {UID STORE command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
1800
2737
  # to alter data associated with messages in the mailbox, in particular their
1801
2738
  # flags.
@@ -1804,8 +2741,16 @@ module Net
1804
2741
  # message sequence numbers.
1805
2742
  #
1806
2743
  # Related: #store
1807
- def uid_store(set, attr, flags)
1808
- return store_internal("UID STORE", set, attr, flags)
2744
+ #
2745
+ # ==== Capabilities
2746
+ #
2747
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
2748
+ # #uid_store must be used instead of #store, and UIDFetchData will be
2749
+ # returned instead of FetchData.
2750
+ #
2751
+ # Otherwise, #uid_store is updated by extensions in the same way as #store.
2752
+ def uid_store(set, attr, flags, unchangedsince: nil)
2753
+ store_internal("UID STORE", set, attr, flags, unchangedsince: unchangedsince)
1809
2754
  end
1810
2755
 
1811
2756
  # Sends a {COPY command [IMAP4rev1 §6.4.7]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.7]
@@ -1815,13 +2760,16 @@ module Net
1815
2760
  #
1816
2761
  # Related: #uid_copy
1817
2762
  #
1818
- # ===== Capabilities
2763
+ # ==== Capabilities
1819
2764
  #
1820
2765
  # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
1821
2766
  # supported, the server's response should include a +COPYUID+ response code
1822
2767
  # with UIDPlusData. This will report the UIDVALIDITY of the destination
1823
2768
  # mailbox, the UID set of the source messages, and the assigned UID set of
1824
2769
  # the moved messages.
2770
+ #
2771
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
2772
+ # +COPY+ command is prohibited. Use #uid_copy instead.
1825
2773
  def copy(set, mailbox)
1826
2774
  copy_internal("COPY", set, mailbox)
1827
2775
  end
@@ -1832,9 +2780,12 @@ module Net
1832
2780
  #
1833
2781
  # Similar to #copy, but +set+ contains unique identifiers.
1834
2782
  #
1835
- # ===== Capabilities
2783
+ # ==== Capabilities
2784
+ #
2785
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] in enabled,
2786
+ # #uid_copy must be used instead of #copy.
1836
2787
  #
1837
- # +UIDPLUS+ affects #uid_copy the same way it affects #copy.
2788
+ # Otherwise, #uid_copy is updated by extensions in the same way as #copy.
1838
2789
  def uid_copy(set, mailbox)
1839
2790
  copy_internal("UID COPY", set, mailbox)
1840
2791
  end
@@ -1847,10 +2798,10 @@ module Net
1847
2798
  #
1848
2799
  # Related: #uid_move
1849
2800
  #
1850
- # ===== Capabilities
2801
+ # ==== Capabilities
1851
2802
  #
1852
- # The server's capabilities must include +MOVE+
1853
- # [RFC6851[https://tools.ietf.org/html/rfc6851]].
2803
+ # The server's capabilities must include either +IMAP4rev2+ or +MOVE+
2804
+ # [RFC6851[https://www.rfc-editor.org/rfc/rfc6851]].
1854
2805
  #
1855
2806
  # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
1856
2807
  # supported, the server's response should include a +COPYUID+ response code
@@ -1858,6 +2809,8 @@ module Net
1858
2809
  # mailbox, the UID set of the source messages, and the assigned UID set of
1859
2810
  # the moved messages.
1860
2811
  #
2812
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
2813
+ # +MOVE+ command is prohibited. Use #uid_move instead.
1861
2814
  def move(set, mailbox)
1862
2815
  copy_internal("MOVE", set, mailbox)
1863
2816
  end
@@ -1871,11 +2824,15 @@ module Net
1871
2824
  #
1872
2825
  # Related: #move
1873
2826
  #
1874
- # ===== Capabilities
2827
+ # ==== Capabilities
2828
+ #
2829
+ # The server's capabilities must include either +IMAP4rev2+ or +MOVE+
2830
+ # [RFC6851[https://www.rfc-editor.org/rfc/rfc6851]].
1875
2831
  #
1876
- # Same as #move: The server's capabilities must include +MOVE+
1877
- # [RFC6851[https://tools.ietf.org/html/rfc6851]]. +UIDPLUS+ also affects
1878
- # #uid_move the same way it affects #move.
2832
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
2833
+ # #uid_move must be used instead of #move.
2834
+ #
2835
+ # Otherwise, #uid_move is updated by extensions in the same way as #move.
1879
2836
  def uid_move(set, mailbox)
1880
2837
  copy_internal("UID MOVE", set, mailbox)
1881
2838
  end
@@ -1891,17 +2848,17 @@ module Net
1891
2848
  #
1892
2849
  # Related: #uid_sort, #search, #uid_search, #thread, #uid_thread
1893
2850
  #
1894
- # ===== For example:
2851
+ # ==== For example:
1895
2852
  #
1896
2853
  # p imap.sort(["FROM"], ["ALL"], "US-ASCII")
1897
2854
  # #=> [1, 2, 3, 5, 6, 7, 8, 4, 9]
1898
2855
  # p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII")
1899
2856
  # #=> [6, 7, 8, 1]
1900
2857
  #
1901
- # ===== Capabilities
2858
+ # ==== Capabilities
1902
2859
  #
1903
2860
  # The server's capabilities must include +SORT+
1904
- # [RFC5256[https://tools.ietf.org/html/rfc5256]].
2861
+ # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
1905
2862
  def sort(sort_keys, search_keys, charset)
1906
2863
  return sort_internal("SORT", sort_keys, search_keys, charset)
1907
2864
  end
@@ -1913,10 +2870,10 @@ module Net
1913
2870
  #
1914
2871
  # Related: #sort, #search, #uid_search, #thread, #uid_thread
1915
2872
  #
1916
- # ===== Capabilities
2873
+ # ==== Capabilities
1917
2874
  #
1918
2875
  # The server's capabilities must include +SORT+
1919
- # [RFC5256[https://tools.ietf.org/html/rfc5256]].
2876
+ # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
1920
2877
  def uid_sort(sort_keys, search_keys, charset)
1921
2878
  return sort_internal("UID SORT", sort_keys, search_keys, charset)
1922
2879
  end
@@ -1938,10 +2895,10 @@ module Net
1938
2895
  #
1939
2896
  # Related: #uid_thread, #search, #uid_search, #sort, #uid_sort
1940
2897
  #
1941
- # ===== Capabilities
2898
+ # ==== Capabilities
1942
2899
  #
1943
2900
  # The server's capabilities must include +THREAD+
1944
- # [RFC5256[https://tools.ietf.org/html/rfc5256]].
2901
+ # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
1945
2902
  def thread(algorithm, search_keys, charset)
1946
2903
  return thread_internal("THREAD", algorithm, search_keys, charset)
1947
2904
  end
@@ -1952,14 +2909,112 @@ module Net
1952
2909
  #
1953
2910
  # Related: #thread, #search, #uid_search, #sort, #uid_sort
1954
2911
  #
1955
- # ===== Capabilities
2912
+ # ==== Capabilities
1956
2913
  #
1957
2914
  # The server's capabilities must include +THREAD+
1958
- # [RFC5256[https://tools.ietf.org/html/rfc5256]].
2915
+ # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
1959
2916
  def uid_thread(algorithm, search_keys, charset)
1960
2917
  return thread_internal("UID THREAD", algorithm, search_keys, charset)
1961
2918
  end
1962
2919
 
2920
+ # Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1]
2921
+ # {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1]
2922
+ # to enable the specified server +capabilities+. Each capability may be an
2923
+ # array, string, or symbol. Returns a list of the capabilities that were
2924
+ # enabled.
2925
+ #
2926
+ # The +ENABLE+ command is only valid in the _authenticated_ state, before
2927
+ # any mailbox is selected.
2928
+ #
2929
+ # Related: #capable?, #capabilities, #capability
2930
+ #
2931
+ # ==== Capabilities
2932
+ #
2933
+ # The server's capabilities must include
2934
+ # +ENABLE+ [RFC5161[https://www.rfc-editor.org/rfc/rfc5161]]
2935
+ # or +IMAP4REV2+ [RFC9051[https://www.rfc-editor.org/rfc/rfc9051]].
2936
+ #
2937
+ # Additionally, the server capabilities must include a capability matching
2938
+ # each enabled extension (usually the same name as the enabled extension).
2939
+ # The following capabilities may be enabled:
2940
+ #
2941
+ # [+CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]]
2942
+ #
2943
+ # Updates various commands to return +CONDSTORE+ extension responses. It
2944
+ # is not necessary to explicitly enable +CONDSTORE+—using any of the
2945
+ # command parameters defined by the extension will implicitly enable it.
2946
+ # See {[RFC7162 §3.1]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1].
2947
+ #
2948
+ # [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
2949
+ #
2950
+ # In a future release, <tt>enable(:utf8)</tt> will enable either
2951
+ # <tt>"UTF8=ACCEPT"</tt> or <tt>"IMAP4rev2"</tt>, depending on server
2952
+ # capabilities.
2953
+ #
2954
+ # [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://www.rfc-editor.org/rfc/rfc6855]]]
2955
+ #
2956
+ # The server's capabilities must include <tt>UTF8=ACCEPT</tt> _or_
2957
+ # <tt>UTF8=ONLY</tt>.
2958
+ #
2959
+ # This allows the server to send strings encoded as UTF-8 which might
2960
+ # otherwise need to use a 7-bit encoding, such as {modified
2961
+ # UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for
2962
+ # message headers.
2963
+ #
2964
+ # *Note:* <em>A future update may set string encodings slightly
2965
+ # differently</em>, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8"
2966
+ # when it is. Currently, the encoding of strings sent as "quoted" or
2967
+ # "text" will _always_ be "UTF-8", even when only ASCII characters are
2968
+ # used (e.g. "Subject: Agenda") And currently, string "literals" sent
2969
+ # by the server will always have an "ASCII-8BIT" (binary)
2970
+ # encoding, even if they generally contain UTF-8 data, if they are
2971
+ # text at all.
2972
+ #
2973
+ # [<tt>"UTF8=ONLY"</tt> [RFC6855[https://www.rfc-editor.org/rfc/rfc6855]]]
2974
+ #
2975
+ # A server that reports the <tt>UTF8=ONLY</tt> capability _requires_ that
2976
+ # the client <tt>enable("UTF8=ACCEPT")</tt> before any mailboxes may be
2977
+ # selected. For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
2978
+ # <tt>enable("UTF8=ACCEPT")</tt>.
2979
+ #
2980
+ # [+UIDONLY+ {[RFC9586]}[https://www.rfc-editor.org/rfc/rfc9586.pdf]]
2981
+ #
2982
+ # When UIDONLY is enabled, the #fetch, #store, #search, #copy, and #move
2983
+ # commands are prohibited and result in a tagged BAD response. Clients
2984
+ # should instead use uid_fetch, uid_store, uid_search, uid_copy, or
2985
+ # uid_move, respectively. All +FETCH+ responses that would be returned are
2986
+ # replaced by +UIDFETCH+ responses. All +EXPUNGED+ responses that would be
2987
+ # returned are replaced by +VANISHED+ responses. The "<sequence set>"
2988
+ # uid_search criterion is prohibited.
2989
+ #
2990
+ # ===== Unsupported capabilities
2991
+ #
2992
+ # *Note:* Some extensions that use ENABLE permit the server to send syntax
2993
+ # that Net::IMAP cannot parse, which may raise an exception and disconnect.
2994
+ # Some extensions may work, but the support may be incomplete, untested, or
2995
+ # experimental.
2996
+ #
2997
+ # Until a capability is documented here as supported, enabling it may result
2998
+ # in undocumented behavior and a future release may update with incompatible
2999
+ # behavior <em>without warning or deprecation</em>.
3000
+ #
3001
+ # <em>Caution is advised.</em>
3002
+ #
3003
+ def enable(*capabilities)
3004
+ capabilities = capabilities
3005
+ .flatten
3006
+ .map {|e| ENABLE_ALIASES[e] || e }
3007
+ .uniq
3008
+ .join(' ')
3009
+ synchronize do
3010
+ send_command("ENABLE #{capabilities}")
3011
+ result = clear_responses("ENABLED").last || []
3012
+ @utf8_strings ||= result.include? "UTF8=ACCEPT"
3013
+ @utf8_strings ||= result.include? "IMAP4REV2"
3014
+ result
3015
+ end
3016
+ end
3017
+
1963
3018
  # Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3]
1964
3019
  # {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13]
1965
3020
  # that waits for notifications of new or expunged messages. Yields
@@ -1972,17 +3027,23 @@ module Net
1972
3027
  # checks the connection for each 60 seconds.
1973
3028
  #
1974
3029
  # loop do
1975
- # imap.idle(60) do |res|
1976
- # ...
3030
+ # imap.idle(60) do |response|
3031
+ # do_something_with(response)
3032
+ # imap.idle_done if some_condition?(response)
1977
3033
  # end
1978
3034
  # end
1979
3035
  #
3036
+ # Returns the server's response to indicate the IDLE state has ended.
3037
+ # Returns +nil+ if the server does not respond to #idle_done within
3038
+ # {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]
3039
+ # seconds.
3040
+ #
1980
3041
  # Related: #idle_done, #noop, #check
1981
3042
  #
1982
- # ===== Capabilities
3043
+ # ==== Capabilities
1983
3044
  #
1984
- # The server's capabilities must include +IDLE+
1985
- # [RFC2177[https://tools.ietf.org/html/rfc2177]].
3045
+ # The server's capabilities must include either +IMAP4rev2+ or +IDLE+
3046
+ # [RFC2177[https://www.rfc-editor.org/rfc/rfc2177]].
1986
3047
  def idle(timeout = nil, &response_handler)
1987
3048
  raise LocalJumpError, "no block given" unless response_handler
1988
3049
 
@@ -2004,7 +3065,7 @@ module Net
2004
3065
  unless @receiver_thread_terminating
2005
3066
  remove_response_handler(response_handler)
2006
3067
  put_string("DONE#{CRLF}")
2007
- response = get_tagged_response(tag, "IDLE", @idle_response_timeout)
3068
+ response = get_tagged_response(tag, "IDLE", idle_response_timeout)
2008
3069
  end
2009
3070
  end
2010
3071
  end
@@ -2012,7 +3073,11 @@ module Net
2012
3073
  return response
2013
3074
  end
2014
3075
 
2015
- # Leaves IDLE.
3076
+ # Leaves IDLE, allowing #idle to return.
3077
+ #
3078
+ # If the server does not respond within
3079
+ # {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]
3080
+ # seconds, #idle will return +nil+.
2016
3081
  #
2017
3082
  # Related: #idle
2018
3083
  def idle_done
@@ -2024,6 +3089,196 @@ module Net
2024
3089
  end
2025
3090
  end
2026
3091
 
3092
+ RESPONSES_DEPRECATION_MSG =
3093
+ "Pass a type or block to #responses, " \
3094
+ "set config.responses_without_block to :frozen_dup " \
3095
+ "or :silence_deprecation_warning, " \
3096
+ "or use #extract_responses or #clear_responses."
3097
+ private_constant :RESPONSES_DEPRECATION_MSG
3098
+
3099
+ # :call-seq:
3100
+ # responses -> hash of {String => Array} (see config.responses_without_block)
3101
+ # responses(type) -> frozen array
3102
+ # responses {|hash| ...} -> block result
3103
+ # responses(type) {|array| ...} -> block result
3104
+ #
3105
+ # Yields or returns unhandled server responses. Unhandled responses are
3106
+ # stored in a hash, with arrays of UntaggedResponse#data keyed by
3107
+ # UntaggedResponse#name and <em>non-+nil+</em> untagged ResponseCode#data
3108
+ # keyed by ResponseCode#name.
3109
+ #
3110
+ # When a block is given, yields unhandled responses and returns the block's
3111
+ # result. Without a block, returns the unhandled responses.
3112
+ #
3113
+ # [With +type+]
3114
+ # Yield or return only the array of responses for that +type+.
3115
+ # When no block is given, the returned array is a frozen copy.
3116
+ # [Without +type+]
3117
+ # Yield or return the entire responses hash.
3118
+ #
3119
+ # When no block is given, the behavior is determined by
3120
+ # Config#responses_without_block:
3121
+ # >>>
3122
+ # [+:silence_deprecation_warning+ <em>(original behavior)</em>]
3123
+ # Returns the mutable responses hash (without any warnings).
3124
+ # <em>This is not thread-safe.</em>
3125
+ #
3126
+ # [+:warn+ <em>(default since +v0.5+)</em>]
3127
+ # Prints a warning and returns the mutable responses hash.
3128
+ # <em>This is not thread-safe.</em>
3129
+ #
3130
+ # [+:frozen_dup+ <em>(planned default for +v0.6+)</em>]
3131
+ # Returns a frozen copy of the unhandled responses hash, with frozen
3132
+ # array values.
3133
+ #
3134
+ # [+:raise+]
3135
+ # Raise an +ArgumentError+ with the deprecation warning.
3136
+ #
3137
+ # For example:
3138
+ #
3139
+ # imap.select("inbox")
3140
+ # p imap.responses("EXISTS").last
3141
+ # #=> 2
3142
+ # p imap.responses("UIDNEXT", &:last)
3143
+ # #=> 123456
3144
+ # p imap.responses("UIDVALIDITY", &:last)
3145
+ # #=> 968263756
3146
+ # p imap.responses {|responses|
3147
+ # {
3148
+ # exists: responses.delete("EXISTS").last,
3149
+ # uidnext: responses.delete("UIDNEXT").last,
3150
+ # uidvalidity: responses.delete("UIDVALIDITY").last,
3151
+ # }
3152
+ # }
3153
+ # #=> {:exists=>2, :uidnext=>123456, :uidvalidity=>968263756}
3154
+ # # "EXISTS", "UIDNEXT", and "UIDVALIDITY" have been removed:
3155
+ # p imap.responses(&:keys)
3156
+ # #=> ["FLAGS", "OK", "PERMANENTFLAGS", "RECENT", "HIGHESTMODSEQ"]
3157
+ #
3158
+ # Related: #extract_responses, #clear_responses, #response_handlers, #greeting
3159
+ #
3160
+ # ==== Thread safety
3161
+ # >>>
3162
+ # *Note:* Access to the responses hash is synchronized for thread-safety.
3163
+ # The receiver thread and response_handlers cannot process new responses
3164
+ # until the block completes. Accessing either the response hash or its
3165
+ # response type arrays outside of the block is unsafe. They can be safely
3166
+ # updated inside the block. Consider using #clear_responses or
3167
+ # #extract_responses instead.
3168
+ #
3169
+ # Net::IMAP will add and remove responses from the responses hash and its
3170
+ # array values, in the calling threads for commands and in the receiver
3171
+ # thread, but will not modify any responses after adding them to the
3172
+ # responses hash.
3173
+ #
3174
+ # ==== Clearing responses
3175
+ #
3176
+ # Previously unhandled responses are automatically cleared before entering a
3177
+ # mailbox with #select or #examine. Long-lived connections can receive many
3178
+ # unhandled server responses, which must be pruned or they will continually
3179
+ # consume more memory. Update or clear the responses hash or arrays inside
3180
+ # the block, or remove responses with #extract_responses, #clear_responses,
3181
+ # or #add_response_handler.
3182
+ #
3183
+ # ==== Missing responses
3184
+ #
3185
+ # Only non-+nil+ data is stored. Many important response codes have no data
3186
+ # of their own, but are used as "tags" on the ResponseText object they are
3187
+ # attached to. ResponseText will be accessible by its response types:
3188
+ # "+OK+", "+NO+", "+BAD+", "+BYE+", or "+PREAUTH+".
3189
+ #
3190
+ # TaggedResponse#data is not saved to #responses, nor is any
3191
+ # ResponseCode#data on tagged responses. Although some command methods do
3192
+ # return the TaggedResponse directly, #add_response_handler must be used to
3193
+ # handle all response codes.
3194
+ def responses(type = nil)
3195
+ if block_given?
3196
+ synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
3197
+ elsif type
3198
+ synchronize { @responses[type.to_s.upcase].dup.freeze }
3199
+ else
3200
+ case config.responses_without_block
3201
+ when :raise
3202
+ raise ArgumentError, RESPONSES_DEPRECATION_MSG
3203
+ when :warn
3204
+ warn(RESPONSES_DEPRECATION_MSG, uplevel: 1, category: :deprecated)
3205
+ when :frozen_dup
3206
+ synchronize {
3207
+ responses = @responses.transform_values(&:freeze)
3208
+ responses.default_proc = nil
3209
+ responses.default = [].freeze
3210
+ return responses.freeze
3211
+ }
3212
+ end
3213
+ @responses
3214
+ end
3215
+ end
3216
+
3217
+ # :call-seq:
3218
+ # clear_responses -> hash
3219
+ # clear_responses(type) -> array
3220
+ #
3221
+ # Clears and returns the unhandled #responses hash or the unhandled
3222
+ # responses array for a single response +type+.
3223
+ #
3224
+ # Clearing responses is synchronized with other threads. The lock is
3225
+ # released before returning.
3226
+ #
3227
+ # Related: #extract_responses, #responses, #response_handlers
3228
+ def clear_responses(type = nil)
3229
+ synchronize {
3230
+ if type
3231
+ @responses.delete(type) || []
3232
+ else
3233
+ @responses.dup.transform_values(&:freeze)
3234
+ .tap { _1.default = [].freeze }
3235
+ .tap { @responses.clear }
3236
+ end
3237
+ }
3238
+ .freeze
3239
+ end
3240
+
3241
+ # :call-seq:
3242
+ # extract_responses(type) {|response| ... } -> array
3243
+ #
3244
+ # Yields all of the unhandled #responses for a single response +type+.
3245
+ # Removes and returns the responses for which the block returns a true
3246
+ # value.
3247
+ #
3248
+ # Extracting responses is synchronized with other threads. The lock is
3249
+ # released before returning.
3250
+ #
3251
+ # Related: #responses, #clear_responses
3252
+ def extract_responses(type)
3253
+ type = String.try_convert(type) or
3254
+ raise ArgumentError, "type must be a string"
3255
+ raise ArgumentError, "must provide a block" unless block_given?
3256
+ extracted = []
3257
+ responses(type) do |all|
3258
+ all.reject! do |response|
3259
+ extracted << response if yield response
3260
+ end
3261
+ end
3262
+ extracted
3263
+ end
3264
+
3265
+ # Returns all response handlers, including those that are added internally
3266
+ # by commands. Each response handler will be called with every new
3267
+ # UntaggedResponse, TaggedResponse, and ContinuationRequest.
3268
+ #
3269
+ # Response handlers are called with a mutex inside the receiver thread. New
3270
+ # responses cannot be processed and commands from other threads must wait
3271
+ # until all response_handlers return. An exception will shut-down the
3272
+ # receiver thread and close the connection.
3273
+ #
3274
+ # For thread-safety, the returned array is a frozen copy of the internal
3275
+ # array.
3276
+ #
3277
+ # Related: #add_response_handler, #remove_response_handler
3278
+ def response_handlers
3279
+ synchronize { @response_handlers.clone.freeze }
3280
+ end
3281
+
2027
3282
  # Adds a response handler. For example, to detect when
2028
3283
  # the server sends a new EXISTS response (which normally
2029
3284
  # indicates new messages being added to the mailbox),
@@ -2043,12 +3298,18 @@ module Net
2043
3298
  # Related: #remove_response_handler, #response_handlers
2044
3299
  def add_response_handler(handler = nil, &block)
2045
3300
  raise ArgumentError, "two Procs are passed" if handler && block
2046
- @response_handlers.push(block || handler)
3301
+ synchronize do
3302
+ @response_handlers.push(block || handler)
3303
+ end
2047
3304
  end
2048
3305
 
2049
3306
  # Removes the response handler.
3307
+ #
3308
+ # Related: #add_response_handler, #response_handlers
2050
3309
  def remove_response_handler(handler)
2051
- @response_handlers.delete(handler)
3310
+ synchronize do
3311
+ @response_handlers.delete(handler)
3312
+ end
2052
3313
  end
2053
3314
 
2054
3315
  private
@@ -2057,116 +3318,46 @@ module Net
2057
3318
  PORT = 143 # :nodoc:
2058
3319
  SSL_PORT = 993 # :nodoc:
2059
3320
 
2060
- @@debug = false
2061
-
2062
- # :call-seq:
2063
- # Net::IMAP.new(host, options = {})
2064
- #
2065
- # Creates a new Net::IMAP object and connects it to the specified
2066
- # +host+.
2067
- #
2068
- # +options+ is an option hash, each key of which is a symbol.
2069
- #
2070
- # The available options are:
2071
- #
2072
- # port:: Port number (default value is 143 for imap, or 993 for imaps)
2073
- # ssl:: If +options[:ssl]+ is true, then an attempt will be made
2074
- # to use SSL (now TLS) to connect to the server.
2075
- # If +options[:ssl]+ is a hash, it's passed to
2076
- # OpenSSL::SSL::SSLContext#set_params as parameters.
2077
- # open_timeout:: Seconds to wait until a connection is opened
2078
- # idle_response_timeout:: Seconds to wait until an IDLE response is received
2079
- # response_handlers:: A list of response handlers to be added before the
2080
- # receiver thread is started. This ensures every server
2081
- # response is handled, including the #greeting. Note
2082
- # that the greeting is handled in the current thread,
2083
- # but all other responses are handled in the receiver
2084
- # thread.
2085
- # max_response_size:: See #max_response_size.
2086
- #
2087
- # The most common errors are:
2088
- #
2089
- # Errno::ECONNREFUSED:: Connection refused by +host+ or an intervening
2090
- # firewall.
2091
- # Errno::ETIMEDOUT:: Connection timed out (possibly due to packets
2092
- # being dropped by an intervening firewall).
2093
- # Errno::ENETUNREACH:: There is no route to that network.
2094
- # SocketError:: Hostname not known or other socket error.
2095
- # Net::IMAP::ByeResponseError:: The connected to the host was successful, but
2096
- # it immediately said goodbye.
2097
- def initialize(host, port_or_options = {},
2098
- usessl = false, certs = nil, verify = true)
2099
- super()
2100
- @host = host
2101
- begin
2102
- options = port_or_options.to_hash
2103
- rescue NoMethodError
2104
- # for backward compatibility
2105
- options = {}
2106
- options[:port] = port_or_options
2107
- if usessl
2108
- options[:ssl] = create_ssl_params(certs, verify)
2109
- end
3321
+ def start_imap_connection
3322
+ @greeting = get_server_greeting
3323
+ @capabilities = capabilities_from_resp_code @greeting
3324
+ @response_handlers.each do |handler| handler.call(@greeting) end
3325
+ @receiver_thread = start_receiver_thread
3326
+ rescue Exception
3327
+ state_logout!
3328
+ @sock.close
3329
+ raise
3330
+ end
3331
+
3332
+ def get_server_greeting
3333
+ greeting = get_response
3334
+ raise Error, "No server greeting - connection closed" unless greeting
3335
+ record_untagged_response_code greeting
3336
+ case greeting.name
3337
+ when "PREAUTH" then state_authenticated!
3338
+ when "BYE" then state_logout!; raise ByeResponseError, greeting
2110
3339
  end
2111
- @port = options[:port] || (options[:ssl] ? SSL_PORT : PORT)
2112
- @tag_prefix = "RUBY"
2113
- @tagno = 0
2114
- @open_timeout = options[:open_timeout] || 30
2115
- @idle_response_timeout = options[:idle_response_timeout] || 5
2116
- @max_response_size = options[:max_response_size]
2117
- @parser = ResponseParser.new
2118
- @sock = tcp_socket(@host, @port)
2119
- @reader = ResponseReader.new(self, @sock)
2120
- begin
2121
- if options[:ssl]
2122
- start_tls_session(options[:ssl])
2123
- @usessl = true
2124
- else
2125
- @usessl = false
2126
- end
2127
- @responses = Hash.new([].freeze)
2128
- @tagged_responses = {}
2129
- @response_handlers = []
2130
- options[:response_handlers]&.each do |h| add_response_handler(h) end
2131
- @tagged_response_arrival = new_cond
2132
- @continued_command_tag = nil
2133
- @continuation_request_arrival = new_cond
2134
- @continuation_request_exception = nil
2135
- @idle_done_cond = nil
2136
- @logout_command_tag = nil
2137
- @debug_output_bol = true
2138
- @exception = nil
2139
-
2140
- @greeting = get_response
2141
- if @greeting.nil?
2142
- raise Error, "connection closed"
2143
- end
2144
- if @greeting.name == "BYE"
2145
- raise ByeResponseError, @greeting
2146
- end
2147
- @response_handlers.each do |handler| handler.call(@greeting) end
3340
+ greeting
3341
+ end
2148
3342
 
2149
- @client_thread = Thread.current
2150
- @receiver_thread = Thread.start {
2151
- begin
2152
- receive_responses
2153
- rescue Exception
2154
- end
2155
- }
2156
- @receiver_thread_terminating = false
2157
- rescue Exception
2158
- @sock.close
2159
- raise
3343
+ def start_receiver_thread
3344
+ Thread.start do
3345
+ receive_responses
3346
+ rescue Exception => ex
3347
+ @receiver_thread_exception = ex
3348
+ # don't exit the thread with an exception
3349
+ ensure
3350
+ state_logout!
2160
3351
  end
2161
3352
  end
2162
3353
 
2163
3354
  def tcp_socket(host, port)
2164
- s = Socket.tcp(host, port, :connect_timeout => @open_timeout)
3355
+ s = Socket.tcp(host, port, :connect_timeout => open_timeout)
2165
3356
  s.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, true)
2166
3357
  s
2167
3358
  rescue Errno::ETIMEDOUT
2168
3359
  raise Net::OpenTimeout, "Timeout to open TCP connection to " +
2169
- "#{host}:#{port} (exceeds #{@open_timeout} seconds)"
3360
+ "#{host}:#{port} (exceeds #{open_timeout} seconds)"
2170
3361
  end
2171
3362
 
2172
3363
  def receive_responses
@@ -2179,6 +3370,7 @@ module Net
2179
3370
  resp = get_response
2180
3371
  rescue Exception => e
2181
3372
  synchronize do
3373
+ state_logout!
2182
3374
  @sock.close
2183
3375
  @exception = e
2184
3376
  end
@@ -2198,6 +3390,7 @@ module Net
2198
3390
  @tagged_response_arrival.broadcast
2199
3391
  case resp.tag
2200
3392
  when @logout_command_tag
3393
+ state_logout!
2201
3394
  return
2202
3395
  when @continued_command_tag
2203
3396
  @continuation_request_exception =
@@ -2205,12 +3398,9 @@ module Net
2205
3398
  @continuation_request_arrival.signal
2206
3399
  end
2207
3400
  when UntaggedResponse
2208
- record_response(resp.name, resp.data)
2209
- if resp.data.instance_of?(ResponseText) &&
2210
- (code = resp.data.code)
2211
- record_response(code.name, code.data)
2212
- end
3401
+ record_untagged_response(resp)
2213
3402
  if resp.name == "BYE" && @logout_command_tag.nil?
3403
+ state_logout!
2214
3404
  @sock.close
2215
3405
  @exception = ByeResponseError.new(resp)
2216
3406
  connection_closed = true
@@ -2218,6 +3408,7 @@ module Net
2218
3408
  when ContinuationRequest
2219
3409
  @continuation_request_arrival.signal
2220
3410
  end
3411
+ state_unselected! if resp in {data: {code: {name: "CLOSED"}}}
2221
3412
  @response_handlers.each do |handler|
2222
3413
  handler.call(resp)
2223
3414
  end
@@ -2263,24 +3454,54 @@ module Net
2263
3454
  when /\A(?:BAD)\z/ni
2264
3455
  raise BadResponseError, resp
2265
3456
  else
2266
- raise UnknownResponseError, resp
3457
+ disconnect
3458
+ raise InvalidResponseError, "invalid tagged resp: %p" % [resp.raw.chomp]
2267
3459
  end
2268
3460
  end
2269
3461
 
2270
3462
  def get_response
2271
3463
  buff = @reader.read_response_buffer
2272
3464
  return nil if buff.length == 0
2273
- $stderr.print(buff.gsub(/^/n, "S: ")) if @@debug
3465
+ $stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
2274
3466
  @parser.parse(buff)
2275
3467
  end
2276
3468
 
3469
+ #############################
3470
+ # built-in response handlers
3471
+
3472
+ # store name => [..., data]
3473
+ def record_untagged_response(resp)
3474
+ @responses[resp.name] << resp.data
3475
+ record_untagged_response_code resp
3476
+ end
3477
+
3478
+ # store code.name => [..., code.data]
3479
+ def record_untagged_response_code(resp)
3480
+ return unless resp.data.is_a?(ResponseText)
3481
+ return unless (code = resp.data.code)
3482
+ @responses[code.name] << code.data
3483
+ end
3484
+
3485
+ # NOTE: only call this for greeting, login, and authenticate
3486
+ def capabilities_from_resp_code(resp)
3487
+ return unless %w[PREAUTH OK].any? { _1.casecmp? resp.name }
3488
+ return unless (code = resp.data.code)
3489
+ return unless code.name.casecmp?("CAPABILITY")
3490
+ code.data.freeze
3491
+ end
3492
+
2277
3493
  #############################
2278
3494
 
2279
- def record_response(name, data)
2280
- unless @responses.has_key?(name)
2281
- @responses[name] = []
3495
+ # Calls send_command, yielding the text of each ContinuationRequest and
3496
+ # responding with each block result. Returns TaggedResponse. Raises
3497
+ # NoResponseError or BadResponseError.
3498
+ def send_command_with_continuations(cmd, *args)
3499
+ send_command(cmd, *args) do |server_response|
3500
+ if server_response.instance_of?(ContinuationRequest)
3501
+ client_response = yield server_response.data.text
3502
+ put_string(client_response + CRLF)
3503
+ end
2282
3504
  end
2283
- @responses[name].push(data)
2284
3505
  end
2285
3506
 
2286
3507
  def send_command(cmd, *args, &block)
@@ -2318,12 +3539,12 @@ module Net
2318
3539
 
2319
3540
  def put_string(str)
2320
3541
  @sock.print(str)
2321
- if @@debug
3542
+ if config.debug?
2322
3543
  if @debug_output_bol
2323
3544
  $stderr.print("C: ")
2324
3545
  end
2325
- $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
2326
- if /\r\n\z/n.match(str)
3546
+ $stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " })
3547
+ if /\n\z/n.match(str)
2327
3548
  @debug_output_bol = true
2328
3549
  else
2329
3550
  @debug_output_bol = false
@@ -2331,23 +3552,119 @@ module Net
2331
3552
  end
2332
3553
  end
2333
3554
 
2334
- def search_internal(cmd, keys, charset)
2335
- if keys.instance_of?(String)
2336
- keys = [RawData.new(keys)]
3555
+ def enforce_logindisabled?
3556
+ if config.enforce_logindisabled == :when_capabilities_cached
3557
+ capabilities_cached?
2337
3558
  else
2338
- normalize_searching_criteria(keys)
3559
+ config.enforce_logindisabled
3560
+ end
3561
+ end
3562
+
3563
+ def expunge_internal(...)
3564
+ synchronize do
3565
+ send_command(...)
3566
+ expunged_array = clear_responses("EXPUNGE")
3567
+ vanished_array = extract_responses("VANISHED") { !_1.earlier? }
3568
+ if vanished_array.empty?
3569
+ expunged_array
3570
+ elsif vanished_array.length == 1
3571
+ vanished_array.first
3572
+ else
3573
+ merged_uids = SequenceSet[*vanished_array.map(&:uids)]
3574
+ VanishedData[uids: merged_uids, earlier: false]
3575
+ end
3576
+ end
3577
+ end
3578
+
3579
+ RETURN_WHOLE = /\ARETURN\z/i
3580
+ RETURN_START = /\ARETURN\b/i
3581
+ private_constant :RETURN_WHOLE, :RETURN_START
3582
+
3583
+ def search_args(keys, charset_arg = nil, return: nil, charset: nil)
3584
+ {return:} => {return: return_kw}
3585
+ case [return_kw, keys]
3586
+ in [nil, Array[RETURN_WHOLE, return_opts, *keys]]
3587
+ return_opts = convert_return_opts(return_opts)
3588
+ esearch = true
3589
+ in [nil => return_opts, RETURN_START]
3590
+ esearch = true
3591
+ in [nil => return_opts, keys]
3592
+ esearch = false
3593
+ in [_, Array[RETURN_WHOLE, _, *] | RETURN_START]
3594
+ raise ArgumentError, "conflicting return options"
3595
+ in [_, Array[RETURN_WHOLE, _, *]] # workaround for https://bugs.ruby-lang.org/issues/20956
3596
+ raise ArgumentError, "conflicting return options"
3597
+ in [_, RETURN_START] # workaround for https://bugs.ruby-lang.org/issues/20956
3598
+ raise ArgumentError, "conflicting return options"
3599
+ in [return_opts, keys]
3600
+ return_opts = convert_return_opts(return_opts)
3601
+ esearch = true
3602
+ end
3603
+ if charset && charset_arg
3604
+ raise ArgumentError, "multiple charset arguments"
3605
+ end
3606
+ charset ||= charset_arg
3607
+ # NOTE: not handling combined RETURN and CHARSET for raw strings
3608
+ if charset && keys in /\ACHARSET\b/i | Array[/\ACHARSET\z/i, *]
3609
+ raise ArgumentError, "multiple charset arguments"
2339
3610
  end
3611
+ args = normalize_searching_criteria(keys)
3612
+ args.prepend("CHARSET", charset) if charset
3613
+ args.prepend("RETURN", return_opts) if return_opts
3614
+ return args, esearch
3615
+ end
3616
+
3617
+ def convert_return_opts(unconverted)
3618
+ return_opts = Array.try_convert(unconverted) or
3619
+ raise TypeError, "expected return options to be Array, got %s" % [
3620
+ unconverted.class
3621
+ ]
3622
+ return_opts.map {|opt|
3623
+ case opt
3624
+ when Symbol then opt.to_s
3625
+ when PartialRange::Negative then PartialRange[opt]
3626
+ when Range then SequenceSet[opt]
3627
+ else opt
3628
+ end
3629
+ }
3630
+ end
3631
+
3632
+ def search_internal(cmd, ...)
3633
+ args, esearch = search_args(...)
2340
3634
  synchronize do
2341
- if charset
2342
- send_command(cmd, "CHARSET", charset, *keys)
3635
+ tagged = send_command(cmd, *args)
3636
+ tag = tagged.tag
3637
+ # Only the last ESEARCH or SEARCH is used. Excess results are ignored.
3638
+ esearch_result = extract_responses("ESEARCH") {|response|
3639
+ response in ESearchResult(tag: ^tag)
3640
+ }.last
3641
+ search_result = clear_responses("SEARCH").last
3642
+ if esearch_result
3643
+ # silently ignore SEARCH results, if any
3644
+ esearch_result
3645
+ elsif search_result
3646
+ # warn EXPECTED_ESEARCH_RESULT if esearch
3647
+ search_result
3648
+ elsif esearch
3649
+ # warn NO_SEARCH_RESPONSE
3650
+ ESearchResult[tag:, uid: cmd.start_with?("UID ")]
2343
3651
  else
2344
- send_command(cmd, *keys)
3652
+ # warn NO_SEARCH_RESPONSE
3653
+ SearchResult[]
2345
3654
  end
2346
- return @responses.delete("SEARCH")[-1]
2347
3655
  end
2348
3656
  end
2349
3657
 
2350
- def fetch_internal(cmd, set, attr, mod = nil)
3658
+ def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
3659
+ set = SequenceSet[set]
3660
+ if partial
3661
+ mod ||= []
3662
+ mod << "PARTIAL" << PartialRange[partial]
3663
+ end
3664
+ if changedsince
3665
+ mod ||= []
3666
+ mod << "CHANGEDSINCE" << Integer(changedsince)
3667
+ end
2351
3668
  case attr
2352
3669
  when String then
2353
3670
  attr = RawData.new(attr)
@@ -2357,119 +3674,158 @@ module Net
2357
3674
  }
2358
3675
  end
2359
3676
 
2360
- synchronize do
2361
- @responses.delete("FETCH")
2362
- if mod
2363
- send_command(cmd, MessageSet.new(set), attr, mod)
2364
- else
2365
- send_command(cmd, MessageSet.new(set), attr)
2366
- end
2367
- return @responses.delete("FETCH")
2368
- end
3677
+ args = [cmd, set, attr]
3678
+ args << mod if mod
3679
+ send_command_returning_fetch_results(*args)
2369
3680
  end
2370
3681
 
2371
- def store_internal(cmd, set, attr, flags)
2372
- if attr.instance_of?(String)
2373
- attr = RawData.new(attr)
2374
- end
3682
+ def store_internal(cmd, set, attr, flags, unchangedsince: nil)
3683
+ attr = RawData.new(attr) if attr.instance_of?(String)
3684
+ args = [SequenceSet.new(set)]
3685
+ args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
3686
+ args << attr << flags
3687
+ send_command_returning_fetch_results(cmd, *args)
3688
+ end
3689
+
3690
+ def send_command_returning_fetch_results(...)
2375
3691
  synchronize do
2376
- @responses.delete("FETCH")
2377
- send_command(cmd, MessageSet.new(set), attr, flags)
2378
- return @responses.delete("FETCH")
3692
+ clear_responses("FETCH")
3693
+ clear_responses("UIDFETCH")
3694
+ send_command(...)
3695
+ fetches = clear_responses("FETCH")
3696
+ uidfetches = clear_responses("UIDFETCH")
3697
+ uidfetches.any? ? uidfetches : fetches
2379
3698
  end
2380
3699
  end
2381
3700
 
2382
3701
  def copy_internal(cmd, set, mailbox)
2383
- send_command(cmd, MessageSet.new(set), mailbox)
3702
+ send_command(cmd, SequenceSet.new(set), mailbox)
2384
3703
  end
2385
3704
 
2386
3705
  def sort_internal(cmd, sort_keys, search_keys, charset)
2387
- if search_keys.instance_of?(String)
2388
- search_keys = [RawData.new(search_keys)]
2389
- else
2390
- normalize_searching_criteria(search_keys)
2391
- end
2392
- normalize_searching_criteria(search_keys)
3706
+ search_keys = normalize_searching_criteria(search_keys)
2393
3707
  synchronize do
2394
3708
  send_command(cmd, sort_keys, charset, *search_keys)
2395
- return @responses.delete("SORT")[-1]
3709
+ clear_responses("SORT").last || []
2396
3710
  end
2397
3711
  end
2398
3712
 
2399
3713
  def thread_internal(cmd, algorithm, search_keys, charset)
2400
- if search_keys.instance_of?(String)
2401
- search_keys = [RawData.new(search_keys)]
2402
- else
2403
- normalize_searching_criteria(search_keys)
3714
+ search_keys = normalize_searching_criteria(search_keys)
3715
+ synchronize do
3716
+ send_command(cmd, algorithm, charset, *search_keys)
3717
+ clear_responses("THREAD").last || []
2404
3718
  end
2405
- normalize_searching_criteria(search_keys)
2406
- send_command(cmd, algorithm, charset, *search_keys)
2407
- return @responses.delete("THREAD")[-1]
2408
3719
  end
2409
3720
 
2410
- def normalize_searching_criteria(keys)
2411
- keys.collect! do |i|
2412
- case i
2413
- when -1, Range, Array
2414
- MessageSet.new(i)
3721
+ def normalize_searching_criteria(criteria)
3722
+ return [RawData.new(criteria)] if criteria.is_a?(String)
3723
+ criteria.map {|i|
3724
+ if coerce_search_arg_to_seqset?(i)
3725
+ SequenceSet[i]
2415
3726
  else
2416
3727
  i
2417
3728
  end
2418
- end
3729
+ }
2419
3730
  end
2420
3731
 
2421
- def create_ssl_params(certs = nil, verify = true)
2422
- params = {}
2423
- if certs
2424
- if File.file?(certs)
2425
- params[:ca_file] = certs
2426
- elsif File.directory?(certs)
2427
- params[:ca_path] = certs
2428
- end
3732
+ def coerce_search_arg_to_seqset?(obj)
3733
+ case obj
3734
+ when Set, -1, :* then true
3735
+ when Range then true
3736
+ when Array then obj.all? { coerce_search_array_arg_to_seqset? _1 }
3737
+ else obj.respond_to?(:to_sequence_set)
2429
3738
  end
2430
- if verify
2431
- params[:verify_mode] = VERIFY_PEER
3739
+ end
3740
+
3741
+ def coerce_search_array_arg_to_seqset?(obj)
3742
+ case obj
3743
+ when Integer then obj.positive? || obj == -1
3744
+ when String then ResponseParser::Patterns::SEQUENCE_SET_STR.match?(obj.b)
2432
3745
  else
2433
- params[:verify_mode] = VERIFY_NONE
3746
+ coerce_search_arg_to_seqset?(obj)
2434
3747
  end
2435
- return params
2436
3748
  end
2437
3749
 
2438
- def start_tls_session(params = {})
2439
- unless defined?(OpenSSL::SSL)
2440
- raise "SSL extension not installed"
2441
- end
2442
- if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
2443
- raise RuntimeError, "already using SSL"
2444
- end
2445
- begin
2446
- params = params.to_hash
2447
- rescue NoMethodError
2448
- params = {}
2449
- end
2450
- context = SSLContext.new
2451
- context.set_params(params)
2452
- if defined?(VerifyCallbackProc)
2453
- context.verify_callback = VerifyCallbackProc
3750
+ def build_ssl_ctx(ssl)
3751
+ if ssl
3752
+ params = (Hash.try_convert(ssl) || {}).freeze
3753
+ context = SSLContext.new
3754
+ context.set_params(params)
3755
+ if defined?(VerifyCallbackProc)
3756
+ context.verify_callback = VerifyCallbackProc
3757
+ end
3758
+ context.freeze
3759
+ [params, context]
3760
+ else
3761
+ false
2454
3762
  end
2455
- @sock = SSLSocket.new(@sock, context)
3763
+ end
3764
+
3765
+ def start_tls_session
3766
+ raise "SSL extension not installed" unless defined?(OpenSSL::SSL)
3767
+ raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
3768
+ raise "cannot start TLS without SSLContext" unless ssl_ctx
3769
+ @sock = SSLSocket.new(@sock, ssl_ctx)
2456
3770
  @reader = ResponseReader.new(self, @sock)
2457
3771
  @sock.sync_close = true
2458
3772
  @sock.hostname = @host if @sock.respond_to? :hostname=
2459
- ssl_socket_connect(@sock, @open_timeout)
2460
- if context.verify_mode != VERIFY_NONE
3773
+ ssl_socket_connect(@sock, open_timeout)
3774
+ if ssl_ctx.verify_mode != VERIFY_NONE
2461
3775
  @sock.post_connection_check(@host)
3776
+ @tls_verified = true
3777
+ end
3778
+ end
3779
+
3780
+ def state_authenticated!(resp = nil)
3781
+ synchronize do
3782
+ @capabilities = capabilities_from_resp_code resp if resp
3783
+ @connection_state = ConnectionState::Authenticated.new
2462
3784
  end
2463
3785
  end
2464
3786
 
3787
+ def state_selected!
3788
+ synchronize do
3789
+ @connection_state = ConnectionState::Selected.new
3790
+ end
3791
+ end
3792
+
3793
+ def state_unselected!
3794
+ state_authenticated! if connection_state.to_sym == :selected
3795
+ end
3796
+
3797
+ def state_logout!
3798
+ synchronize do
3799
+ @connection_state = ConnectionState::Logout.new
3800
+ end
3801
+ end
3802
+
3803
+ def sasl_adapter
3804
+ SASLAdapter.new(self, &method(:send_command_with_continuations))
3805
+ end
3806
+
3807
+ #--
3808
+ # We could get the saslprep method by extending the SASLprep module
3809
+ # directly. It's done indirectly, so SASLprep can be lazily autoloaded,
3810
+ # because most users won't need it.
3811
+ #++
3812
+ # Delegates to Net::IMAP::StringPrep::SASLprep#saslprep.
3813
+ def self.saslprep(string, **opts)
3814
+ Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
3815
+ end
3816
+
2465
3817
  end
2466
3818
  end
2467
3819
 
2468
3820
  require_relative "imap/errors"
3821
+ require_relative "imap/config"
2469
3822
  require_relative "imap/command_data"
2470
3823
  require_relative "imap/data_encoding"
3824
+ require_relative "imap/data_lite"
2471
3825
  require_relative "imap/flags"
2472
3826
  require_relative "imap/response_data"
2473
3827
  require_relative "imap/response_parser"
2474
3828
  require_relative "imap/authenticators"
2475
- require_relative "imap/sasl"
3829
+
3830
+ require_relative "imap/deprecated_client_options"
3831
+ Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions