net-imap 0.3.7 → 0.5.6

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