net-imap 0.3.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of net-imap might be problematic. Click here for more details.

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