net-imap 0.3.9 → 0.5.6

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

Potentially problematic release.


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

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