net-imap 0.3.4 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
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 +21 -9
  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 -31
  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,77 +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: The untagged responses, as a hash. Keys are the untagged
515
- # response type (e.g. "OK", "FETCH", "FLAGS") and response code (e.g.
516
- # "ALERT", "UIDVALIDITY", "UIDNEXT", "TRYCREATE", etc). Values are arrays
517
- # of UntaggedResponse or ResponseCode.
518
- # - #add_response_handler: Add a block to be called inside the receiver thread
519
- # with every server response.
520
- # - #remove_response_handler: Remove a previously added response handler.
511
+ # ==== RFC7162: +CONDSTORE+
521
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.
522
547
  #
523
548
  # == References
524
- #--
525
- # TODO: Consider moving references list to REFERENCES.md or REFERENCES.rdoc.
526
- #++
527
549
  #
528
550
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
529
551
  # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
@@ -553,57 +575,57 @@ module Net
553
575
  # Gahrns, M., "IMAP4 Multi-Accessed Mailbox Practice", RFC 2180, DOI
554
576
  # 10.17487/RFC2180, July 1997, <https://www.rfc-editor.org/info/rfc2180>.
555
577
  #
556
- # [UTF7[https://tools.ietf.org/html/rfc2152]]::
578
+ # [UTF7[https://www.rfc-editor.org/rfc/rfc2152]]::
557
579
  # Goldsmith, D. and M. Davis, "UTF-7 A Mail-Safe Transformation Format of
558
580
  # Unicode", RFC 2152, DOI 10.17487/RFC2152, May 1997,
559
581
  # <https://www.rfc-editor.org/info/rfc2152>.
560
582
  #
561
583
  # === Message envelope and body structure
562
584
  #
563
- # [RFC5322[https://tools.ietf.org/html/rfc5322]]::
585
+ # [RFC5322[https://www.rfc-editor.org/rfc/rfc5322]]::
564
586
  # Resnick, P., Ed., "Internet Message Format",
565
587
  # RFC 5322, DOI 10.17487/RFC5322, October 2008,
566
588
  # <https://www.rfc-editor.org/info/rfc5322>.
567
589
  #
568
590
  # <em>Note: obsoletes</em>
569
- # RFC-2822[https://tools.ietf.org/html/rfc2822]<em> (April 2001) and</em>
570
- # 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>
571
593
  #
572
- # [CHARSET[https://tools.ietf.org/html/rfc2978]]::
594
+ # [CHARSET[https://www.rfc-editor.org/rfc/rfc2978]]::
573
595
  # Freed, N. and J. Postel, "IANA Charset Registration Procedures", BCP 19,
574
596
  # RFC 2978, DOI 10.17487/RFC2978, October 2000,
575
597
  # <https://www.rfc-editor.org/info/rfc2978>.
576
598
  #
577
- # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]::
599
+ # [DISPOSITION[https://www.rfc-editor.org/rfc/rfc2183]]::
578
600
  # Troost, R., Dorner, S., and K. Moore, Ed., "Communicating Presentation
579
601
  # Information in Internet Messages: The Content-Disposition Header
580
602
  # Field", RFC 2183, DOI 10.17487/RFC2183, August 1997,
581
603
  # <https://www.rfc-editor.org/info/rfc2183>.
582
604
  #
583
- # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]::
605
+ # [MIME-IMB[https://www.rfc-editor.org/rfc/rfc2045]]::
584
606
  # Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions
585
607
  # (MIME) Part One: Format of Internet Message Bodies",
586
608
  # RFC 2045, DOI 10.17487/RFC2045, November 1996,
587
609
  # <https://www.rfc-editor.org/info/rfc2045>.
588
610
  #
589
- # [MIME-IMT[https://tools.ietf.org/html/rfc2046]]::
611
+ # [MIME-IMT[https://www.rfc-editor.org/rfc/rfc2046]]::
590
612
  # Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions
591
613
  # (MIME) Part Two: Media Types", RFC 2046, DOI 10.17487/RFC2046,
592
614
  # November 1996, <https://www.rfc-editor.org/info/rfc2046>.
593
615
  #
594
- # [MIME-HDRS[https://tools.ietf.org/html/rfc2047]]::
616
+ # [MIME-HDRS[https://www.rfc-editor.org/rfc/rfc2047]]::
595
617
  # Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part Three:
596
618
  # Message Header Extensions for Non-ASCII Text",
597
619
  # RFC 2047, DOI 10.17487/RFC2047, November 1996,
598
620
  # <https://www.rfc-editor.org/info/rfc2047>.
599
621
  #
600
- # [RFC2231[https://tools.ietf.org/html/rfc2231]]::
622
+ # [RFC2231[https://www.rfc-editor.org/rfc/rfc2231]]::
601
623
  # Freed, N. and K. Moore, "MIME Parameter Value and Encoded Word
602
624
  # Extensions: Character Sets, Languages, and Continuations",
603
625
  # RFC 2231, DOI 10.17487/RFC2231, November 1997,
604
626
  # <https://www.rfc-editor.org/info/rfc2231>.
605
627
  #
606
- # [I18n-HDRS[https://tools.ietf.org/html/rfc6532]]::
628
+ # [I18n-HDRS[https://www.rfc-editor.org/rfc/rfc6532]]::
607
629
  # Yang, A., Steele, S., and N. Freed, "Internationalized Email Headers",
608
630
  # RFC 6532, DOI 10.17487/RFC6532, February 2012,
609
631
  # <https://www.rfc-editor.org/info/rfc6532>.
@@ -619,42 +641,40 @@ module Net
619
641
  # RFC 2557, DOI 10.17487/RFC2557, March 1999,
620
642
  # <https://www.rfc-editor.org/info/rfc2557>.
621
643
  #
622
- # [MD5[https://tools.ietf.org/html/rfc1864]]::
644
+ # [MD5[https://www.rfc-editor.org/rfc/rfc1864]]::
623
645
  # Myers, J. and M. Rose, "The Content-MD5 Header Field",
624
646
  # RFC 1864, DOI 10.17487/RFC1864, October 1995,
625
647
  # <https://www.rfc-editor.org/info/rfc1864>.
626
648
  #
627
- #--
628
- # 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>.
629
654
  #
630
- # [RFC3503[https://tools.ietf.org/html/rfc3503]]
631
- # Melnikov, A., "Message Disposition Notification (MDN)
632
- # profile for Internet Message Access Protocol (IMAP)",
633
- # RFC 3503, DOI 10.17487/RFC3503, March 2003,
634
- # <https://www.rfc-editor.org/info/rfc3503>.
635
- #++
655
+ # === \IMAP Extensions
636
656
  #
637
- # === Supported \IMAP Extensions
638
- #
639
- # [QUOTA[https://tools.ietf.org/html/rfc2087]]::
640
- # Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
641
- # January 1997, <https://www.rfc-editor.org/info/rfc2087>.
642
- #--
643
- # TODO: test compatibility with updated QUOTA extension:
644
- # [QUOTA[https://tools.ietf.org/html/rfc9208]]::
657
+ # [QUOTA[https://www.rfc-editor.org/rfc/rfc9208]]::
645
658
  # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
646
659
  # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
647
- #++
648
- # [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]]::
649
665
  # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
650
666
  # June 1997, <https://www.rfc-editor.org/info/rfc2177>.
651
- # [NAMESPACE[https://tools.ietf.org/html/rfc2342]]::
667
+ # [NAMESPACE[https://www.rfc-editor.org/rfc/rfc2342]]::
652
668
  # Gahrns, M. and C. Newman, "IMAP4 Namespace", RFC 2342,
653
669
  # DOI 10.17487/RFC2342, May 1998, <https://www.rfc-editor.org/info/rfc2342>.
654
- # [ID[https://tools.ietf.org/html/rfc2971]]::
670
+ # [ID[https://www.rfc-editor.org/rfc/rfc2971]]::
655
671
  # Showalter, T., "IMAP4 ID extension", RFC 2971, DOI 10.17487/RFC2971,
656
672
  # October 2000, <https://www.rfc-editor.org/info/rfc2971>.
657
- # [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]]::
658
678
  # Melnikov, A., "IMAP4 Access Control List (ACL) Extension", RFC 4314,
659
679
  # DOI 10.17487/RFC4314, December 2005,
660
680
  # <https://www.rfc-editor.org/info/rfc4314>.
@@ -662,46 +682,79 @@ module Net
662
682
  # Crispin, M., "Internet Message Access Protocol (\IMAP) - UIDPLUS
663
683
  # extension", RFC 4315, DOI 10.17487/RFC4315, December 2005,
664
684
  # <https://www.rfc-editor.org/info/rfc4315>.
665
- # [SORT[https://tools.ietf.org/html/rfc5256]]::
685
+ # [SORT[https://www.rfc-editor.org/rfc/rfc5256]]::
666
686
  # Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and
667
687
  # THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008,
668
688
  # <https://www.rfc-editor.org/info/rfc5256>.
669
- # [THREAD[https://tools.ietf.org/html/rfc5256]]::
689
+ # [THREAD[https://www.rfc-editor.org/rfc/rfc5256]]::
670
690
  # Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and
671
691
  # THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008,
672
692
  # <https://www.rfc-editor.org/info/rfc5256>.
673
693
  # [RFC5530[https://www.rfc-editor.org/rfc/rfc5530.html]]::
674
694
  # Gulbrandsen, A., "IMAP Response Codes", RFC 5530, DOI 10.17487/RFC5530,
675
695
  # May 2009, <https://www.rfc-editor.org/info/rfc5530>.
676
- # [MOVE[https://tools.ietf.org/html/rfc6851]]::
696
+ # [MOVE[https://www.rfc-editor.org/rfc/rfc6851]]::
677
697
  # Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
678
698
  # (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
679
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>.
680
725
  #
681
726
  # === IANA registries
682
- #
683
727
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
684
728
  # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
685
729
  # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
686
730
  # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
687
731
  # * {IMAP Threading Algorithms}[https://www.iana.org/assignments/imap-threading-algorithms/imap-threading-algorithms.xhtml]
688
- #--
689
- # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
690
- # * [{LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
691
- # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
692
- # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
693
- # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
694
- # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
695
- #++
696
732
  # * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
697
733
  # * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]:
698
734
  # +imap+: tcp/143, +imaps+: tcp/993
699
735
  # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
700
736
  # +imap+
701
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]
702
745
  #
703
746
  class IMAP < Protocol
704
- VERSION = "0.3.4"
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__)
705
758
 
706
759
  include MonitorMixin
707
760
  if defined?(OpenSSL::SSL)
@@ -709,41 +762,15 @@ module Net
709
762
  include SSL
710
763
  end
711
764
 
712
- # Returns the initial greeting the server, an UntaggedResponse.
713
- attr_reader :greeting
714
-
715
- # Returns recorded untagged responses.
716
- #
717
- # For example:
718
- #
719
- # imap.select("inbox")
720
- # p imap.responses["EXISTS"][-1]
721
- # #=> 2
722
- # p imap.responses["UIDVALIDITY"][-1]
723
- # #=> 968263756
724
- attr_reader :responses
725
-
726
- # Returns all response handlers.
727
- attr_reader :response_handlers
728
-
729
- # Seconds to wait until a connection is opened.
730
- # If the IMAP object cannot open a connection within this time,
731
- # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
732
- attr_reader :open_timeout
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