net-imap 0.3.7 → 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pages.yml +46 -0
  3. data/.github/workflows/test.yml +5 -12
  4. data/.gitignore +2 -0
  5. data/Gemfile +3 -0
  6. data/README.md +15 -4
  7. data/Rakefile +0 -7
  8. data/docs/styles.css +0 -12
  9. data/lib/net/imap/authenticators.rb +26 -57
  10. data/lib/net/imap/command_data.rb +13 -6
  11. data/lib/net/imap/data_encoding.rb +14 -2
  12. data/lib/net/imap/deprecated_client_options.rb +139 -0
  13. data/lib/net/imap/errors.rb +20 -0
  14. data/lib/net/imap/fetch_data.rb +518 -0
  15. data/lib/net/imap/response_data.rb +178 -255
  16. data/lib/net/imap/response_parser/parser_utils.rb +240 -0
  17. data/lib/net/imap/response_parser.rb +1722 -1193
  18. data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
  19. data/lib/net/imap/sasl/authentication_exchange.rb +107 -0
  20. data/lib/net/imap/sasl/authenticators.rb +118 -0
  21. data/lib/net/imap/sasl/client_adapter.rb +72 -0
  22. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +21 -11
  23. data/lib/net/imap/sasl/digest_md5_authenticator.rb +180 -0
  24. data/lib/net/imap/sasl/external_authenticator.rb +83 -0
  25. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  26. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +25 -16
  27. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
  28. data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
  29. data/lib/net/imap/sasl/protocol_adapters.rb +45 -0
  30. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  31. data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
  32. data/lib/net/imap/sasl/stringprep.rb +6 -66
  33. data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
  34. data/lib/net/imap/sasl.rb +144 -43
  35. data/lib/net/imap/sasl_adapter.rb +21 -0
  36. data/lib/net/imap/search_result.rb +150 -0
  37. data/lib/net/imap/sequence_set.rb +1414 -0
  38. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  39. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  40. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  41. data/lib/net/imap/stringprep/tables.rb +146 -0
  42. data/lib/net/imap/stringprep/trace.rb +85 -0
  43. data/lib/net/imap/stringprep.rb +159 -0
  44. data/lib/net/imap.rb +1213 -636
  45. data/net-imap.gemspec +5 -3
  46. data/rakelib/benchmarks.rake +91 -0
  47. data/rakelib/saslprep.rake +4 -4
  48. data/rakelib/string_prep_tables_generator.rb +82 -60
  49. metadata +34 -14
  50. data/benchmarks/stringprep.yml +0 -65
  51. data/benchmarks/table-regexps.yml +0 -39
  52. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  53. data/lib/net/imap/authenticators/plain.rb +0 -41
  54. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  55. data/lib/net/imap/sasl/saslprep.rb +0 -55
  56. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  57. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
data/lib/net/imap.rb CHANGED
@@ -24,11 +24,9 @@ end
24
24
  module Net
25
25
 
26
26
  # Net::IMAP implements Internet Message Access Protocol (\IMAP) client
27
- # functionality. The protocol is described in
28
- # [IMAP4rev1[https://tools.ietf.org/html/rfc3501]].
29
- #--
30
- # TODO: and [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
31
- #++
27
+ # functionality. The protocol is described
28
+ # in {IMAP4rev1 [RFC3501]}[https://tools.ietf.org/html/rfc3501]
29
+ # and {IMAP4rev2 [RFC9051]}[https://tools.ietf.org/html/rfc9051].
32
30
  #
33
31
  # == \IMAP Overview
34
32
  #
@@ -77,31 +75,22 @@ module Net
77
75
  # UIDs have to be reassigned. An \IMAP client thus cannot
78
76
  # rearrange message orders.
79
77
  #
80
- # === Server capabilities and protocol extensions
78
+ # === Examples of Usage
81
79
  #
82
- # Net::IMAP <em>does not modify its behavior</em> according to server
83
- # #capability. Users of the class must check for required capabilities before
84
- # issuing commands. Special care should be taken to follow all #capability
85
- # requirements for #starttls, #login, and #authenticate.
86
- #
87
- # See the #capability method for more information.
88
- #
89
- # == Examples of Usage
90
- #
91
- # === List sender and subject of all recent messages in the default mailbox
80
+ # ==== List sender and subject of all recent messages in the default mailbox
92
81
  #
93
82
  # imap = Net::IMAP.new('mail.example.com')
94
- # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
83
+ # imap.authenticate('PLAIN', 'joe_user', 'joes_password')
95
84
  # imap.examine('INBOX')
96
85
  # imap.search(["RECENT"]).each do |message_id|
97
86
  # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
98
87
  # puts "#{envelope.from[0].name}: \t#{envelope.subject}"
99
88
  # end
100
89
  #
101
- # === Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
90
+ # ==== Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
102
91
  #
103
92
  # imap = Net::IMAP.new('mail.example.com')
104
- # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
93
+ # imap.authenticate('PLAIN', 'joe_user', 'joes_password')
105
94
  # imap.select('Mail/sent-mail')
106
95
  # if not imap.list('Mail/', 'sent-apr03')
107
96
  # imap.create('Mail/sent-apr03')
@@ -112,12 +101,96 @@ module Net
112
101
  # end
113
102
  # imap.expunge
114
103
  #
104
+ # == Capabilities
105
+ #
106
+ # Most Net::IMAP methods do not _currently_ modify their behaviour according
107
+ # to the server's advertised #capabilities. Users of this class must check
108
+ # that the server is capable of extension commands or command arguments before
109
+ # sending them. Special care should be taken to follow the #capabilities
110
+ # requirements for #starttls, #login, and #authenticate.
111
+ #
112
+ # See #capable?, #auth_capable?, #capabilities, #auth_mechanisms to discover
113
+ # server capabilities. For relevant capability requirements, see the
114
+ # documentation on each \IMAP command.
115
+ #
116
+ # imap = Net::IMAP.new("mail.example.com")
117
+ # imap.capable?(:IMAP4rev1) or raise "Not an IMAP4rev1 server"
118
+ # imap.capable?(:starttls) or raise "Cannot start TLS"
119
+ # imap.starttls
120
+ #
121
+ # if imap.auth_capable?("PLAIN")
122
+ # imap.authenticate "PLAIN", username, password
123
+ # elsif !imap.capability?("LOGINDISABLED")
124
+ # imap.login username, password
125
+ # else
126
+ # raise "No acceptable authentication mechanisms"
127
+ # end
128
+ #
129
+ # # Support for "UTF8=ACCEPT" implies support for "ENABLE"
130
+ # imap.enable :utf8 if imap.capable?("UTF8=ACCEPT")
131
+ #
132
+ # namespaces = imap.namespace if imap.capable?(:namespace)
133
+ # mbox_prefix = namespaces&.personal&.first&.prefix || ""
134
+ # mbox_delim = namespaces&.personal&.first&.delim || "/"
135
+ # mbox_path = prefix + %w[path to my mailbox].join(delim)
136
+ # imap.create mbox_path
137
+ #
138
+ # === Basic IMAP4rev1 capabilities
139
+ #
140
+ # IMAP4rev1 servers must advertise +IMAP4rev1+ in their capabilities list.
141
+ # IMAP4rev1 servers must _implement_ the +STARTTLS+, <tt>AUTH=PLAIN</tt>,
142
+ # and +LOGINDISABLED+ capabilities. See #starttls, #login, and #authenticate
143
+ # for the implications of these capabilities.
144
+ #
145
+ # === Caching +CAPABILITY+ responses
146
+ #
147
+ # Net::IMAP automatically stores and discards capability data according to the
148
+ # the requirements and recommendations in
149
+ # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
150
+ # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
151
+ # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
152
+ # Use #capable?, #auth_capable?, or #capabilities to use this cache and avoid
153
+ # sending the #capability command unnecessarily.
154
+ #
155
+ # The server may advertise its initial capabilities using the +CAPABILITY+
156
+ # ResponseCode in a +PREAUTH+ or +OK+ #greeting. When TLS has started
157
+ # (#starttls) and after authentication (#login or #authenticate), the server's
158
+ # capabilities may change and cached capabilities are discarded. The server
159
+ # may send updated capabilities with an +OK+ TaggedResponse to #login or
160
+ # #authenticate, and these will be cached by Net::IMAP. But the
161
+ # TaggedResponse to #starttls MUST be ignored--it is sent before TLS starts
162
+ # and is unprotected.
163
+ #
164
+ # When storing capability values to variables, be careful that they are
165
+ # discarded or reset appropriately, especially following #starttls.
166
+ #
167
+ # === Using IMAP4rev1 extensions
168
+ #
169
+ # See the {IANA IMAP4 capabilities
170
+ # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list of
171
+ # all standard capabilities, and their reference RFCs.
172
+ #
173
+ # IMAP4rev1 servers must not activate behavior that is incompatible with the
174
+ # base specification until an explicit client action invokes a capability,
175
+ # e.g. sending a command or command argument specific to that capability.
176
+ # Servers may send data with backward compatible behavior, such as response
177
+ # codes or mailbox attributes, at any time without client action.
178
+ #
179
+ # Invoking capabilities which are unknown to Net::IMAP may cause unexpected
180
+ # behavior and errors. For example, ResponseParseError is raised when
181
+ # unknown response syntax is received. Invoking commands or command
182
+ # parameters that are unsupported by the server may raise NoResponseError,
183
+ # BadResponseError, or cause other unexpected behavior.
184
+ #
185
+ # Some capabilities must be explicitly activated using the #enable command.
186
+ # See #enable for details.
187
+ #
115
188
  # == Thread Safety
116
189
  #
117
190
  # Net::IMAP supports concurrent threads. For example,
118
191
  #
119
192
  # imap = Net::IMAP.new("imap.foo.net", "imap2")
120
- # imap.authenticate("cram-md5", "bar", "password")
193
+ # imap.authenticate("scram-md5", "bar", "password")
121
194
  # imap.select("inbox")
122
195
  # fetch_thread = Thread.start { imap.fetch(1..-1, "UID") }
123
196
  # search_result = imap.search(["BODY", "hello"])
@@ -173,24 +246,54 @@ module Net
173
246
  # == What's here?
174
247
  #
175
248
  # * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods]
176
- # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
177
- # * {...for any state}[rdoc-ref:Net::IMAP@IMAP+commands+for+any+state]
178
- # * {...for the "not authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Not+Authenticated-22+state]
179
- # * {...for the "authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Authenticated-22+state]
180
- # * {...for the "selected" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Selected-22+state]
181
- # * {...for the "logout" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Logout-22+state]
182
- # * {Supported IMAP extensions}[rdoc-ref:Net::IMAP@Supported+IMAP+extensions]
249
+ # * {Server capabilities}[rdoc-ref:Net::IMAP@Server+capabilities]
183
250
  # * {Handling server responses}[rdoc-ref:Net::IMAP@Handling+server+responses]
251
+ # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
252
+ # * {for any state}[rdoc-ref:Net::IMAP@Any+state]
253
+ # * {for the "not authenticated" state}[rdoc-ref:Net::IMAP@Not+Authenticated+state]
254
+ # * {for the "authenticated" state}[rdoc-ref:Net::IMAP@Authenticated+state]
255
+ # * {for the "selected" state}[rdoc-ref:Net::IMAP@Selected+state]
256
+ # * {for the "logout" state}[rdoc-ref:Net::IMAP@Logout+state]
257
+ # * {IMAP extension support}[rdoc-ref:Net::IMAP@IMAP+extension+support]
184
258
  #
185
259
  # === Connection control methods
186
260
  #
187
- # - Net::IMAP.new: A new client connects immediately and waits for a
188
- # successful server greeting before returning the new client object.
261
+ # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
262
+ # waits for a successful server greeting before the method returns.
189
263
  # - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
190
264
  # - #logout: Tells the server to end the session. Enters the "_logout_" state.
191
265
  # - #disconnect: Disconnects the connection (without sending #logout first).
192
266
  # - #disconnected?: True if the connection has been closed.
193
267
  #
268
+ # === Server capabilities
269
+ #
270
+ # - #capable?: Returns whether the server supports a given capability.
271
+ # - #capabilities: Returns the server's capabilities as an array of strings.
272
+ # - #auth_capable?: Returns whether the server advertises support for a given
273
+ # SASL mechanism, for use with #authenticate.
274
+ # - #auth_mechanisms: Returns the #authenticate SASL mechanisms which
275
+ # the server claims to support as an array of strings.
276
+ # - #clear_cached_capabilities: Clears cached capabilities.
277
+ #
278
+ # <em>The capabilities cache is automatically cleared after completing
279
+ # #starttls, #login, or #authenticate.</em>
280
+ # - #capability: Sends the +CAPABILITY+ command and returns the #capabilities.
281
+ #
282
+ # <em>In general, #capable? should be used rather than explicitly sending a
283
+ # +CAPABILITY+ command to the server.</em>
284
+ #
285
+ # === Handling server responses
286
+ #
287
+ # - #greeting: The server's initial untagged response, which can indicate a
288
+ # pre-authenticated connection.
289
+ # - #responses: Yields unhandled UntaggedResponse#data and <em>non-+nil+</em>
290
+ # ResponseCode#data.
291
+ # - #clear_responses: Deletes unhandled data from #responses and returns it.
292
+ # - #add_response_handler: Add a block to be called inside the receiver thread
293
+ # with every server response.
294
+ # - #response_handlers: Returns the list of response handlers.
295
+ # - #remove_response_handler: Remove a previously added response handler.
296
+ #
194
297
  # === Core \IMAP commands
195
298
  #
196
299
  # The following commands are defined either by
@@ -199,69 +302,48 @@ module Net
199
302
  # [IDLE[https://tools.ietf.org/html/rfc2177]],
200
303
  # [NAMESPACE[https://tools.ietf.org/html/rfc2342]],
201
304
  # [UNSELECT[https://tools.ietf.org/html/rfc3691]],
202
- #--
203
- # TODO: [ENABLE[https://tools.ietf.org/html/rfc5161]],
204
- # TODO: [LIST-EXTENDED[https://tools.ietf.org/html/rfc5258]],
205
- # TODO: [LIST-STATUS[https://tools.ietf.org/html/rfc5819]],
206
- #++
305
+ # [ENABLE[https://tools.ietf.org/html/rfc5161]],
207
306
  # [MOVE[https://tools.ietf.org/html/rfc6851]].
208
307
  # These extensions are widely supported by modern IMAP4rev1 servers and have
209
308
  # all been integrated into [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
210
- # <em>Note: Net::IMAP doesn't fully support IMAP4rev2 yet.</em>
211
- #
212
- #--
213
- # TODO: When IMAP4rev2 is supported, add the following to the each of the
214
- # appropriate commands below.
215
- # Note:: CHECK has been removed from IMAP4rev2.
216
- # Note:: LSUB is obsoleted by +LIST-EXTENDED and has been removed from IMAP4rev2.
217
- # <em>Some arguments require the +LIST-EXTENDED+ or +IMAP4rev2+ capability.</em>
218
- # <em>Requires either the +ENABLE+ or +IMAP4rev2+ capability.</em>
219
- # <em>Requires either the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
220
- # <em>Requires either the +IDLE+ or +IMAP4rev2+ capability.</em>
221
- # <em>Requires either the +UNSELECT+ or +IMAP4rev2+ capability.</em>
222
- # <em>Requires either the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
223
- # <em>Requires either the +MOVE+ or +IMAP4rev2+ capability.</em>
224
- #++
225
- #
226
- # ==== \IMAP commands for any state
309
+ # <em>*NOTE:* Net::IMAP doesn't support IMAP4rev2 yet.</em>
310
+ #
311
+ # ==== Any state
227
312
  #
228
313
  # - #capability: Returns the server's capabilities as an array of strings.
229
314
  #
230
- # <em>Capabilities may change after</em> #starttls, #authenticate, or #login
231
- # <em>and cached capabilities must be reloaded.</em>
315
+ # <em>In general, #capable? should be used rather than explicitly sending a
316
+ # +CAPABILITY+ command to the server.</em>
232
317
  # - #noop: Allows the server to send unsolicited untagged #responses.
233
318
  # - #logout: Tells the server to end the session. Enters the "_logout_" state.
234
319
  #
235
- # ==== \IMAP commands for the "Not Authenticated" state
320
+ # ==== Not Authenticated state
236
321
  #
237
- # In addition to the universal commands, the following commands are valid in
238
- # the "<em>not authenticated</em>" state:
322
+ # In addition to the commands for any state, the following commands are valid
323
+ # in the "<em>not authenticated</em>" state:
239
324
  #
240
325
  # - #starttls: Upgrades a clear-text connection to use TLS.
241
326
  #
242
327
  # <em>Requires the +STARTTLS+ capability.</em>
243
- # - #authenticate: Identifies the client to the server using a {SASL
244
- # mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml].
245
- # Enters the "_authenticated_" state.
328
+ # - #authenticate: Identifies the client to the server using the given
329
+ # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
330
+ # and credentials. Enters the "_authenticated_" state.
246
331
  #
247
- # <em>Requires the <tt>AUTH=#{mechanism}</tt> capability for the chosen
248
- # mechanism.</em>
332
+ # <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
333
+ # supported mechanisms.</em>
249
334
  # - #login: Identifies the client to the server using a plain text password.
250
335
  # Using #authenticate is generally preferred. Enters the "_authenticated_"
251
336
  # state.
252
337
  #
253
338
  # <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
254
339
  #
255
- # ==== \IMAP commands for the "Authenticated" state
256
- #
257
- # In addition to the universal commands, the following commands are valid in
258
- # the "_authenticated_" state:
340
+ # ==== Authenticated state
259
341
  #
260
- #--
261
- # - #enable: <em>Not implemented by Net::IMAP, yet.</em>
342
+ # In addition to the commands for any state, the following commands are valid
343
+ # in the "_authenticated_" state:
262
344
  #
263
- # <em>Requires the +ENABLE+ capability.</em>
264
- #++
345
+ # - #enable: Enables backwards incompatible server extensions.
346
+ # <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
265
347
  # - #select: Open a mailbox and enter the "_selected_" state.
266
348
  # - #examine: Open a mailbox read-only, and enter the "_selected_" state.
267
349
  # - #create: Creates a new mailbox.
@@ -271,37 +353,31 @@ module Net
271
353
  # - #unsubscribe: Removes a mailbox from the "subscribed" set.
272
354
  # - #list: Returns names and attributes of mailboxes matching a given pattern.
273
355
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
274
- #
275
- # <em>Requires the +NAMESPACE+ capability.</em>
356
+ # <em>Requires the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
276
357
  # - #status: Returns mailbox information, e.g. message count, unseen message
277
358
  # count, +UIDVALIDITY+ and +UIDNEXT+.
278
359
  # - #append: Appends a message to the end of a mailbox.
279
360
  # - #idle: Allows the server to send updates to the client, without the client
280
361
  # needing to poll using #noop.
362
+ # <em>Requires the +IDLE+ or +IMAP4rev2+ capability.</em>
363
+ # - *Obsolete* #lsub: <em>Replaced by <tt>LIST-EXTENDED</tt> and removed from
364
+ # +IMAP4rev2+.</em> Lists mailboxes in the "subscribed" set.
281
365
  #
282
- # <em>Requires the +IDLE+ capability.</em>
283
- # - #lsub: Lists mailboxes the user has declared "active" or "subscribed".
284
- #--
285
- # <em>Replaced by</em> <tt>LIST-EXTENDED</tt> <em>and removed from</em>
286
- # +IMAP4rev2+. <em>However, Net::IMAP hasn't implemented</em>
287
- # <tt>LIST-EXTENDED</tt> _yet_.
288
- #++
366
+ # <em>*Note:* Net::IMAP hasn't implemented <tt>LIST-EXTENDED</tt> yet.</em>
289
367
  #
290
- # ==== \IMAP commands for the "Selected" state
368
+ # ==== Selected state
291
369
  #
292
- # In addition to the universal commands and the "authenticated" commands, the
293
- # following commands are valid in the "_selected_" state:
370
+ # In addition to the commands for any state and the "_authenticated_"
371
+ # commands, the following commands are valid in the "_selected_" state:
294
372
  #
295
373
  # - #close: Closes the mailbox and returns to the "_authenticated_" state,
296
374
  # expunging deleted messages, unless the mailbox was opened as read-only.
297
375
  # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
298
376
  # without expunging any messages.
299
- #
300
- # <em>Requires the +UNSELECT+ capability.</em>
377
+ # <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
301
378
  # - #expunge: Permanently removes messages which have the Deleted flag set.
302
- # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
303
- #
304
- # <em>Requires the +UIDPLUS+ capability.</em>
379
+ # - #uid_expunge: Restricts expunge to only remove the specified UIDs.
380
+ # <em>Requires the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
305
381
  # - #search, #uid_search: Returns sequence numbers or UIDs of messages that
306
382
  # match the given searching criteria.
307
383
  # - #fetch, #uid_fetch: Returns data associated with a set of messages,
@@ -311,45 +387,33 @@ module Net
311
387
  # specified destination mailbox.
312
388
  # - #move, #uid_move: Moves the specified messages to the end of the
313
389
  # specified destination mailbox, expunging them from the current mailbox.
390
+ # <em>Requires the +MOVE+ or +IMAP4rev2+ capability.</em>
391
+ # - #check: <em>*Obsolete:* removed from +IMAP4rev2+.</em>
392
+ # Can be replaced with #noop or #idle.
314
393
  #
315
- # <em>Requires the +MOVE+ capability.</em>
316
- # - #check: Mostly obsolete. Can be replaced with #noop or #idle.
317
- #--
318
- # <em>Removed from IMAP4rev2.</em>
319
- #++
320
- #
321
- # ==== \IMAP commands for the "Logout" state
394
+ # ==== Logout state
322
395
  #
323
- # No \IMAP commands are valid in the +logout+ state. If the socket is still
396
+ # No \IMAP commands are valid in the "_logout_" state. If the socket is still
324
397
  # open, Net::IMAP will close it after receiving server confirmation.
325
398
  # Exceptions will be raised by \IMAP commands that have already started and
326
399
  # are waiting for a response, as well as any that are called after logout.
327
400
  #
328
- # === Supported \IMAP extensions
401
+ # === \IMAP extension support
329
402
  #
330
403
  # ==== RFC9051: +IMAP4rev2+
331
404
  #
332
- # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is <em>not supported
333
- # yet</em>, Net::IMAP supports several extensions that have been folded into
334
- # it: +IDLE+, +MOVE+, +NAMESPACE+, +UIDPLUS+, and +UNSELECT+.
335
- #--
336
- # TODO: RFC4466, ABNF extensions (automatic support for other extensions)
337
- # TODO: +ESEARCH+, ExtendedSearchData
338
- # TODO: +SEARCHRES+,
339
- # TODO: +ENABLE+,
340
- # TODO: +SASL-IR+,
341
- # TODO: +LIST-EXTENDED+,
342
- # TODO: +LIST-STATUS+,
343
- # TODO: +LITERAL-+,
344
- # TODO: +BINARY+ (only the FETCH side)
345
- # TODO: +SPECIAL-USE+
346
- # implicitly supported, but we can do better: Response codes: RFC5530, etc
347
- # implicitly supported, but we can do better: <tt>STATUS=SIZE</tt>
348
- # implicitly supported, but we can do better: <tt>STATUS DELETED</tt>
349
- #++
350
- # Commands for these extensions are included with the {Core IMAP
351
- # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above. Other supported
352
- # extensons are listed below.
405
+ # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is not supported
406
+ # yet, Net::IMAP supports several extensions that have been folded into it:
407
+ # +ENABLE+, +IDLE+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, +UNSELECT+,
408
+ # <tt>STATUS=SIZE</tt>, and the fetch side of +BINARY+.
409
+ # Commands for these extensions are listed with the {Core IMAP
410
+ # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above.
411
+ #
412
+ # >>>
413
+ # <em>The following are folded into +IMAP4rev2+ but are currently
414
+ # unsupported or incompletely supported by</em> Net::IMAP<em>: RFC4466
415
+ # extensions, +ESEARCH+, +SEARCHRES+, +LIST-EXTENDED+, +LIST-STATUS+,
416
+ # +LITERAL-+, and +SPECIAL-USE+.</em>
353
417
  #
354
418
  # ==== RFC2087: +QUOTA+
355
419
  # - #getquota: returns the resource usage and limits for a quota root
@@ -358,92 +422,56 @@ module Net
358
422
  # - #setquota: sets the resource limits for a given quota root.
359
423
  #
360
424
  # ==== 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].
425
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
426
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
363
427
  # - #idle: Allows the server to send updates to the client, without the client
364
428
  # needing to poll using #noop.
365
429
  #
366
430
  # ==== 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].
431
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
432
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
369
433
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
370
434
  #
371
435
  # ==== RFC2971: +ID+
372
436
  # - #id: exchanges client and server implementation information.
373
437
  #
374
- #--
375
- # ==== RFC3502: +MULTIAPPEND+
376
- # TODO...
377
- #++
378
- #
379
- #--
380
438
  # ==== RFC3516: +BINARY+
381
- # TODO...
382
- #++
439
+ # The fetch side of +BINARY+ has been folded into
440
+ # IMAP4rev2[https://tools.ietf.org/html/rfc9051].
441
+ # - Updates #fetch and #uid_fetch with the +BINARY+, +BINARY.PEEK+, and
442
+ # +BINARY.SIZE+ items. See FetchData#binary and FetchData#binary_size.
443
+ #
444
+ # >>>
445
+ # *NOTE:* The binary extension the #append command is _not_ supported yet.
383
446
  #
384
447
  # ==== 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].
448
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
449
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
387
450
  # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
388
451
  # without expunging any messages.
389
452
  #
390
453
  # ==== RFC4314: +ACL+
391
454
  # - #getacl: lists the authenticated user's access rights to a mailbox.
392
455
  # - #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.
456
+ # >>>
457
+ # *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
397
458
  #
398
459
  # ==== 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].
460
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
461
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
401
462
  # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
402
463
  # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode
403
464
  # - Updates #append with the +APPENDUID+ ResponseCode
404
465
  # - Updates #copy, #move with the +COPYUID+ ResponseCode
405
466
  #
406
- #--
407
- # ==== RFC4466: Collected Extensions to IMAP4 ABNF
408
- # TODO...
409
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this RFC updates
410
- # the protocol to enable new optional parameters to many commands: #select,
411
- # #examine, #create, #rename, #fetch, #uid_fetch, #store, #uid_store, #search,
412
- # #uid_search, and #append. However, specific parameters are not defined.
413
- # Extensions to these commands use this syntax whenever possible. Net::IMAP
414
- # may be partially compatible with extensions to these commands, even without
415
- # any explicit support.
416
- #++
417
- #
418
- #--
419
- # ==== RFC4731 +ESEARCH+
420
- # TODO...
421
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
422
- # - Updates #search, #uid_search to accept result options: +MIN+, +MAX+,
423
- # +ALL+, +COUNT+, and to return ExtendedSearchData.
424
- #++
425
- #
426
- #--
427
467
  # ==== RFC4959: +SASL-IR+
428
- # TODO...
429
468
  # 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
- #++
469
+ # - Updates #authenticate with the option to send an initial response.
437
470
  #
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
- #++
471
+ # ==== RFC5161: +ENABLE+
472
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
473
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
474
+ # - #enable: Enables backwards incompatible server extensions.
447
475
  #
448
476
  # ==== RFC5256: +SORT+
449
477
  # - #sort, #uid_sort: An alternate version of #search or #uid_search which
@@ -453,75 +481,54 @@ module Net
453
481
  # which arranges the results into ordered groups or threads according to a
454
482
  # chosen algorithm.
455
483
  #
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)
484
+ # ==== +X-GM-EXT-1+
485
+ # +X-GM-EXT-1+ is a non-standard Gmail extension. See {Google's
486
+ # documentation}[https://developers.google.com/gmail/imap/imap-extensions].
487
+ # - Updates #fetch and #uid_fetch with support for +X-GM-MSGID+ (unique
488
+ # message ID), +X-GM-THRID+ (thread ID), and +X-GM-LABELS+ (Gmail labels).
489
+ # - Updates #search with the +X-GM-RAW+ search attribute.
475
490
  # - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses.
476
491
  #
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
- #++
492
+ # *NOTE:* The +OBJECTID+ extension should replace +X-GM-MSGID+ and
493
+ # +X-GM-THRID+, but Gmail does not support it (as of 2023-11-10).
483
494
  #
484
495
  # ==== 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].
496
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
497
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
487
498
  # - #move, #uid_move: Moves the specified messages to the end of the
488
499
  # specified destination mailbox, expunging them from the current mailbox.
489
500
  #
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
- #++
501
+ # ==== RFC6855: <tt>UTF8=ACCEPT</tt>, <tt>UTF8=ONLY</tt>
509
502
  #
510
- # === Handling server responses
503
+ # - See #enable for information about support for UTF-8 string encoding.
511
504
  #
512
- # - #greeting: The server's initial untagged response, which can indicate a
513
- # pre-authenticated connection.
514
- # - #responses: A hash with arrays of unhandled <em>non-+nil+</em>
515
- # UntaggedResponse and ResponseCode +#data+, keyed by +#name+.
516
- # - #add_response_handler: Add a block to be called inside the receiver thread
517
- # with every server response.
518
- # - #remove_response_handler: Remove a previously added response handler.
505
+ # ==== RFC7162: +CONDSTORE+
506
+ #
507
+ # - Updates #enable with +CONDSTORE+ parameter. +CONDSTORE+ will also be
508
+ # enabled by using any of the extension's command parameters, listed below.
509
+ # - Updates #status with the +HIGHESTMODSEQ+ status attribute.
510
+ # - Updates #select and #examine with the +condstore+ modifier, and adds
511
+ # either a +HIGHESTMODSEQ+ or +NOMODSEQ+ ResponseCode to the responses.
512
+ # - Updates #search, #uid_search, #sort, and #uid_sort with the +MODSEQ+
513
+ # search criterion, and adds SearchResult#modseq to the search response.
514
+ # - Updates #thread and #uid_thread with the +MODSEQ+ search criterion
515
+ # <em>(but thread responses are unchanged)</em>.
516
+ # - Updates #fetch and #uid_fetch with the +changedsince+ modifier and
517
+ # +MODSEQ+ FetchData attribute.
518
+ # - Updates #store and #uid_store with the +unchangedsince+ modifier and adds
519
+ # the +MODIFIED+ ResponseCode to the tagged response.
520
+ #
521
+ # ==== RFC8438: <tt>STATUS=SIZE</tt>
522
+ # - Updates #status with the +SIZE+ status attribute.
519
523
  #
524
+ # ==== RFC8474: +OBJECTID+
525
+ # - Adds +MAILBOXID+ ResponseCode to #create tagged response.
526
+ # - Adds +MAILBOXID+ ResponseCode to #select and #examine untagged response.
527
+ # - Updates #fetch and #uid_fetch with the +EMAILID+ and +THREADID+ items.
528
+ # See FetchData#emailid and FetchData#emailid.
529
+ # - Updates #status with support for the +MAILBOXID+ status attribute.
520
530
  #
521
531
  # == References
522
- #--
523
- # TODO: Consider moving references list to REFERENCES.md or REFERENCES.rdoc.
524
- #++
525
532
  #
526
533
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
527
534
  # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
@@ -622,27 +629,21 @@ module Net
622
629
  # RFC 1864, DOI 10.17487/RFC1864, October 1995,
623
630
  # <https://www.rfc-editor.org/info/rfc1864>.
624
631
  #
625
- #--
626
- # TODO: Document IMAP keywords.
632
+ # [RFC3503[https://tools.ietf.org/html/rfc3503]]::
633
+ # Melnikov, A., "Message Disposition Notification (MDN)
634
+ # profile for Internet Message Access Protocol (IMAP)",
635
+ # RFC 3503, DOI 10.17487/RFC3503, March 2003,
636
+ # <https://www.rfc-editor.org/info/rfc3503>.
627
637
  #
628
- # [RFC3503[https://tools.ietf.org/html/rfc3503]]
629
- # Melnikov, A., "Message Disposition Notification (MDN)
630
- # profile for Internet Message Access Protocol (IMAP)",
631
- # RFC 3503, DOI 10.17487/RFC3503, March 2003,
632
- # <https://www.rfc-editor.org/info/rfc3503>.
633
- #++
638
+ # === \IMAP Extensions
634
639
  #
635
- # === Supported \IMAP Extensions
636
- #
637
- # [QUOTA[https://tools.ietf.org/html/rfc2087]]::
638
- # Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
639
- # January 1997, <https://www.rfc-editor.org/info/rfc2087>.
640
- #--
641
- # TODO: test compatibility with updated QUOTA extension:
642
640
  # [QUOTA[https://tools.ietf.org/html/rfc9208]]::
643
641
  # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
644
642
  # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
645
- #++
643
+ #
644
+ # <em>Note: obsoletes</em>
645
+ # RFC-2087[https://tools.ietf.org/html/rfc2087]<em> (January 1997)</em>.
646
+ # <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
646
647
  # [IDLE[https://tools.ietf.org/html/rfc2177]]::
647
648
  # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
648
649
  # June 1997, <https://www.rfc-editor.org/info/rfc2177>.
@@ -652,6 +653,10 @@ module Net
652
653
  # [ID[https://tools.ietf.org/html/rfc2971]]::
653
654
  # Showalter, T., "IMAP4 ID extension", RFC 2971, DOI 10.17487/RFC2971,
654
655
  # October 2000, <https://www.rfc-editor.org/info/rfc2971>.
656
+ # [BINARY[https://tools.ietf.org/html/rfc3516]]::
657
+ # Nerenberg, L., "IMAP4 Binary Content Extension", RFC 3516,
658
+ # DOI 10.17487/RFC3516, April 2003,
659
+ # <https://www.rfc-editor.org/info/rfc3516>.
655
660
  # [ACL[https://tools.ietf.org/html/rfc4314]]::
656
661
  # Melnikov, A., "IMAP4 Access Control List (ACL) Extension", RFC 4314,
657
662
  # DOI 10.17487/RFC4314, December 2005,
@@ -675,31 +680,54 @@ module Net
675
680
  # Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
676
681
  # (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
677
682
  # <https://www.rfc-editor.org/info/rfc6851>.
683
+ # [UTF8=ACCEPT[https://tools.ietf.org/html/rfc6855]]::
684
+ # [UTF8=ONLY[https://tools.ietf.org/html/rfc6855]]::
685
+ # Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed.,
686
+ # "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013,
687
+ # <https://www.rfc-editor.org/info/rfc6855>.
688
+ # [CONDSTORE[https://tools.ietf.org/html/rfc7162]]::
689
+ # [QRESYNC[https://tools.ietf.org/html/rfc7162]]::
690
+ # Melnikov, A. and D. Cridland, "IMAP Extensions: Quick Flag Changes
691
+ # Resynchronization (CONDSTORE) and Quick Mailbox Resynchronization
692
+ # (QRESYNC)", RFC 7162, DOI 10.17487/RFC7162, May 2014,
693
+ # <https://www.rfc-editor.org/info/rfc7162>.
694
+ # [OBJECTID[https://tools.ietf.org/html/rfc8474]]::
695
+ # Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
696
+ # RFC 8474, DOI 10.17487/RFC8474, September 2018,
697
+ # <https://www.rfc-editor.org/info/rfc8474>.
678
698
  #
679
699
  # === IANA registries
680
- #
681
700
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
682
701
  # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
683
702
  # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
684
703
  # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
685
704
  # * {IMAP Threading Algorithms}[https://www.iana.org/assignments/imap-threading-algorithms/imap-threading-algorithms.xhtml]
686
- #--
687
- # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
688
- # * [{LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
689
- # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
690
- # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
691
- # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
692
- # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
693
- #++
694
705
  # * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
695
706
  # * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]:
696
707
  # +imap+: tcp/143, +imaps+: tcp/993
697
708
  # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
698
709
  # +imap+
699
710
  # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
711
+ # ===== For currently unsupported features:
712
+ # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
713
+ # * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
714
+ # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
715
+ # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
716
+ # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
717
+ # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
700
718
  #
701
719
  class IMAP < Protocol
702
- VERSION = "0.3.7"
720
+ VERSION = "0.4.9"
721
+
722
+ # Aliases for supported capabilities, to be used with the #enable command.
723
+ ENABLE_ALIASES = {
724
+ utf8: "UTF8=ACCEPT",
725
+ "UTF8=ONLY" => "UTF8=ACCEPT",
726
+ }.freeze
727
+
728
+ autoload :SASL, File.expand_path("imap/sasl", __dir__)
729
+ autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
730
+ autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
703
731
 
704
732
  include MonitorMixin
705
733
  if defined?(OpenSSL::SSL)
@@ -707,35 +735,6 @@ module Net
707
735
  include SSL
708
736
  end
709
737
 
710
- # Returns the initial greeting the server, an UntaggedResponse.
711
- attr_reader :greeting
712
-
713
- # Returns a hash with arrays of unhandled <em>non-+nil+</em>
714
- # UntaggedResponse#data keyed by UntaggedResponse#name, and
715
- # ResponseCode#data keyed by ResponseCode#name.
716
- #
717
- # For example:
718
- #
719
- # imap.select("inbox")
720
- # p imap.responses["EXISTS"][-1]
721
- # #=> 2
722
- # p imap.responses["UIDVALIDITY"][-1]
723
- # #=> 968263756
724
- attr_reader :responses
725
-
726
- # Returns all response handlers.
727
- attr_reader :response_handlers
728
-
729
- # Seconds to wait until a connection is opened.
730
- # If the IMAP object cannot open a connection within this time,
731
- # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
732
- attr_reader :open_timeout
733
-
734
- # Seconds to wait until an IDLE response is received.
735
- attr_reader :idle_response_timeout
736
-
737
- attr_accessor :client_thread # :nodoc:
738
-
739
738
  # Returns the debug mode.
740
739
  def self.debug
741
740
  return @@debug
@@ -762,9 +761,175 @@ module Net
762
761
  alias default_ssl_port default_tls_port
763
762
  end
764
763
 
764
+ # Returns the initial greeting the server, an UntaggedResponse.
765
+ attr_reader :greeting
766
+
767
+ # Seconds to wait until a connection is opened.
768
+ # If the IMAP object cannot open a connection within this time,
769
+ # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
770
+ attr_reader :open_timeout
771
+
772
+ # Seconds to wait until an IDLE response is received.
773
+ attr_reader :idle_response_timeout
774
+
775
+ # The hostname this client connected to
776
+ attr_reader :host
777
+
778
+ # The port this client connected to
779
+ attr_reader :port
780
+
781
+ # Returns the
782
+ # {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]
783
+ # used by the SSLSocket when TLS is attempted, even when the TLS handshake
784
+ # is unsuccessful. The context object will be frozen.
785
+ #
786
+ # Returns +nil+ for a plaintext connection.
787
+ attr_reader :ssl_ctx
788
+
789
+ # Returns the parameters that were sent to #ssl_ctx
790
+ # {set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]
791
+ # when the connection tries to use TLS (even when unsuccessful).
792
+ #
793
+ # Returns +false+ for a plaintext connection.
794
+ attr_reader :ssl_ctx_params
795
+
796
+ # Creates a new Net::IMAP object and connects it to the specified
797
+ # +host+.
798
+ #
799
+ # ==== Options
800
+ #
801
+ # Accepts the following options:
802
+ #
803
+ # [port]
804
+ # Port number. Defaults to 993 when +ssl+ is truthy, and 143 otherwise.
805
+ #
806
+ # [ssl]
807
+ # If +true+, the connection will use TLS with the default params set by
808
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params].
809
+ # If +ssl+ is a hash, it's passed to
810
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
811
+ # the keys are names of attribute assignment methods on
812
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
813
+ #
814
+ # [open_timeout]
815
+ # Seconds to wait until a connection is opened
816
+ # [idle_response_timeout]
817
+ # Seconds to wait until an IDLE response is received
818
+ #
819
+ # See DeprecatedClientOptions.new for deprecated arguments.
820
+ #
821
+ # ==== Examples
822
+ #
823
+ # Connect to cleartext port 143 at mail.example.com and recieve the server greeting:
824
+ # imap = Net::IMAP.new('mail.example.com', ssl: false) # => #<Net::IMAP:0x00007f79b0872bd0>
825
+ # imap.port => 143
826
+ # imap.tls_verified? => false
827
+ # imap.greeting => name: ("OK" | "PREAUTH") => status
828
+ # status # => "OK"
829
+ # # The client is connected in the "Not Authenticated" state.
830
+ #
831
+ # Connect with TLS to port 993
832
+ # imap = Net::IMAP.new('mail.example.com', ssl: true) # => #<Net::IMAP:0x00007f79b0872bd0>
833
+ # imap.port => 993
834
+ # imap.tls_verified? => true
835
+ # imap.greeting => name: (/OK/i | /PREAUTH/i) => status
836
+ # case status
837
+ # in /OK/i
838
+ # # The client is connected in the "Not Authenticated" state.
839
+ # imap.authenticate("PLAIN", "joe_user", "joes_password")
840
+ # in /PREAUTH/i
841
+ # # The client is connected in the "Authenticated" state.
842
+ # end
843
+ #
844
+ # Connect with prior authentication, for example using an SSL certificate:
845
+ # ssl_ctx_params = {
846
+ # cert: OpenSSL::X509::Certificate.new(File.read("client.crt")),
847
+ # key: OpenSSL::PKey::EC.new(File.read('client.key')),
848
+ # extra_chain_cert: [
849
+ # OpenSSL::X509::Certificate.new(File.read("intermediate.crt")),
850
+ # ],
851
+ # }
852
+ # imap = Net::IMAP.new('mail.example.com', ssl: ssl_ctx_params)
853
+ # imap.port => 993
854
+ # imap.tls_verified? => true
855
+ # imap.greeting => name: "PREAUTH"
856
+ # # The client is connected in the "Authenticated" state.
857
+ #
858
+ # ==== Exceptions
859
+ #
860
+ # The most common errors are:
861
+ #
862
+ # [Errno::ECONNREFUSED]
863
+ # Connection refused by +host+ or an intervening firewall.
864
+ # [Errno::ETIMEDOUT]
865
+ # Connection timed out (possibly due to packets being dropped by an
866
+ # intervening firewall).
867
+ # [Errno::ENETUNREACH]
868
+ # There is no route to that network.
869
+ # [SocketError]
870
+ # Hostname not known or other socket error.
871
+ # [Net::IMAP::ByeResponseError]
872
+ # Connected to the host successfully, but it immediately said goodbye.
873
+ #
874
+ def initialize(host, port: nil, ssl: nil,
875
+ open_timeout: 30, idle_response_timeout: 5)
876
+ super()
877
+ # Config options
878
+ @host = host
879
+ @port = port || (ssl ? SSL_PORT : PORT)
880
+ @open_timeout = Integer(open_timeout)
881
+ @idle_response_timeout = Integer(idle_response_timeout)
882
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
883
+
884
+ # Basic Client State
885
+ @utf8_strings = false
886
+ @debug_output_bol = true
887
+ @exception = nil
888
+ @greeting = nil
889
+ @capabilities = nil
890
+
891
+ # Client Protocol Reciever
892
+ @parser = ResponseParser.new
893
+ @responses = Hash.new {|h, k| h[k] = [] }
894
+ @response_handlers = []
895
+ @receiver_thread = nil
896
+ @receiver_thread_exception = nil
897
+ @receiver_thread_terminating = false
898
+
899
+ # Client Protocol Sender (including state for currently running commands)
900
+ @tag_prefix = "RUBY"
901
+ @tagno = 0
902
+ @tagged_responses = {}
903
+ @tagged_response_arrival = new_cond
904
+ @continued_command_tag = nil
905
+ @continuation_request_arrival = new_cond
906
+ @continuation_request_exception = nil
907
+ @idle_done_cond = nil
908
+ @logout_command_tag = nil
909
+
910
+ # Connection
911
+ @tls_verified = false
912
+ @sock = tcp_socket(@host, @port)
913
+ start_tls_session if ssl_ctx
914
+ start_imap_connection
915
+
916
+ # DEPRECATED: to remove in next version
917
+ @client_thread = Thread.current
918
+ end
919
+
920
+ # Returns true after the TLS negotiation has completed and the remote
921
+ # hostname has been verified. Returns false when TLS has been established
922
+ # but peer verification was disabled.
923
+ def tls_verified?; @tls_verified end
924
+
925
+ def client_thread # :nodoc:
926
+ warn "Net::IMAP#client_thread is deprecated and will be removed soon."
927
+ @client_thread
928
+ end
929
+
765
930
  # Disconnects from the server.
766
931
  #
767
- # Related: #logout
932
+ # Related: #logout, #logout!
768
933
  def disconnect
769
934
  return if disconnected?
770
935
  begin
@@ -794,62 +959,123 @@ module Net
794
959
  return @sock.closed?
795
960
  end
796
961
 
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.
962
+ # Returns whether the server supports a given +capability+. When available,
963
+ # cached #capabilities are used without sending a new #capability command to
964
+ # the server.
800
965
  #
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.
966
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
967
+ # behaviour according to the server's advertised #capabilities.</em>
804
968
  #
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>
969
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
970
+ #
971
+ # Related: #auth_capable?, #capabilities, #capability, #enable
972
+ def capable?(capability) capabilities.include? capability.to_s.upcase end
973
+ alias capability? capable?
974
+
975
+ # Returns the server capabilities. When available, cached capabilities are
976
+ # used without sending a new #capability command to the server.
977
+ #
978
+ # To ensure a case-insensitive comparison, #capable? can be used instead.
979
+ #
980
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
981
+ # behaviour according to the server's advertised #capabilities.</em>
982
+ #
983
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
984
+ #
985
+ # Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable
986
+ def capabilities
987
+ @capabilities || capability
988
+ end
989
+
990
+ # Returns the #authenticate mechanisms that the server claims to support.
991
+ # These are derived from the #capabilities with an <tt>AUTH=</tt> prefix.
992
+ #
993
+ # This may be different when the connection is cleartext or using TLS. Most
994
+ # servers will drop all <tt>AUTH=</tt> mechanisms from #capabilities after
995
+ # the connection has authenticated.
811
996
  #
812
- # Capability requirements—other than +IMAP4rev1+—are listed in the
813
- # documentation for each command method.
997
+ # imap = Net::IMAP.new(hostname, ssl: false)
998
+ # imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"]
999
+ # imap.auth_mechanisms # => []
814
1000
  #
815
- # ===== Basic IMAP4rev1 capabilities
1001
+ # imap.starttls
1002
+ # imap.capabilities # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2",
1003
+ # # "AUTH=OAUTHBEARER"]
1004
+ # imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"]
816
1005
  #
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.
1006
+ # imap.authenticate("XOAUTH2", username, oauth2_access_token)
1007
+ # imap.auth_mechanisms # => []
822
1008
  #
823
- # ===== Using IMAP4rev1 extensions
1009
+ # Related: #authenticate, #auth_capable?, #capabilities
1010
+ def auth_mechanisms
1011
+ capabilities
1012
+ .grep(/\AAUTH=/i)
1013
+ .map { _1.delete_prefix("AUTH=") }
1014
+ end
1015
+
1016
+ # Returns whether the server supports a given SASL +mechanism+ for use with
1017
+ # the #authenticate command. The +mechanism+ is supported when
1018
+ # #capabilities includes <tt>"AUTH=#{mechanism.to_s.upcase}"</tt>. When
1019
+ # available, cached capabilities are used without sending a new #capability
1020
+ # command to the server.
824
1021
  #
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.
1022
+ # imap.capable? "AUTH=PLAIN" # => true
1023
+ # imap.auth_capable? "PLAIN" # => true
1024
+ # imap.auth_capable? "blurdybloop" # => false
830
1025
  #
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.
1026
+ # Related: #authenticate, #auth_mechanisms, #capable?, #capabilities
1027
+ def auth_capable?(mechanism)
1028
+ capable? "AUTH=#{mechanism}"
1029
+ end
1030
+
1031
+ # Returns whether capabilities have been cached. When true, #capable? and
1032
+ # #capabilities don't require sending a #capability command to the server.
836
1033
  #
837
- # ===== Caching +CAPABILITY+ responses
1034
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
838
1035
  #
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.
1036
+ # Related: #capable?, #capability, #clear_cached_capabilities
1037
+ def capabilities_cached?
1038
+ !!@capabilities
1039
+ end
1040
+
1041
+ # Clears capabilities that have been remembered by the Net::IMAP client.
1042
+ # This forces a #capability command to be sent the next time a #capabilities
1043
+ # query method is called.
843
1044
  #
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.
1045
+ # Net::IMAP automatically discards its cached capabilities when they can
1046
+ # change. Explicitly calling this _should_ be unnecessary for well-behaved
1047
+ # servers.
848
1048
  #
1049
+ # Related: #capable?, #capability, #capabilities_cached?
1050
+ def clear_cached_capabilities
1051
+ synchronize do
1052
+ clear_responses("CAPABILITY")
1053
+ @capabilities = nil
1054
+ end
1055
+ end
1056
+
1057
+ # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
1058
+ # and returns an array of capabilities that are supported by the server.
1059
+ # The result is stored for use by #capable? and #capabilities.
1060
+ #
1061
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
1062
+ # behaviour according to the server's advertised #capabilities.</em>
1063
+ #
1064
+ # Net::IMAP automatically stores and discards capability data according to
1065
+ # the requirements and recommendations in
1066
+ # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
1067
+ # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
1068
+ # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
1069
+ # Use #capable?, #auth_capable?, or #capabilities to this cache and avoid
1070
+ # sending the #capability command unnecessarily.
1071
+ #
1072
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
1073
+ #
1074
+ # Related: #capable?, #auth_capable?, #capability, #enable
849
1075
  def capability
850
1076
  synchronize do
851
1077
  send_command("CAPABILITY")
852
- return @responses.delete("CAPABILITY")[-1]
1078
+ @capabilities = clear_responses("CAPABILITY").last.freeze
853
1079
  end
854
1080
  end
855
1081
 
@@ -860,8 +1086,7 @@ module Net
860
1086
  # Note that the user should first check if the server supports the ID
861
1087
  # capability. For example:
862
1088
  #
863
- # capabilities = imap.capability
864
- # if capabilities.include?("ID")
1089
+ # if capable?(:ID)
865
1090
  # id = imap.id(
866
1091
  # name: "my IMAP client (ruby)",
867
1092
  # version: MyIMAP::VERSION,
@@ -875,11 +1100,11 @@ module Net
875
1100
  # ===== Capabilities
876
1101
  #
877
1102
  # The server's capabilities must include +ID+
878
- # [RFC2971[https://tools.ietf.org/html/rfc2971]]
1103
+ # [RFC2971[https://tools.ietf.org/html/rfc2971]].
879
1104
  def id(client_id=nil)
880
1105
  synchronize do
881
1106
  send_command("ID", ClientID.new(client_id))
882
- @responses.delete("ID")&.last
1107
+ clear_responses("ID").last
883
1108
  end
884
1109
  end
885
1110
 
@@ -888,7 +1113,7 @@ module Net
888
1113
  #
889
1114
  # This allows the server to send unsolicited untagged EXPUNGE #responses,
890
1115
  # but does not execute any client request. \IMAP servers are permitted to
891
- # send unsolicited untagged responses at any time, except for `EXPUNGE`.
1116
+ # send unsolicited untagged responses at any time, except for +EXPUNGE+:
892
1117
  #
893
1118
  # * +EXPUNGE+ can only be sent while a command is in progress.
894
1119
  # * +EXPUNGE+ must _not_ be sent during #fetch, #store, or #search.
@@ -903,15 +1128,43 @@ module Net
903
1128
  # to inform the command to inform the server that the client is done with
904
1129
  # the connection.
905
1130
  #
906
- # Related: #disconnect
1131
+ # Related: #disconnect, #logout!
907
1132
  def logout
908
1133
  send_command("LOGOUT")
909
1134
  end
910
1135
 
1136
+ # Calls #logout then, after receiving the TaggedResponse for the +LOGOUT+,
1137
+ # calls #disconnect. Returns the TaggedResponse from +LOGOUT+. Returns
1138
+ # +nil+ when the client is already disconnected, in contrast to #logout
1139
+ # which raises an exception.
1140
+ #
1141
+ # If #logout raises a StandardError, a warning will be printed but the
1142
+ # exception will not be re-raised.
1143
+ #
1144
+ # This is useful in situations where the connection must be dropped, for
1145
+ # example for security or after tests. If logout errors need to be handled,
1146
+ # use #logout and #disconnect instead.
1147
+ #
1148
+ # Related: #logout, #disconnect
1149
+ def logout!
1150
+ logout unless disconnected?
1151
+ rescue => ex
1152
+ warn "%s during <Net::IMAP %s:%s> logout!: %s" % [
1153
+ ex.class, host, port, ex
1154
+ ]
1155
+ ensure
1156
+ disconnect
1157
+ end
1158
+
911
1159
  # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1]
912
1160
  # to start a TLS session.
913
1161
  #
914
- # Any +options+ are forwarded to OpenSSL::SSL::SSLContext#set_params.
1162
+ # Any +options+ are forwarded directly to
1163
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
1164
+ # the keys are names of attribute assignment methods on
1165
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
1166
+ #
1167
+ # See DeprecatedClientOptions#starttls for deprecated arguments.
915
1168
  #
916
1169
  # This method returns after TLS negotiation and hostname verification are
917
1170
  # both successful. Any error indicates that the connection has not been
@@ -921,132 +1174,156 @@ module Net
921
1174
  # >>>
922
1175
  # Any #response_handlers added before STARTTLS should be aware that the
923
1176
  # TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation.
924
- # TLS negotiation starts immediately after that response.
1177
+ # TLS starts immediately _after_ that response. Any response code sent
1178
+ # with the response (e.g. CAPABILITY) is insecure and cannot be trusted.
925
1179
  #
926
1180
  # Related: Net::IMAP.new, #login, #authenticate
927
1181
  #
928
1182
  # ===== Capability
929
- #
930
- # The server's capabilities must include +STARTTLS+.
1183
+ # Clients should not call #starttls unless the server advertises the
1184
+ # +STARTTLS+ capability.
931
1185
  #
932
1186
  # 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.
1187
+ # Cached #capabilities will be cleared when this method completes.
939
1188
  #
940
- def starttls(options = {}, verify = true)
1189
+ def starttls(**options)
1190
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
941
1191
  send_command("STARTTLS") do |resp|
942
1192
  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)
1193
+ clear_cached_capabilities
1194
+ clear_responses
1195
+ start_tls_session
950
1196
  end
951
1197
  end
952
1198
  end
953
1199
 
954
1200
  # :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
1201
+ # authenticate(mechanism, *, sasl_ir: true, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp
960
1202
  #
961
1203
  # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
962
1204
  # to authenticate the client. If successful, the connection enters the
963
1205
  # "_authenticated_" state.
964
1206
  #
965
1207
  # +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
1208
  #
971
- # An exception Net::IMAP::NoResponseError is raised if authentication fails.
1209
+ # +sasl_ir+ allows or disallows sending an "initial response" (see the
1210
+ # +SASL-IR+ capability, below).
972
1211
  #
973
- # Related: #login, #starttls
1212
+ # All other arguments are forwarded to the registered SASL authenticator for
1213
+ # the requested mechanism. <em>The documentation for each individual
1214
+ # mechanism must be consulted for its specific parameters.</em>
974
1215
  #
975
- # ==== Supported SASL Mechanisms
1216
+ # Related: #login, #starttls, #auth_capable?, #auth_mechanisms
976
1217
  #
977
- # +PLAIN+:: See PlainAuthenticator.
978
- # Login using clear-text username and password.
1218
+ # ==== Mechanisms
979
1219
  #
980
- # +XOAUTH2+:: See XOauth2Authenticator.
981
- # Login using a username and OAuth2 access token.
982
- # Non-standard and obsoleted by +OAUTHBEARER+, but widely
983
- # supported.
1220
+ # Each mechanism has different properties and requirements. Please consult
1221
+ # the documentation for the specific mechanisms you are using:
984
1222
  #
985
- # >>>
986
- # *Deprecated:* <em>Obsolete mechanisms are available for backwards
987
- # compatibility.</em>
1223
+ # +ANONYMOUS+::
1224
+ # See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
1225
+ #
1226
+ # Allows the user to gain access to public services or resources without
1227
+ # authenticating or disclosing an identity.
1228
+ #
1229
+ # +EXTERNAL+::
1230
+ # See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
1231
+ #
1232
+ # Authenticates using already established credentials, such as a TLS
1233
+ # certificate or IPsec.
1234
+ #
1235
+ # +OAUTHBEARER+::
1236
+ # See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator].
988
1237
  #
989
- # For +DIGEST-MD5+ see DigestMD5Authenticator.
1238
+ # Login using an OAuth2 Bearer token. This is the standard mechanism
1239
+ # for using OAuth2 with \SASL, but it is not yet deployed as widely as
1240
+ # +XOAUTH2+.
990
1241
  #
991
- # For +LOGIN+, see LoginAuthenticator.
1242
+ # +PLAIN+::
1243
+ # See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator].
992
1244
  #
993
- # For +CRAM-MD5+, see CramMD5Authenticator.
1245
+ # Login using clear-text username and password.
994
1246
  #
995
- # <em>Using a deprecated mechanism will print a warning.</em>
1247
+ # +SCRAM-SHA-1+::
1248
+ # +SCRAM-SHA-256+::
1249
+ # See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator].
996
1250
  #
997
- # See Net::IMAP::Authenticators for information on plugging in
998
- # authenticators for other mechanisms. See the {SASL mechanism
1251
+ # Login by username and password. The password is not sent to the
1252
+ # server but is used in a salted challenge/response exchange.
1253
+ # +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
1254
+ # Net::IMAP::SASL. New authenticators can easily be added for any other
1255
+ # <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
1256
+ # OpenSSL::Digest.
1257
+ #
1258
+ # +XOAUTH2+::
1259
+ # See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator].
1260
+ #
1261
+ # Login using a username and an OAuth2 access token. Non-standard and
1262
+ # obsoleted by +OAUTHBEARER+, but widely supported.
1263
+ #
1264
+ # See the {SASL mechanism
999
1265
  # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
1000
- # for information on these and other SASL mechanisms.
1266
+ # for a list of all SASL mechanisms and their specifications. To register
1267
+ # new authenticators, see Authenticators.
1001
1268
  #
1002
- # ===== Capabilities
1269
+ # ===== Deprecated mechanisms
1003
1270
  #
1004
- # Clients MUST NOT attempt to authenticate with a mechanism unless
1005
- # <tt>"AUTH=#{mechanism}"</tt> for that mechanism is a server capability.
1271
+ # <em>Obsolete mechanisms should be avoided, but are still available for
1272
+ # backwards compatibility. See</em> Net::IMAP::SASL@Deprecated+mechanisms.
1273
+ # <em>Using a deprecated mechanism will print a warning.</em>
1006
1274
  #
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
1275
+ # ==== Capabilities
1276
+ #
1277
+ # <tt>"AUTH=#{mechanism}"</tt> capabilities indicate server support for
1278
+ # mechanisms. Use #auth_capable? or #auth_mechanisms to check for support
1279
+ # before using a particular mechanism.
1280
+ #
1281
+ # if imap.auth_capable? "XOAUTH2"
1282
+ # imap.authenticate "XOAUTH2", username, oauth2_access_token
1283
+ # elsif imap.auth_capable? "PLAIN"
1284
+ # imap.authenticate "PLAIN", username, password
1285
+ # elsif !imap.capability? "LOGINDISABLED"
1037
1286
  # imap.login username, password
1287
+ # else
1288
+ # raise "No acceptable authentication mechanism is available"
1038
1289
  # end
1039
1290
  #
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
1291
+ # Although servers should list all supported \SASL mechanisms, they may
1292
+ # allow authentication with an unlisted +mechanism+.
1293
+ #
1294
+ # If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported
1295
+ # and the appropriate <tt>"AUTH=#{mechanism}"</tt> capability is present,
1296
+ # an "initial response" may be sent as an argument to the +AUTHENTICATE+
1297
+ # command, saving a round-trip. The SASL exchange allows for server
1298
+ # challenges and client responses, but many mechanisms expect the client to
1299
+ # "respond" first. The initial response will only be sent for
1300
+ # "client-first" mechanisms.
1301
+ #
1302
+ # Server capabilities may change after #starttls, #login, and #authenticate.
1303
+ # Previously cached #capabilities will be cleared when this method
1304
+ # completes. If the TaggedResponse to #authenticate includes updated
1305
+ # capabilities, they will be cached.
1306
+ def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback)
1307
+ mechanism = mechanism.to_s.tr("_", "-").upcase
1308
+ authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
1309
+ cmdargs = ["AUTHENTICATE", mechanism]
1310
+ if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
1311
+ authenticator.respond_to?(:initial_response?) &&
1312
+ authenticator.initial_response?
1313
+ response = authenticator.process(nil)
1314
+ cmdargs << (response.empty? ? "=" : [response].pack("m0"))
1315
+ end
1316
+ result = send_command_with_continuations(*cmdargs) {|data|
1317
+ challenge = data.unpack1("m")
1318
+ response = authenticator.process challenge
1319
+ [response].pack("m0")
1320
+ }
1321
+ if authenticator.respond_to?(:done?) && !authenticator.done?
1322
+ logout!
1323
+ raise SASL::AuthenticationIncomplete, result
1049
1324
  end
1325
+ @capabilities = capabilities_from_resp_code result
1326
+ result
1050
1327
  end
1051
1328
 
1052
1329
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1054,16 +1331,25 @@ module Net
1054
1331
  # this +user+. If successful, the connection enters the "_authenticated_"
1055
1332
  # state.
1056
1333
  #
1057
- # Using #authenticate is generally preferred over #login. The LOGIN command
1058
- # is not the same as #authenticate with the "LOGIN" +mechanism+.
1334
+ # Using #authenticate {should be
1335
+ # preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command]
1336
+ # over #login. The LOGIN command is not the same as #authenticate with the
1337
+ # "LOGIN" +mechanism+.
1059
1338
  #
1060
1339
  # A Net::IMAP::NoResponseError is raised if authentication fails.
1061
1340
  #
1062
1341
  # Related: #authenticate, #starttls
1063
1342
  #
1064
- # ==== Capabilities
1065
- # Clients MUST NOT call #login if +LOGINDISABLED+ is listed with the
1066
- # capabilities.
1343
+ # ===== Capabilities
1344
+ #
1345
+ # An IMAP client MUST NOT call #login when the server advertises the
1346
+ # +LOGINDISABLED+ capability.
1347
+ #
1348
+ # if imap.capability? "LOGINDISABLED"
1349
+ # raise "Remote server has disabled the login command"
1350
+ # else
1351
+ # imap.login username, password
1352
+ # end
1067
1353
  #
1068
1354
  # Server capabilities may change after #starttls, #login, and #authenticate.
1069
1355
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1072,17 +1358,24 @@ module Net
1072
1358
  #
1073
1359
  def login(user, password)
1074
1360
  send_command("LOGIN", user, password)
1361
+ .tap { @capabilities = capabilities_from_resp_code _1 }
1075
1362
  end
1076
1363
 
1077
1364
  # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
1078
1365
  # to select a +mailbox+ so that messages in the +mailbox+ can be accessed.
1079
1366
  #
1080
1367
  # 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.
1368
+ # that mailbox from <tt>imap.responses("EXISTS", &:last)</tt>, and the
1369
+ # number of recent messages from <tt>imap.responses("RECENT", &:last)</tt>.
1370
+ # Note that these values can change if new messages arrive during a session
1371
+ # or when existing messages are expunged; see #add_response_handler for a
1372
+ # way to detect these events.
1373
+ #
1374
+ # When the +condstore+ keyword argument is true, the server is told to
1375
+ # enable the extension. If +mailbox+ supports persistence of mod-sequences,
1376
+ # the +HIGHESTMODSEQ+ ResponseCode will be sent as an untagged response to
1377
+ # #select and all `FETCH` responses will include FetchData#modseq.
1378
+ # Otherwise, the +NOMODSEQ+ ResponseCode will be sent.
1086
1379
  #
1087
1380
  # A Net::IMAP::NoResponseError is raised if the mailbox does not
1088
1381
  # exist or is for some reason non-selectable.
@@ -1095,11 +1388,18 @@ module Net
1095
1388
  # the server may return an untagged "NO" response with a "UIDNOTSTICKY"
1096
1389
  # response code indicating that the mailstore does not support persistent
1097
1390
  # UIDs:
1098
- # @responses["NO"].last.code.name == "UIDNOTSTICKY"
1099
- def select(mailbox)
1391
+ # imap.responses("NO", &:last)&.code&.name == "UIDNOTSTICKY"
1392
+ #
1393
+ # If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported,
1394
+ # the +condstore+ keyword parameter may be used.
1395
+ # imap.select("mbox", condstore: true)
1396
+ # modseq = imap.responses("HIGHESTMODSEQ", &:last)
1397
+ def select(mailbox, condstore: false)
1398
+ args = ["SELECT", mailbox]
1399
+ args << ["CONDSTORE"] if condstore
1100
1400
  synchronize do
1101
1401
  @responses.clear
1102
- send_command("SELECT", mailbox)
1402
+ send_command(*args)
1103
1403
  end
1104
1404
  end
1105
1405
 
@@ -1112,10 +1412,12 @@ module Net
1112
1412
  # exist or is for some reason non-examinable.
1113
1413
  #
1114
1414
  # Related: #select
1115
- def examine(mailbox)
1415
+ def examine(mailbox, condstore: false)
1416
+ args = ["EXAMINE", mailbox]
1417
+ args << ["CONDSTORE"] if condstore
1116
1418
  synchronize do
1117
1419
  @responses.clear
1118
- send_command("EXAMINE", mailbox)
1420
+ send_command(*args)
1119
1421
  end
1120
1422
  end
1121
1423
 
@@ -1185,10 +1487,10 @@ module Net
1185
1487
  # to the client. +refname+ provides a context (for instance, a base
1186
1488
  # directory in a directory-based mailbox hierarchy). +mailbox+ specifies a
1187
1489
  # 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.
1490
+ # may be used in +mailbox+: <tt>"*"</tt>, which matches all characters
1491
+ # *including* the hierarchy delimiter (for instance, "/" on a UNIX-hosted
1492
+ # directory-based mailbox hierarchy); and <tt>"%"</tt>, which matches all
1493
+ # characters *except* the hierarchy delimiter.
1192
1494
  #
1193
1495
  # If +refname+ is empty, +mailbox+ is used directly to determine
1194
1496
  # which mailboxes to match. If +mailbox+ is empty, the root
@@ -1213,7 +1515,7 @@ module Net
1213
1515
  def list(refname, mailbox)
1214
1516
  synchronize do
1215
1517
  send_command("LIST", refname, mailbox)
1216
- return @responses.delete("LIST")
1518
+ clear_responses("LIST")
1217
1519
  end
1218
1520
  end
1219
1521
 
@@ -1236,23 +1538,22 @@ module Net
1236
1538
  # servers, then folder creation (and listing, moving, etc) can lead to
1237
1539
  # errors.
1238
1540
  #
1239
- # From RFC2342:
1240
- #
1241
- # Although typically a server will support only a single Personal
1541
+ # From RFC2342[https://tools.ietf.org/html/rfc2342]:
1542
+ # >>>
1543
+ # <em>Although typically a server will support only a single Personal
1242
1544
  # Namespace, and a single Other User's Namespace, circumstances exist
1243
1545
  # where there MAY be multiples of these, and a client MUST be prepared
1244
1546
  # for them. If a client is configured such that it is required to create
1245
1547
  # a certain mailbox, there can be circumstances where it is unclear which
1246
1548
  # Personal Namespaces it should create the mailbox in. In these
1247
1549
  # situations a client SHOULD let the user select which namespaces to
1248
- # create the mailbox in.
1550
+ # create the mailbox in.</em>
1249
1551
  #
1250
1552
  # Related: #list, Namespaces, Namespace
1251
1553
  #
1252
1554
  # ===== For example:
1253
1555
  #
1254
- # capabilities = imap.capability
1255
- # if capabilities.include?("NAMESPACE")
1556
+ # if capable?("NAMESPACE")
1256
1557
  # namespaces = imap.namespace
1257
1558
  # if namespace = namespaces.personal.first
1258
1559
  # prefix = namespace.prefix # e.g. "" or "INBOX."
@@ -1271,7 +1572,7 @@ module Net
1271
1572
  def namespace
1272
1573
  synchronize do
1273
1574
  send_command("NAMESPACE")
1274
- return @responses.delete("NAMESPACE")[-1]
1575
+ clear_responses("NAMESPACE").last
1275
1576
  end
1276
1577
  end
1277
1578
 
@@ -1315,7 +1616,7 @@ module Net
1315
1616
  def xlist(refname, mailbox)
1316
1617
  synchronize do
1317
1618
  send_command("XLIST", refname, mailbox)
1318
- return @responses.delete("XLIST")
1619
+ clear_responses("XLIST")
1319
1620
  end
1320
1621
  end
1321
1622
 
@@ -1334,8 +1635,8 @@ module Net
1334
1635
  synchronize do
1335
1636
  send_command("GETQUOTAROOT", mailbox)
1336
1637
  result = []
1337
- result.concat(@responses.delete("QUOTAROOT"))
1338
- result.concat(@responses.delete("QUOTA"))
1638
+ result.concat(clear_responses("QUOTAROOT"))
1639
+ result.concat(clear_responses("QUOTA"))
1339
1640
  return result
1340
1641
  end
1341
1642
  end
@@ -1354,7 +1655,7 @@ module Net
1354
1655
  def getquota(mailbox)
1355
1656
  synchronize do
1356
1657
  send_command("GETQUOTA", mailbox)
1357
- return @responses.delete("QUOTA")
1658
+ clear_responses("QUOTA")
1358
1659
  end
1359
1660
  end
1360
1661
 
@@ -1410,7 +1711,7 @@ module Net
1410
1711
  def getacl(mailbox)
1411
1712
  synchronize do
1412
1713
  send_command("GETACL", mailbox)
1413
- return @responses.delete("ACL")[-1]
1714
+ clear_responses("ACL").last
1414
1715
  end
1415
1716
  end
1416
1717
 
@@ -1425,31 +1726,74 @@ module Net
1425
1726
  def lsub(refname, mailbox)
1426
1727
  synchronize do
1427
1728
  send_command("LSUB", refname, mailbox)
1428
- return @responses.delete("LSUB")
1729
+ clear_responses("LSUB")
1429
1730
  end
1430
1731
  end
1431
1732
 
1432
- # Sends a {STATUS commands [IMAP4rev1 §6.3.10]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.10]
1733
+ # Sends a {STATUS command [IMAP4rev1 §6.3.10]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.10]
1433
1734
  # 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:
1735
+ # or more attributes whose statuses are to be requested.
1736
+ #
1737
+ # The return value is a hash of attributes. Most status attributes return
1738
+ # integer values, but some return other value types (documented below).
1739
+ #
1740
+ # A Net::IMAP::NoResponseError is raised if status values
1741
+ # for +mailbox+ cannot be returned; for instance, because it
1742
+ # does not exist.
1743
+ #
1744
+ # ===== Supported attributes
1745
+ #
1746
+ # +MESSAGES+:: The number of messages in the mailbox.
1747
+ #
1748
+ # +UIDNEXT+:: The next unique identifier value of the mailbox.
1749
+ #
1750
+ # +UIDVALIDITY+:: The unique identifier validity value of the mailbox.
1436
1751
  #
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.
1752
+ # +UNSEEN+:: The number of messages without the <tt>\Seen</tt> flag.
1440
1753
  #
1441
- # The return value is a hash of attributes. For example:
1754
+ # +DELETED+:: The number of messages with the <tt>\Deleted</tt> flag.
1755
+ #
1756
+ # +SIZE+::
1757
+ # The approximate size of the mailbox---must be greater than or equal to
1758
+ # the sum of all messages' +RFC822.SIZE+ fetch item values.
1759
+ #
1760
+ # +HIGHESTMODSEQ+::
1761
+ # The highest mod-sequence value of all messages in the mailbox. See
1762
+ # +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
1763
+ #
1764
+ # +MAILBOXID+::
1765
+ # A server-allocated unique _string_ identifier for the mailbox. See
1766
+ # +OBJECTID+ {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
1767
+ #
1768
+ # +RECENT+::
1769
+ # The number of messages with the <tt>\Recent</tt> flag.
1770
+ # _NOTE:_ +RECENT+ was removed from IMAP4rev2.
1771
+ #
1772
+ # Unsupported attributes may be requested. The attribute value will be
1773
+ # either an Integer or an ExtensionData object.
1774
+ #
1775
+ # ===== For example:
1442
1776
  #
1443
1777
  # p imap.status("inbox", ["MESSAGES", "RECENT"])
1444
1778
  # #=> {"RECENT"=>0, "MESSAGES"=>44}
1445
1779
  #
1446
- # A Net::IMAP::NoResponseError is raised if status values
1447
- # for +mailbox+ cannot be returned; for instance, because it
1448
- # does not exist.
1780
+ # ===== Capabilities
1781
+ #
1782
+ # +SIZE+ requires the server's capabilities to include either +IMAP4rev2+ or
1783
+ # <tt>STATUS=SIZE</tt>
1784
+ # {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html].
1785
+ #
1786
+ # +DELETED+ requires the server's capabilities to include +IMAP4rev2+.
1787
+ #
1788
+ # +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+
1789
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
1790
+ #
1791
+ # +MAILBOXID+ requires the server's capabilities to include +OBJECTID+
1792
+ # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
1449
1793
  def status(mailbox, attr)
1450
1794
  synchronize do
1451
1795
  send_command("STATUS", mailbox, attr)
1452
- return @responses.delete("STATUS")[-1].attr
1796
+ clear_responses("STATUS").last&.attr
1453
1797
  end
1454
1798
  end
1455
1799
 
@@ -1538,7 +1882,7 @@ module Net
1538
1882
  def expunge
1539
1883
  synchronize do
1540
1884
  send_command("EXPUNGE")
1541
- return @responses.delete("EXPUNGE")
1885
+ clear_responses("EXPUNGE")
1542
1886
  end
1543
1887
  end
1544
1888
 
@@ -1570,7 +1914,7 @@ module Net
1570
1914
  def uid_expunge(uid_set)
1571
1915
  synchronize do
1572
1916
  send_command("UID EXPUNGE", MessageSet.new(uid_set))
1573
- return @responses.delete("EXPUNGE")
1917
+ clear_responses("EXPUNGE")
1574
1918
  end
1575
1919
  end
1576
1920
 
@@ -1580,6 +1924,10 @@ module Net
1580
1924
  # string holding the entire search string, or a single-dimension array of
1581
1925
  # search keywords and arguments.
1582
1926
  #
1927
+ # Returns a SearchResult object. SearchResult inherits from Array (for
1928
+ # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
1929
+ # capability has been enabled.
1930
+ #
1583
1931
  # Related: #uid_search
1584
1932
  #
1585
1933
  # ===== Search criteria
@@ -1589,7 +1937,7 @@ module Net
1589
1937
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
1590
1938
  # in addition to documentation for
1591
1939
  # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
1592
- # reported by #capability which may define additional search filters, e.g:
1940
+ # reported by #capabilities which may define additional search filters, e.g:
1593
1941
  # +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
1594
1942
  # +SAVEDATE+. The following are some common search criteria:
1595
1943
  #
@@ -1628,6 +1976,15 @@ module Net
1628
1976
  # p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
1629
1977
  # #=> [1, 6, 7, 8]
1630
1978
  #
1979
+ # ===== Capabilities
1980
+ #
1981
+ # If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported
1982
+ # and enabled for the selected mailbox, a non-empty SearchResult will
1983
+ # include a +MODSEQ+ value.
1984
+ # imap.select("mbox", condstore: true)
1985
+ # result = imap.search(["SUBJECT", "hi there", "not", "new")
1986
+ # #=> Net::IMAP::SearchResult[1, 6, 7, 8, modseq: 5594]
1987
+ # result.modseq # => 5594
1631
1988
  def search(keys, charset = nil)
1632
1989
  return search_internal("SEARCH", keys, charset)
1633
1990
  end
@@ -1636,11 +1993,18 @@ module Net
1636
1993
  # to search the mailbox for messages that match the given searching
1637
1994
  # criteria, and returns unique identifiers (<tt>UID</tt>s).
1638
1995
  #
1996
+ # Returns a SearchResult object. SearchResult inherits from Array (for
1997
+ # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
1998
+ # capability has been enabled.
1999
+ #
1639
2000
  # See #search for documentation of search criteria.
1640
2001
  def uid_search(keys, charset = nil)
1641
2002
  return search_internal("UID SEARCH", keys, charset)
1642
2003
  end
1643
2004
 
2005
+ # :call-seq:
2006
+ # fetch(set, attr, changedsince: nil) -> array of FetchData
2007
+ #
1644
2008
  # Sends a {FETCH command [IMAP4rev1 §6.4.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.5]
1645
2009
  # to retrieve data associated with a message in the mailbox.
1646
2010
  #
@@ -1656,8 +2020,10 @@ module Net
1656
2020
  # +attr+ is a list of attributes to fetch; see the documentation
1657
2021
  # for FetchData for a list of valid attributes.
1658
2022
  #
1659
- # The return value is an array of FetchData or nil
1660
- # (instead of an empty array) if there is no matching message.
2023
+ # +changedsince+ is an optional integer mod-sequence. It limits results to
2024
+ # messages with a mod-sequence greater than +changedsince+.
2025
+ #
2026
+ # The return value is an array of FetchData.
1661
2027
  #
1662
2028
  # Related: #uid_search, FetchData
1663
2029
  #
@@ -1678,10 +2044,23 @@ module Net
1678
2044
  # #=> "12-Oct-2000 22:40:59 +0900"
1679
2045
  # p data.attr["UID"]
1680
2046
  # #=> 98
1681
- def fetch(set, attr, mod = nil)
1682
- return fetch_internal("FETCH", set, attr, mod)
2047
+ #
2048
+ # ===== Capabilities
2049
+ #
2050
+ # Many extensions define new message +attr+ names. See FetchData for a list
2051
+ # of supported extension fields.
2052
+ #
2053
+ # The server's capabilities must include +CONDSTORE+
2054
+ # {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the
2055
+ # +changedsince+ argument. Using +changedsince+ implicitly enables the
2056
+ # +CONDSTORE+ extension.
2057
+ def fetch(set, attr, mod = nil, changedsince: nil)
2058
+ fetch_internal("FETCH", set, attr, mod, changedsince: changedsince)
1683
2059
  end
1684
2060
 
2061
+ # :call-seq:
2062
+ # uid_fetch(set, attr, changedsince: nil) -> array of FetchData
2063
+ #
1685
2064
  # Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
1686
2065
  # to retrieve data associated with a message in the mailbox.
1687
2066
  #
@@ -1694,32 +2073,63 @@ module Net
1694
2073
  # whether a +UID+ was specified as a message data item to the +FETCH+.
1695
2074
  #
1696
2075
  # Related: #fetch, FetchData
1697
- def uid_fetch(set, attr, mod = nil)
1698
- return fetch_internal("UID FETCH", set, attr, mod)
2076
+ #
2077
+ # ===== Capabilities
2078
+ # Same as #fetch.
2079
+ def uid_fetch(set, attr, mod = nil, changedsince: nil)
2080
+ fetch_internal("UID FETCH", set, attr, mod, changedsince: changedsince)
1699
2081
  end
1700
2082
 
2083
+ # :call-seq:
2084
+ # store(set, attr, value, unchangedsince: nil) -> array of FetchData
2085
+ #
1701
2086
  # Sends a {STORE command [IMAP4rev1 §6.4.6]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.6]
1702
2087
  # 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.
2088
+ # flags.
2089
+ #
2090
+ # +set+ is a number, an array of numbers, or a Range object. Each number is
2091
+ # a message sequence number.
2092
+ #
2093
+ # +attr+ is the name of a data item to store. The semantics of +value+
2094
+ # varies based on +attr+:
2095
+ # * When +attr+ is <tt>"FLAGS"</tt>, the flags in +value+ replace the
2096
+ # message's flag list.
2097
+ # * When +attr+ is <tt>"+FLAGS"</tt>, the flags in +value+ are added to
2098
+ # the flags for the message.
2099
+ # * When +attr+ is <tt>"-FLAGS"</tt>, the flags in +value+ are removed
2100
+ # from the message.
1708
2101
  #
1709
- # The return value is an array of FetchData
2102
+ # +unchangedsince+ is an optional integer mod-sequence. It prohibits any
2103
+ # changes to messages with +mod-sequence+ greater than the specified
2104
+ # +unchangedsince+ value. A SequenceSet of any messages that fail this
2105
+ # check will be returned in a +MODIFIED+ ResponseCode.
2106
+ #
2107
+ # The return value is an array of FetchData.
1710
2108
  #
1711
2109
  # Related: #uid_store
1712
2110
  #
1713
2111
  # ===== For example:
1714
2112
  #
1715
2113
  # 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]}>, \\
2114
+ # #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>,
2115
+ # #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>,
1718
2116
  # #<Net::IMAP::FetchData seqno=8, attr={"FLAGS"=>[:Seen, :Deleted]}>]
1719
- def store(set, attr, flags)
1720
- return store_internal("STORE", set, attr, flags)
2117
+ #
2118
+ # ===== Capabilities
2119
+ #
2120
+ # Extensions may define new data items to be used with #store.
2121
+ #
2122
+ # The server's capabilities must include +CONDSTORE+
2123
+ # {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the
2124
+ # +unchangedsince+ argument. Using +unchangedsince+ implicitly enables the
2125
+ # +CONDSTORE+ extension.
2126
+ def store(set, attr, flags, unchangedsince: nil)
2127
+ store_internal("STORE", set, attr, flags, unchangedsince: unchangedsince)
1721
2128
  end
1722
2129
 
2130
+ # :call-seq:
2131
+ # uid_store(set, attr, value, unchangedsince: nil) -> array of FetchData
2132
+ #
1723
2133
  # Sends a {UID STORE command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
1724
2134
  # to alter data associated with messages in the mailbox, in particular their
1725
2135
  # flags.
@@ -1728,8 +2138,11 @@ module Net
1728
2138
  # message sequence numbers.
1729
2139
  #
1730
2140
  # Related: #store
1731
- def uid_store(set, attr, flags)
1732
- return store_internal("UID STORE", set, attr, flags)
2141
+ #
2142
+ # ===== Capabilities
2143
+ # Same as #store.
2144
+ def uid_store(set, attr, flags, unchangedsince: nil)
2145
+ store_internal("UID STORE", set, attr, flags, unchangedsince: unchangedsince)
1733
2146
  end
1734
2147
 
1735
2148
  # Sends a {COPY command [IMAP4rev1 §6.4.7]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.7]
@@ -1884,6 +2297,94 @@ module Net
1884
2297
  return thread_internal("UID THREAD", algorithm, search_keys, charset)
1885
2298
  end
1886
2299
 
2300
+ # Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1]
2301
+ # {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1]
2302
+ # to enable the specified server +capabilities+. Each capability may be an
2303
+ # array, string, or symbol. Returns a list of the capabilities that were
2304
+ # enabled.
2305
+ #
2306
+ # The +ENABLE+ command is only valid in the _authenticated_ state, before
2307
+ # any mailbox is selected.
2308
+ #
2309
+ # Related: #capable?, #capabilities, #capability
2310
+ #
2311
+ # ===== Capabilities
2312
+ #
2313
+ # The server's capabilities must include
2314
+ # +ENABLE+ [RFC5161[https://tools.ietf.org/html/rfc5161]]
2315
+ # or +IMAP4REV2+ [RFC9051[https://tools.ietf.org/html/rfc9051]].
2316
+ #
2317
+ # Additionally, the server capabilities must include a capability matching
2318
+ # each enabled extension (usually the same name as the enabled extension).
2319
+ # The following capabilities may be enabled:
2320
+ #
2321
+ # [+CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]]
2322
+ #
2323
+ # Updates various commands to return +CONDSTORE+ extension responses. It
2324
+ # is not necessary to explicitly enable +CONDSTORE+—using any of the
2325
+ # command parameters defined by the extension will implicitly enable it.
2326
+ # See {[RFC7162 §3.1]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1].
2327
+ #
2328
+ # [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
2329
+ #
2330
+ # In a future release, <tt>enable(:utf8)</tt> will enable either
2331
+ # <tt>"UTF8=ACCEPT"</tt> or <tt>"IMAP4rev2"</tt>, depending on server
2332
+ # capabilities.
2333
+ #
2334
+ # [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2335
+ #
2336
+ # The server's capabilities must include <tt>UTF8=ACCEPT</tt> _or_
2337
+ # <tt>UTF8=ONLY</tt>.
2338
+ #
2339
+ # This allows the server to send strings encoded as UTF-8 which might
2340
+ # otherwise need to use a 7-bit encoding, such as {modified
2341
+ # UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for
2342
+ # message headers.
2343
+ #
2344
+ # *Note:* <em>A future update may set string encodings slightly
2345
+ # differently</em>, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8"
2346
+ # when it is. Currently, the encoding of strings sent as "quoted" or
2347
+ # "text" will _always_ be "UTF-8", even when only ASCII characters are
2348
+ # used (e.g. "Subject: Agenda") And currently, string "literals" sent
2349
+ # by the server will always have an "ASCII-8BIT" (binary)
2350
+ # encoding, even if they generally contain UTF-8 data, if they are
2351
+ # text at all.
2352
+ #
2353
+ # [<tt>"UTF8=ONLY"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2354
+ #
2355
+ # A server that reports the <tt>UTF8=ONLY</tt> capability _requires_ that
2356
+ # the client <tt>enable("UTF8=ACCEPT")</tt> before any mailboxes may be
2357
+ # selected. For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
2358
+ # <tt>enable("UTF8=ACCEPT")</tt>.
2359
+ #
2360
+ # ===== Unsupported capabilities
2361
+ #
2362
+ # *Note:* Some extensions that use ENABLE permit the server to send syntax
2363
+ # that Net::IMAP cannot parse, which may raise an exception and disconnect.
2364
+ # Some extensions may work, but the support may be incomplete, untested, or
2365
+ # experimental.
2366
+ #
2367
+ # Until a capability is documented here as supported, enabling it may result
2368
+ # in undocumented behavior and a future release may update with incompatible
2369
+ # behavior <em>without warning or deprecation</em>.
2370
+ #
2371
+ # <em>Caution is advised.</em>
2372
+ #
2373
+ def enable(*capabilities)
2374
+ capabilities = capabilities
2375
+ .flatten
2376
+ .map {|e| ENABLE_ALIASES[e] || e }
2377
+ .uniq
2378
+ .join(' ')
2379
+ synchronize do
2380
+ send_command("ENABLE #{capabilities}")
2381
+ result = clear_responses("ENABLED").last || []
2382
+ @utf8_strings ||= result.include? "UTF8=ACCEPT"
2383
+ @utf8_strings ||= result.include? "IMAP4REV2"
2384
+ result
2385
+ end
2386
+ end
2387
+
1887
2388
  # Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3]
1888
2389
  # {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13]
1889
2390
  # that waits for notifications of new or expunged messages. Yields
@@ -1948,6 +2449,104 @@ module Net
1948
2449
  end
1949
2450
  end
1950
2451
 
2452
+ # :call-seq:
2453
+ # responses {|hash| ...} -> block result
2454
+ # responses(type) {|array| ...} -> block result
2455
+ #
2456
+ # Yields unhandled responses and returns the result of the block.
2457
+ #
2458
+ # Unhandled responses are stored in a hash, with arrays of
2459
+ # <em>non-+nil+</em> UntaggedResponse#data keyed by UntaggedResponse#name
2460
+ # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
2461
+ # yield the entire responses hash. Call with +type+ to yield only the array
2462
+ # of responses for that type.
2463
+ #
2464
+ # For example:
2465
+ #
2466
+ # imap.select("inbox")
2467
+ # p imap.responses("EXISTS", &:last)
2468
+ # #=> 2
2469
+ # p imap.responses("UIDVALIDITY", &:last)
2470
+ # #=> 968263756
2471
+ #
2472
+ # >>>
2473
+ # *Note:* Access to the responses hash is synchronized for thread-safety.
2474
+ # The receiver thread and response_handlers cannot process new responses
2475
+ # until the block completes. Accessing either the response hash or its
2476
+ # response type arrays outside of the block is unsafe.
2477
+ #
2478
+ # Calling without a block is unsafe and deprecated. Future releases will
2479
+ # raise ArgumentError unless a block is given.
2480
+ #
2481
+ # Previously unhandled responses are automatically cleared before entering a
2482
+ # mailbox with #select or #examine. Long-lived connections can receive many
2483
+ # unhandled server responses, which must be pruned or they will continually
2484
+ # consume more memory. Update or clear the responses hash or arrays inside
2485
+ # the block, or use #clear_responses.
2486
+ #
2487
+ # Only non-+nil+ data is stored. Many important response codes have no data
2488
+ # of their own, but are used as "tags" on the ResponseText object they are
2489
+ # attached to. ResponseText will be accessible by its response types:
2490
+ # "+OK+", "+NO+", "+BAD+", "+BYE+", or "+PREAUTH+".
2491
+ #
2492
+ # TaggedResponse#data is not saved to #responses, nor is any
2493
+ # ResponseCode#data on tagged responses. Although some command methods do
2494
+ # return the TaggedResponse directly, #add_response_handler must be used to
2495
+ # handle all response codes.
2496
+ #
2497
+ # Related: #clear_responses, #response_handlers, #greeting
2498
+ def responses(type = nil)
2499
+ if block_given?
2500
+ synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
2501
+ elsif type
2502
+ raise ArgumentError, "Pass a block or use #clear_responses"
2503
+ else
2504
+ # warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1)
2505
+ @responses
2506
+ end
2507
+ end
2508
+
2509
+ # :call-seq:
2510
+ # clear_responses -> hash
2511
+ # clear_responses(type) -> array
2512
+ #
2513
+ # Clears and returns the unhandled #responses hash or the unhandled
2514
+ # responses array for a single response +type+.
2515
+ #
2516
+ # Clearing responses is synchronized with other threads. The lock is
2517
+ # released before returning.
2518
+ #
2519
+ # Related: #responses, #response_handlers
2520
+ def clear_responses(type = nil)
2521
+ synchronize {
2522
+ if type
2523
+ @responses.delete(type) || []
2524
+ else
2525
+ @responses.dup.transform_values(&:freeze)
2526
+ .tap { _1.default = [].freeze }
2527
+ .tap { @responses.clear }
2528
+ end
2529
+ }
2530
+ .freeze
2531
+ end
2532
+
2533
+ # Returns all response handlers, including those that are added internally
2534
+ # by commands. Each response handler will be called with every new
2535
+ # UntaggedResponse, TaggedResponse, and ContinuationRequest.
2536
+ #
2537
+ # Response handlers are called with a mutex inside the receiver thread. New
2538
+ # responses cannot be processed and commands from other threads must wait
2539
+ # until all response_handlers return. An exception will shut-down the
2540
+ # receiver thread and close the connection.
2541
+ #
2542
+ # For thread-safety, the returned array is a frozen copy of the internal
2543
+ # array.
2544
+ #
2545
+ # Related: #add_response_handler, #remove_response_handler
2546
+ def response_handlers
2547
+ synchronize { @response_handlers.clone.freeze }
2548
+ end
2549
+
1951
2550
  # Adds a response handler. For example, to detect when
1952
2551
  # the server sends a new EXISTS response (which normally
1953
2552
  # indicates new messages being added to the mailbox),
@@ -1960,14 +2559,21 @@ module Net
1960
2559
  # end
1961
2560
  # }
1962
2561
  #
2562
+ # Related: #remove_response_handler, #response_handlers
1963
2563
  def add_response_handler(handler = nil, &block)
1964
2564
  raise ArgumentError, "two Procs are passed" if handler && block
1965
- @response_handlers.push(block || handler)
2565
+ synchronize do
2566
+ @response_handlers.push(block || handler)
2567
+ end
1966
2568
  end
1967
2569
 
1968
2570
  # Removes the response handler.
2571
+ #
2572
+ # Related: #add_response_handler, #response_handlers
1969
2573
  def remove_response_handler(handler)
1970
- @response_handlers.delete(handler)
2574
+ synchronize do
2575
+ @response_handlers.delete(handler)
2576
+ end
1971
2577
  end
1972
2578
 
1973
2579
  private
@@ -1978,93 +2584,29 @@ module Net
1978
2584
 
1979
2585
  @@debug = false
1980
2586
 
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
2587
+ def start_imap_connection
2588
+ @greeting = get_server_greeting
2589
+ @capabilities = capabilities_from_resp_code @greeting
2590
+ @receiver_thread = start_receiver_thread
2591
+ rescue Exception
2592
+ @sock.close
2593
+ raise
2594
+ end
2595
+
2596
+ def get_server_greeting
2597
+ greeting = get_response
2598
+ raise Error, "No server greeting - connection closed" unless greeting
2599
+ record_untagged_response_code greeting
2600
+ raise ByeResponseError, greeting if greeting.name == "BYE"
2601
+ greeting
2602
+ end
2603
+
2604
+ def start_receiver_thread
2605
+ Thread.start do
2606
+ receive_responses
2607
+ rescue Exception => ex
2608
+ @receiver_thread_exception = ex
2609
+ # don't exit the thread with an exception
2068
2610
  end
2069
2611
  end
2070
2612
 
@@ -2113,11 +2655,7 @@ module Net
2113
2655
  @continuation_request_arrival.signal
2114
2656
  end
2115
2657
  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
2658
+ record_untagged_response(resp)
2121
2659
  if resp.name == "BYE" && @logout_command_tag.nil?
2122
2660
  @sock.close
2123
2661
  @exception = ByeResponseError.new(resp)
@@ -2171,7 +2709,8 @@ module Net
2171
2709
  when /\A(?:BAD)\z/ni
2172
2710
  raise BadResponseError, resp
2173
2711
  else
2174
- raise UnknownResponseError, resp
2712
+ disconnect
2713
+ raise InvalidResponseError, "invalid tagged resp: %p" % [resp.raw.chomp]
2175
2714
  end
2176
2715
  end
2177
2716
 
@@ -2195,11 +2734,42 @@ module Net
2195
2734
  return @parser.parse(buff)
2196
2735
  end
2197
2736
 
2198
- def record_response(name, data)
2199
- unless @responses.has_key?(name)
2200
- @responses[name] = []
2737
+ #############################
2738
+ # built-in response handlers
2739
+
2740
+ # store name => [..., data]
2741
+ def record_untagged_response(resp)
2742
+ @responses[resp.name] << resp.data
2743
+ record_untagged_response_code resp
2744
+ end
2745
+
2746
+ # store code.name => [..., code.data]
2747
+ def record_untagged_response_code(resp)
2748
+ return unless resp.data.is_a?(ResponseText)
2749
+ return unless (code = resp.data.code)
2750
+ @responses[code.name] << code.data
2751
+ end
2752
+
2753
+ # NOTE: only call this for greeting, login, and authenticate
2754
+ def capabilities_from_resp_code(resp)
2755
+ return unless %w[PREAUTH OK].any? { _1.casecmp? resp.name }
2756
+ return unless (code = resp.data.code)
2757
+ return unless code.name.casecmp?("CAPABILITY")
2758
+ code.data.freeze
2759
+ end
2760
+
2761
+ #############################
2762
+
2763
+ # Calls send_command, yielding the text of each ContinuationRequest and
2764
+ # responding with each block result. Returns TaggedResponse. Raises
2765
+ # NoResponseError or BadResponseError.
2766
+ def send_command_with_continuations(cmd, *args)
2767
+ send_command(cmd, *args) do |server_response|
2768
+ if server_response.instance_of?(ContinuationRequest)
2769
+ client_response = yield server_response.data.text
2770
+ put_string(client_response + CRLF)
2771
+ end
2201
2772
  end
2202
- @responses[name].push(data)
2203
2773
  end
2204
2774
 
2205
2775
  def send_command(cmd, *args, &block)
@@ -2241,8 +2811,8 @@ module Net
2241
2811
  if @debug_output_bol
2242
2812
  $stderr.print("C: ")
2243
2813
  end
2244
- $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
2245
- if /\r\n\z/n.match(str)
2814
+ $stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " })
2815
+ if /\n\z/n.match(str)
2246
2816
  @debug_output_bol = true
2247
2817
  else
2248
2818
  @debug_output_bol = false
@@ -2262,11 +2832,15 @@ module Net
2262
2832
  else
2263
2833
  send_command(cmd, *keys)
2264
2834
  end
2265
- return @responses.delete("SEARCH")[-1]
2835
+ clear_responses("SEARCH").last || []
2266
2836
  end
2267
2837
  end
2268
2838
 
2269
- def fetch_internal(cmd, set, attr, mod = nil)
2839
+ def fetch_internal(cmd, set, attr, mod = nil, changedsince: nil)
2840
+ if changedsince
2841
+ mod ||= []
2842
+ mod << "CHANGEDSINCE" << Integer(changedsince)
2843
+ end
2270
2844
  case attr
2271
2845
  when String then
2272
2846
  attr = RawData.new(attr)
@@ -2277,24 +2851,25 @@ module Net
2277
2851
  end
2278
2852
 
2279
2853
  synchronize do
2280
- @responses.delete("FETCH")
2854
+ clear_responses("FETCH")
2281
2855
  if mod
2282
2856
  send_command(cmd, MessageSet.new(set), attr, mod)
2283
2857
  else
2284
2858
  send_command(cmd, MessageSet.new(set), attr)
2285
2859
  end
2286
- return @responses.delete("FETCH")
2860
+ clear_responses("FETCH")
2287
2861
  end
2288
2862
  end
2289
2863
 
2290
- def store_internal(cmd, set, attr, flags)
2291
- if attr.instance_of?(String)
2292
- attr = RawData.new(attr)
2293
- end
2864
+ def store_internal(cmd, set, attr, flags, unchangedsince: nil)
2865
+ attr = RawData.new(attr) if attr.instance_of?(String)
2866
+ args = [MessageSet.new(set)]
2867
+ args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
2868
+ args << attr << flags
2294
2869
  synchronize do
2295
- @responses.delete("FETCH")
2296
- send_command(cmd, MessageSet.new(set), attr, flags)
2297
- return @responses.delete("FETCH")
2870
+ clear_responses("FETCH")
2871
+ send_command(cmd, *args)
2872
+ clear_responses("FETCH")
2298
2873
  end
2299
2874
  end
2300
2875
 
@@ -2308,10 +2883,9 @@ module Net
2308
2883
  else
2309
2884
  normalize_searching_criteria(search_keys)
2310
2885
  end
2311
- normalize_searching_criteria(search_keys)
2312
2886
  synchronize do
2313
2887
  send_command(cmd, sort_keys, charset, *search_keys)
2314
- return @responses.delete("SORT")[-1]
2888
+ clear_responses("SORT").last || []
2315
2889
  end
2316
2890
  end
2317
2891
 
@@ -2321,9 +2895,10 @@ module Net
2321
2895
  else
2322
2896
  normalize_searching_criteria(search_keys)
2323
2897
  end
2324
- normalize_searching_criteria(search_keys)
2325
- send_command(cmd, algorithm, charset, *search_keys)
2326
- return @responses.delete("THREAD")[-1]
2898
+ synchronize do
2899
+ send_command(cmd, algorithm, charset, *search_keys)
2900
+ clear_responses("THREAD").last || []
2901
+ end
2327
2902
  end
2328
2903
 
2329
2904
  def normalize_searching_criteria(keys)
@@ -2337,49 +2912,49 @@ module Net
2337
2912
  end
2338
2913
  end
2339
2914
 
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
2915
+ def build_ssl_ctx(ssl)
2916
+ if ssl
2917
+ params = (Hash.try_convert(ssl) || {}).freeze
2918
+ context = SSLContext.new
2919
+ context.set_params(params)
2920
+ if defined?(VerifyCallbackProc)
2921
+ context.verify_callback = VerifyCallbackProc
2347
2922
  end
2348
- end
2349
- if verify
2350
- params[:verify_mode] = VERIFY_PEER
2923
+ context.freeze
2924
+ [params, context]
2351
2925
  else
2352
- params[:verify_mode] = VERIFY_NONE
2926
+ false
2353
2927
  end
2354
- return params
2355
2928
  end
2356
2929
 
2357
- def start_tls_session(params = {})
2358
- unless defined?(OpenSSL::SSL)
2359
- raise "SSL extension not installed"
2360
- end
2361
- if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
2362
- raise RuntimeError, "already using SSL"
2363
- end
2364
- begin
2365
- params = params.to_hash
2366
- rescue NoMethodError
2367
- params = {}
2368
- end
2369
- context = SSLContext.new
2370
- context.set_params(params)
2371
- if defined?(VerifyCallbackProc)
2372
- context.verify_callback = VerifyCallbackProc
2373
- end
2374
- @sock = SSLSocket.new(@sock, context)
2930
+ def start_tls_session
2931
+ raise "SSL extension not installed" unless defined?(OpenSSL::SSL)
2932
+ raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
2933
+ raise "cannot start TLS without SSLContext" unless ssl_ctx
2934
+ @sock = SSLSocket.new(@sock, ssl_ctx)
2375
2935
  @sock.sync_close = true
2376
2936
  @sock.hostname = @host if @sock.respond_to? :hostname=
2377
2937
  ssl_socket_connect(@sock, @open_timeout)
2378
- if context.verify_mode != VERIFY_NONE
2938
+ if ssl_ctx.verify_mode != VERIFY_NONE
2379
2939
  @sock.post_connection_check(@host)
2940
+ @tls_verified = true
2380
2941
  end
2381
2942
  end
2382
2943
 
2944
+ def sasl_adapter
2945
+ SASLAdapter.new(self, &method(:send_command_with_continuations))
2946
+ end
2947
+
2948
+ #--
2949
+ # We could get the saslprep method by extending the SASLprep module
2950
+ # directly. It's done indirectly, so SASLprep can be lazily autoloaded,
2951
+ # because most users won't need it.
2952
+ #++
2953
+ # Delegates to Net::IMAP::StringPrep::SASLprep#saslprep.
2954
+ def self.saslprep(string, **opts)
2955
+ Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
2956
+ end
2957
+
2383
2958
  end
2384
2959
  end
2385
2960
 
@@ -2390,4 +2965,6 @@ require_relative "imap/flags"
2390
2965
  require_relative "imap/response_data"
2391
2966
  require_relative "imap/response_parser"
2392
2967
  require_relative "imap/authenticators"
2393
- require_relative "imap/sasl"
2968
+
2969
+ require_relative "imap/deprecated_client_options"
2970
+ Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions