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