net-imap 0.3.9 → 0.4.0

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pages.yml +46 -0
  3. data/.github/workflows/test.yml +5 -12
  4. data/Gemfile +1 -0
  5. data/README.md +15 -4
  6. data/Rakefile +0 -7
  7. data/benchmarks/generate_parser_benchmarks +52 -0
  8. data/benchmarks/parser.yml +578 -0
  9. data/benchmarks/stringprep.yml +1 -1
  10. data/lib/net/imap/authenticators.rb +26 -57
  11. data/lib/net/imap/command_data.rb +13 -6
  12. data/lib/net/imap/deprecated_client_options.rb +139 -0
  13. data/lib/net/imap/errors.rb +0 -34
  14. data/lib/net/imap/response_data.rb +46 -41
  15. data/lib/net/imap/response_parser/parser_utils.rb +230 -0
  16. data/lib/net/imap/response_parser.rb +667 -649
  17. data/lib/net/imap/sasl/anonymous_authenticator.rb +68 -0
  18. data/lib/net/imap/sasl/authenticators.rb +112 -0
  19. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +15 -9
  20. data/lib/net/imap/{authenticators/digest_md5.rb → sasl/digest_md5_authenticator.rb} +74 -21
  21. data/lib/net/imap/sasl/external_authenticator.rb +62 -0
  22. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  23. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +19 -14
  24. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +164 -0
  25. data/lib/net/imap/sasl/plain_authenticator.rb +93 -0
  26. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  27. data/lib/net/imap/sasl/scram_authenticator.rb +278 -0
  28. data/lib/net/imap/sasl/stringprep.rb +6 -66
  29. data/lib/net/imap/sasl/xoauth2_authenticator.rb +88 -0
  30. data/lib/net/imap/sasl.rb +139 -44
  31. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  32. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  33. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  34. data/lib/net/imap/stringprep/tables.rb +146 -0
  35. data/lib/net/imap/stringprep/trace.rb +85 -0
  36. data/lib/net/imap/stringprep.rb +159 -0
  37. data/lib/net/imap.rb +987 -690
  38. data/net-imap.gemspec +1 -1
  39. data/rakelib/saslprep.rake +4 -4
  40. data/rakelib/string_prep_tables_generator.rb +82 -60
  41. metadata +30 -13
  42. data/lib/net/imap/authenticators/plain.rb +0 -41
  43. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  44. data/lib/net/imap/response_reader.rb +0 -75
  45. data/lib/net/imap/sasl/saslprep.rb +0 -55
  46. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  47. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
data/lib/net/imap.rb CHANGED
@@ -24,11 +24,9 @@ end
24
24
  module Net
25
25
 
26
26
  # Net::IMAP implements Internet Message Access Protocol (\IMAP) client
27
- # functionality. The protocol is described in
28
- # [IMAP4rev1[https://tools.ietf.org/html/rfc3501]].
29
- #--
30
- # TODO: and [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
31
- #++
27
+ # functionality. The protocol is described
28
+ # in {IMAP4rev1 [RFC3501]}[https://tools.ietf.org/html/rfc3501]
29
+ # and {IMAP4rev2 [RFC9051]}[https://tools.ietf.org/html/rfc9051].
32
30
  #
33
31
  # == \IMAP Overview
34
32
  #
@@ -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.
78
+ # === Examples of Usage
94
79
  #
95
- # == Examples of Usage
96
- #
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.auth_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,24 +246,54 @@ 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
+ # - #clear_responses: Deletes unhandled data from #responses and returns it.
292
+ # - #add_response_handler: Add a block to be called inside the receiver thread
293
+ # with every server response.
294
+ # - #response_handlers: Returns the list of response handlers.
295
+ # - #remove_response_handler: Remove a previously added response handler.
296
+ #
235
297
  # === Core \IMAP commands
236
298
  #
237
299
  # The following commands are defined either by
@@ -240,70 +302,50 @@ module Net
240
302
  # [IDLE[https://tools.ietf.org/html/rfc2177]],
241
303
  # [NAMESPACE[https://tools.ietf.org/html/rfc2342]],
242
304
  # [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
- #++
305
+ # [ENABLE[https://tools.ietf.org/html/rfc5161]],
248
306
  # [MOVE[https://tools.ietf.org/html/rfc6851]].
249
307
  # These extensions are widely supported by modern IMAP4rev1 servers and have
250
308
  # 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
309
+ # <em>*NOTE:* Net::IMAP doesn't support IMAP4rev2 yet.</em>
310
+ #
311
+ # ==== Any state
268
312
  #
269
313
  # - #capability: Returns the server's capabilities as an array of strings.
270
314
  #
271
- # <em>Capabilities may change after</em> #starttls, #authenticate, or #login
272
- # <em>and cached capabilities must be reloaded.</em>
315
+ # <em>In general, #capable? should be used rather than explicitly sending a
316
+ # +CAPABILITY+ command to the server.</em>
273
317
  # - #noop: Allows the server to send unsolicited untagged #responses.
274
- # - #logout: Tells the server to end the session. Enters the +logout+ state.
318
+ # - #logout: Tells the server to end the session. Enters the "_logout_" state.
275
319
  #
276
- # ==== \IMAP commands for the "Not Authenticated" state
320
+ # ==== Not Authenticated state
277
321
  #
278
322
  # In addition to the commands for any state, the following commands are valid
279
- # in the +not_authenticated+ state:
323
+ # in the "<em>not authenticated</em>" state:
280
324
  #
281
325
  # - #starttls: Upgrades a clear-text connection to use TLS.
282
326
  #
283
327
  # <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.
328
+ # - #authenticate: Identifies the client to the server using the given
329
+ # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
330
+ # and credentials. Enters the "_authenticated_" state.
287
331
  #
288
- # <em>Requires the <tt>AUTH=#{mechanism}</tt> capability for the chosen
289
- # mechanism.</em>
332
+ # <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
333
+ # supported mechanisms.</em>
290
334
  # - #login: Identifies the client to the server using a plain text password.
291
- # Using #authenticate is preferred. Enters the +authenticated+ state.
335
+ # Using #authenticate is generally preferred. Enters the "_authenticated_"
336
+ # state.
292
337
  #
293
338
  # <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
294
339
  #
295
- # ==== \IMAP commands for the "Authenticated" state
340
+ # ==== Authenticated state
296
341
  #
297
342
  # In addition to the commands for any state, the following commands are valid
298
- # in the +authenticated+ state:
343
+ # in the "_authenticated_" state:
299
344
  #
300
- #--
301
- # - #enable: <em>Not implemented by Net::IMAP, yet.</em>
302
- #
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.
345
+ # - #enable: Enables backwards incompatible server extensions.
346
+ # <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
347
+ # - #select: Open a mailbox and enter the "_selected_" state.
348
+ # - #examine: Open a mailbox read-only, and enter the "_selected_" state.
307
349
  # - #create: Creates a new mailbox.
308
350
  # - #delete: Permanently remove a mailbox.
309
351
  # - #rename: Change the name of a mailbox.
@@ -311,37 +353,31 @@ module Net
311
353
  # - #unsubscribe: Removes a mailbox from the "subscribed" set.
312
354
  # - #list: Returns names and attributes of mailboxes matching a given pattern.
313
355
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
314
- #
315
- # <em>Requires the +NAMESPACE+ capability.</em>
356
+ # <em>Requires the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
316
357
  # - #status: Returns mailbox information, e.g. message count, unseen message
317
358
  # count, +UIDVALIDITY+ and +UIDNEXT+.
318
359
  # - #append: Appends a message to the end of a mailbox.
319
360
  # - #idle: Allows the server to send updates to the client, without the client
320
361
  # needing to poll using #noop.
362
+ # <em>Requires the +IDLE+ or +IMAP4rev2+ capability.</em>
363
+ # - *Obsolete* #lsub: <em>Replaced by <tt>LIST-EXTENDED</tt> and removed from
364
+ # +IMAP4rev2+.</em> Lists mailboxes in the "subscribed" set.
321
365
  #
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
- #++
366
+ # <em>*Note:* Net::IMAP hasn't implemented <tt>LIST-EXTENDED</tt> yet.</em>
329
367
  #
330
- # ==== \IMAP commands for the "Selected" state
368
+ # ==== Selected state
331
369
  #
332
- # In addition to the commands for any state and the +authenticated+
333
- # commands, the following commands are valid in the +selected+ state:
370
+ # In addition to the commands for any state and the "_authenticated_"
371
+ # commands, the following commands are valid in the "_selected_" state:
334
372
  #
335
- # - #close: Closes the mailbox and returns to the +authenticated+ state,
373
+ # - #close: Closes the mailbox and returns to the "_authenticated_" state,
336
374
  # expunging deleted messages, unless the mailbox was opened as read-only.
337
- # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
375
+ # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
338
376
  # without expunging any messages.
339
- #
340
- # <em>Requires the +UNSELECT+ capability.</em>
377
+ # <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
341
378
  # - #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>
379
+ # - #uid_expunge: Restricts expunge to only remove the specified UIDs.
380
+ # <em>Requires the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
345
381
  # - #search, #uid_search: Returns sequence numbers or UIDs of messages that
346
382
  # match the given searching criteria.
347
383
  # - #fetch, #uid_fetch: Returns data associated with a set of messages,
@@ -351,45 +387,35 @@ module Net
351
387
  # specified destination mailbox.
352
388
  # - #move, #uid_move: Moves the specified messages to the end of the
353
389
  # specified destination mailbox, expunging them from the current mailbox.
390
+ # <em>Requires the +MOVE+ or +IMAP4rev2+ capability.</em>
391
+ # - #check: <em>*Obsolete:* removed from +IMAP4rev2+.</em>
392
+ # Can be replaced with #noop or #idle.
354
393
  #
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
- #++
394
+ # ==== Logout state
360
395
  #
361
- # ==== \IMAP commands for the "Logout" state
362
- #
363
- # No \IMAP commands are valid in the +logout+ state. If the socket is still
396
+ # No \IMAP commands are valid in the "_logout_" state. If the socket is still
364
397
  # open, Net::IMAP will close it after receiving server confirmation.
365
398
  # Exceptions will be raised by \IMAP commands that have already started and
366
399
  # are waiting for a response, as well as any that are called after logout.
367
400
  #
368
- # === Supported \IMAP extensions
401
+ # === \IMAP extension support
369
402
  #
370
403
  # ==== RFC9051: +IMAP4rev2+
371
404
  #
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.
405
+ # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is not supported
406
+ # yet, Net::IMAP supports several extensions that have been folded into it:
407
+ # +ENABLE+, +IDLE+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, and +UNSELECT+.
408
+ # Commands for these extensions are listed with the {Core IMAP
409
+ # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above.
410
+ #
411
+ # >>>
412
+ # <em>The following are folded into +IMAP4rev2+ but are currently
413
+ # unsupported or incompletely supported by</em> Net::IMAP<em>: RFC4466
414
+ # extensions, +ESEARCH+, +SEARCHRES+, +LIST-EXTENDED+,
415
+ # +LIST-STATUS+, +LITERAL-+, +BINARY+ fetch, and +SPECIAL-USE+. The
416
+ # following extensions are implicitly supported, but will be updated with
417
+ # more direct support: RFC5530 response codes, <tt>STATUS=SIZE</tt>, and
418
+ # <tt>STATUS=DELETED</tt>.</em>
393
419
  #
394
420
  # ==== RFC2087: +QUOTA+
395
421
  # - #getquota: returns the resource usage and limits for a quota root
@@ -398,92 +424,47 @@ 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://tools.ietf.org/html/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://tools.ietf.org/html/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
- # ==== RFC3516: +BINARY+
421
- # TODO...
422
- #++
423
- #
424
440
  # ==== 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,
441
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
442
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
443
+ # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
428
444
  # without expunging any messages.
429
445
  #
430
446
  # ==== RFC4314: +ACL+
431
447
  # - #getacl: lists the authenticated user's access rights to a mailbox.
432
448
  # - #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.
449
+ # >>>
450
+ # *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
437
451
  #
438
452
  # ==== 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].
453
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
454
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
441
455
  # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
442
456
  # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode
443
457
  # - Updates #append with the +APPENDUID+ ResponseCode
444
458
  # - Updates #copy, #move with the +COPYUID+ ResponseCode
445
459
  #
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
- #--
467
460
  # ==== RFC4959: +SASL-IR+
468
- # TODO...
469
461
  # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
470
- # - Updates #authenticate to reduce round-trips for supporting mechanisms.
471
- #++
462
+ # - Updates #authenticate with the option to send an initial response.
472
463
  #
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
- #++
464
+ # ==== RFC5161: +ENABLE+
465
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
466
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
467
+ # - #enable: Enables backwards incompatible server extensions.
487
468
  #
488
469
  # ==== RFC5256: +SORT+
489
470
  # - #sort, #uid_sort: An alternate version of #search or #uid_search which
@@ -493,75 +474,20 @@ module Net
493
474
  # which arranges the results into ordered groups or threads according to a
494
475
  # chosen algorithm.
495
476
  #
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
477
  # ==== +XLIST+ (non-standard, deprecated)
515
478
  # - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses.
516
479
  #
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
- #++
523
- #
524
480
  # ==== 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].
481
+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
482
+ # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
527
483
  # - #move, #uid_move: Moves the specified messages to the end of the
528
484
  # specified destination mailbox, expunging them from the current mailbox.
529
485
  #
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
- #++
549
- #
550
- # === Handling server responses
551
- #
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.
486
+ # ==== RFC6855: <tt>UTF8=ACCEPT</tt>, <tt>UTF8=ONLY</tt>
559
487
  #
488
+ # - See #enable for information about support for UTF-8 string encoding.
560
489
  #
561
490
  # == References
562
- #--
563
- # TODO: Consider moving references list to REFERENCES.md or REFERENCES.rdoc.
564
- #++
565
491
  #
566
492
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
567
493
  # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
@@ -662,27 +588,21 @@ module Net
662
588
  # RFC 1864, DOI 10.17487/RFC1864, October 1995,
663
589
  # <https://www.rfc-editor.org/info/rfc1864>.
664
590
  #
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
- #++
591
+ # [RFC3503[https://tools.ietf.org/html/rfc3503]]::
592
+ # Melnikov, A., "Message Disposition Notification (MDN)
593
+ # profile for Internet Message Access Protocol (IMAP)",
594
+ # RFC 3503, DOI 10.17487/RFC3503, March 2003,
595
+ # <https://www.rfc-editor.org/info/rfc3503>.
674
596
  #
675
- # === Supported \IMAP Extensions
597
+ # === \IMAP Extensions
676
598
  #
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
599
  # [QUOTA[https://tools.ietf.org/html/rfc9208]]::
683
600
  # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
684
601
  # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
685
- #++
602
+ #
603
+ # <em>Note: obsoletes</em>
604
+ # RFC-2087[https://tools.ietf.org/html/rfc2087]<em> (January 1997)</em>.
605
+ # <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
686
606
  # [IDLE[https://tools.ietf.org/html/rfc2177]]::
687
607
  # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
688
608
  # June 1997, <https://www.rfc-editor.org/info/rfc2177>.
@@ -715,33 +635,43 @@ module Net
715
635
  # Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
716
636
  # (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
717
637
  # <https://www.rfc-editor.org/info/rfc6851>.
638
+ # [UTF8=ACCEPT[https://tools.ietf.org/html/rfc6855]]::
639
+ # [UTF8=ONLY[https://tools.ietf.org/html/rfc6855]]::
640
+ # Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed.,
641
+ # "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013,
642
+ # <https://www.rfc-editor.org/info/rfc6855>.
718
643
  #
719
644
  # === IANA registries
720
- #
721
645
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
722
646
  # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
723
647
  # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
724
648
  # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
725
649
  # * {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
650
  # * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
735
651
  # * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]:
736
652
  # +imap+: tcp/143, +imaps+: tcp/993
737
653
  # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
738
654
  # +imap+
739
655
  # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
656
+ # ===== For currently unsupported features:
657
+ # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
658
+ # * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
659
+ # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
660
+ # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
661
+ # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
662
+ # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
740
663
  #
741
664
  class IMAP < Protocol
742
- VERSION = "0.3.9"
665
+ VERSION = "0.4.0"
743
666
 
744
- autoload :ResponseReader, File.expand_path("imap/response_reader", __dir__)
667
+ # Aliases for supported capabilities, to be used with the #enable command.
668
+ ENABLE_ALIASES = {
669
+ utf8: "UTF8=ACCEPT",
670
+ "UTF8=ONLY" => "UTF8=ACCEPT",
671
+ }.freeze
672
+
673
+ autoload :SASL, File.expand_path("imap/sasl", __dir__)
674
+ autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
745
675
 
746
676
  include MonitorMixin
747
677
  if defined?(OpenSSL::SSL)
@@ -749,69 +679,6 @@ module Net
749
679
  include SSL
750
680
  end
751
681
 
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:
814
-
815
682
  # Returns the debug mode.
816
683
  def self.debug
817
684
  return @@debug
@@ -838,9 +705,175 @@ module Net
838
705
  alias default_ssl_port default_tls_port
839
706
  end
840
707
 
708
+ # Returns the initial greeting the server, an UntaggedResponse.
709
+ attr_reader :greeting
710
+
711
+ # Seconds to wait until a connection is opened.
712
+ # If the IMAP object cannot open a connection within this time,
713
+ # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
714
+ attr_reader :open_timeout
715
+
716
+ # Seconds to wait until an IDLE response is received.
717
+ attr_reader :idle_response_timeout
718
+
719
+ # The hostname this client connected to
720
+ attr_reader :host
721
+
722
+ # The port this client connected to
723
+ attr_reader :port
724
+
725
+ # Returns the
726
+ # {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]
727
+ # used by the SSLSocket when TLS is attempted, even when the TLS handshake
728
+ # is unsuccessful. The context object will be frozen.
729
+ #
730
+ # Returns +nil+ for a plaintext connection.
731
+ attr_reader :ssl_ctx
732
+
733
+ # Returns the parameters that were sent to #ssl_ctx
734
+ # {set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]
735
+ # when the connection tries to use TLS (even when unsuccessful).
736
+ #
737
+ # Returns +false+ for a plaintext connection.
738
+ attr_reader :ssl_ctx_params
739
+
740
+ # Creates a new Net::IMAP object and connects it to the specified
741
+ # +host+.
742
+ #
743
+ # ==== Options
744
+ #
745
+ # Accepts the following options:
746
+ #
747
+ # [port]
748
+ # Port number. Defaults to 993 when +ssl+ is truthy, and 143 otherwise.
749
+ #
750
+ # [ssl]
751
+ # If +true+, the connection will use TLS with the default params set by
752
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params].
753
+ # If +ssl+ is a hash, it's passed to
754
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
755
+ # the keys are names of attribute assignment methods on
756
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
757
+ #
758
+ # [open_timeout]
759
+ # Seconds to wait until a connection is opened
760
+ # [idle_response_timeout]
761
+ # Seconds to wait until an IDLE response is received
762
+ #
763
+ # See DeprecatedClientOptions.new for deprecated arguments.
764
+ #
765
+ # ==== Examples
766
+ #
767
+ # Connect to cleartext port 143 at mail.example.com and recieve the server greeting:
768
+ # imap = Net::IMAP.new('mail.example.com', ssl: false) # => #<Net::IMAP:0x00007f79b0872bd0>
769
+ # imap.port => 143
770
+ # imap.tls_verified? => false
771
+ # imap.greeting => name: ("OK" | "PREAUTH") => status
772
+ # status # => "OK"
773
+ # # The client is connected in the "Not Authenticated" state.
774
+ #
775
+ # Connect with TLS to port 993
776
+ # imap = Net::IMAP.new('mail.example.com', ssl: true) # => #<Net::IMAP:0x00007f79b0872bd0>
777
+ # imap.port => 993
778
+ # imap.tls_verified? => true
779
+ # imap.greeting => name: (/OK/i | /PREAUTH/i) => status
780
+ # case status
781
+ # in /OK/i
782
+ # # The client is connected in the "Not Authenticated" state.
783
+ # imap.authenticate("PLAIN", "joe_user", "joes_password")
784
+ # in /PREAUTH/i
785
+ # # The client is connected in the "Authenticated" state.
786
+ # end
787
+ #
788
+ # Connect with prior authentication, for example using an SSL certificate:
789
+ # ssl_ctx_params = {
790
+ # cert: OpenSSL::X509::Certificate.new(File.read("client.crt")),
791
+ # key: OpenSSL::PKey::EC.new(File.read('client.key')),
792
+ # extra_chain_cert: [
793
+ # OpenSSL::X509::Certificate.new(File.read("intermediate.crt")),
794
+ # ],
795
+ # }
796
+ # imap = Net::IMAP.new('mail.example.com', ssl: ssl_ctx_params)
797
+ # imap.port => 993
798
+ # imap.tls_verified? => true
799
+ # imap.greeting => name: "PREAUTH"
800
+ # # The client is connected in the "Authenticated" state.
801
+ #
802
+ # ==== Exceptions
803
+ #
804
+ # The most common errors are:
805
+ #
806
+ # [Errno::ECONNREFUSED]
807
+ # Connection refused by +host+ or an intervening firewall.
808
+ # [Errno::ETIMEDOUT]
809
+ # Connection timed out (possibly due to packets being dropped by an
810
+ # intervening firewall).
811
+ # [Errno::ENETUNREACH]
812
+ # There is no route to that network.
813
+ # [SocketError]
814
+ # Hostname not known or other socket error.
815
+ # [Net::IMAP::ByeResponseError]
816
+ # Connected to the host successfully, but it immediately said goodbye.
817
+ #
818
+ def initialize(host, port: nil, ssl: nil,
819
+ open_timeout: 30, idle_response_timeout: 5)
820
+ super()
821
+ # Config options
822
+ @host = host
823
+ @port = port || (ssl ? SSL_PORT : PORT)
824
+ @open_timeout = Integer(open_timeout)
825
+ @idle_response_timeout = Integer(idle_response_timeout)
826
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
827
+
828
+ # Basic Client State
829
+ @utf8_strings = false
830
+ @debug_output_bol = true
831
+ @exception = nil
832
+ @greeting = nil
833
+ @capabilities = nil
834
+
835
+ # Client Protocol Reciever
836
+ @parser = ResponseParser.new
837
+ @responses = Hash.new {|h, k| h[k] = [] }
838
+ @response_handlers = []
839
+ @receiver_thread = nil
840
+ @receiver_thread_exception = nil
841
+ @receiver_thread_terminating = false
842
+
843
+ # Client Protocol Sender (including state for currently running commands)
844
+ @tag_prefix = "RUBY"
845
+ @tagno = 0
846
+ @tagged_responses = {}
847
+ @tagged_response_arrival = new_cond
848
+ @continued_command_tag = nil
849
+ @continuation_request_arrival = new_cond
850
+ @continuation_request_exception = nil
851
+ @idle_done_cond = nil
852
+ @logout_command_tag = nil
853
+
854
+ # Connection
855
+ @tls_verified = false
856
+ @sock = tcp_socket(@host, @port)
857
+ start_tls_session if ssl_ctx
858
+ start_imap_connection
859
+
860
+ # DEPRECATED: to remove in next version
861
+ @client_thread = Thread.current
862
+ end
863
+
864
+ # Returns true after the TLS negotiation has completed and the remote
865
+ # hostname has been verified. Returns false when TLS has been established
866
+ # but peer verification was disabled.
867
+ def tls_verified?; @tls_verified end
868
+
869
+ def client_thread # :nodoc:
870
+ warn "Net::IMAP#client_thread is deprecated and will be removed soon."
871
+ @client_thread
872
+ end
873
+
841
874
  # Disconnects from the server.
842
875
  #
843
- # Related: #logout
876
+ # Related: #logout, #logout!
844
877
  def disconnect
845
878
  return if disconnected?
846
879
  begin
@@ -870,62 +903,123 @@ module Net
870
903
  return @sock.closed?
871
904
  end
872
905
 
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.
906
+ # Returns whether the server supports a given +capability+. When available,
907
+ # cached #capabilities are used without sending a new #capability command to
908
+ # the server.
876
909
  #
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.
910
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
911
+ # behaviour according to the server's advertised #capabilities.</em>
880
912
  #
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>
913
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
914
+ #
915
+ # Related: #auth_capable?, #capabilities, #capability, #enable
916
+ def capable?(capability) capabilities.include? capability.to_s.upcase end
917
+ alias capability? capable?
918
+
919
+ # Returns the server capabilities. When available, cached capabilities are
920
+ # used without sending a new #capability command to the server.
921
+ #
922
+ # To ensure a case-insensitive comparison, #capable? can be used instead.
923
+ #
924
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
925
+ # behaviour according to the server's advertised #capabilities.</em>
926
+ #
927
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
928
+ #
929
+ # Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable
930
+ def capabilities
931
+ @capabilities || capability
932
+ end
933
+
934
+ # Returns the #authenticate mechanisms that the server claims to support.
935
+ # These are derived from the #capabilities with an <tt>AUTH=</tt> prefix.
936
+ #
937
+ # This may be different when the connection is cleartext or using TLS. Most
938
+ # servers will drop all <tt>AUTH=</tt> mechanisms from #capabilities after
939
+ # the connection has authenticated.
887
940
  #
888
- # Capability requirements—other than +IMAP4rev1+—are listed in the
889
- # documentation for each command method.
941
+ # imap = Net::IMAP.new(hostname, ssl: false)
942
+ # imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"]
943
+ # imap.auth_mechanisms # => []
890
944
  #
891
- # ===== Basic IMAP4rev1 capabilities
945
+ # imap.starttls
946
+ # imap.capabilities # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2",
947
+ # # "AUTH=OAUTHBEARER"]
948
+ # imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"]
892
949
  #
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.
950
+ # imap.authenticate("XOAUTH2", username, oauth2_access_token)
951
+ # imap.auth_mechanisms # => []
898
952
  #
899
- # ===== Using IMAP4rev1 extensions
953
+ # Related: #authenticate, #auth_capable?, #capabilities
954
+ def auth_mechanisms
955
+ capabilities
956
+ .grep(/\AAUTH=/i)
957
+ .map { _1.delete_prefix("AUTH=") }
958
+ end
959
+
960
+ # Returns whether the server supports a given SASL +mechanism+ for use with
961
+ # the #authenticate command. The +mechanism+ is supported when
962
+ # #capabilities includes <tt>"AUTH=#{mechanism.to_s.upcase}"</tt>. When
963
+ # available, cached capabilities are used without sending a new #capability
964
+ # command to the server.
900
965
  #
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.
966
+ # imap.capable? "AUTH=PLAIN" # => true
967
+ # imap.auth_capable? "PLAIN" # => true
968
+ # imap.auth_capable? "blurdybloop" # => false
906
969
  #
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.
970
+ # Related: #authenticate, #auth_mechanisms, #capable?, #capabilities
971
+ def auth_capable?(mechanism)
972
+ capable? "AUTH=#{mechanism}"
973
+ end
974
+
975
+ # Returns whether capabilities have been cached. When true, #capable? and
976
+ # #capabilities don't require sending a #capability command to the server.
912
977
  #
913
- # ===== Caching +CAPABILITY+ responses
978
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
914
979
  #
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.
980
+ # Related: #capable?, #capability, #clear_cached_capabilities
981
+ def capabilities_cached?
982
+ !!@capabilities
983
+ end
984
+
985
+ # Clears capabilities that have been remembered by the Net::IMAP client.
986
+ # This forces a #capability command to be sent the next time a #capabilities
987
+ # query method is called.
919
988
  #
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.
989
+ # Net::IMAP automatically discards its cached capabilities when they can
990
+ # change. Explicitly calling this _should_ be unnecessary for well-behaved
991
+ # servers.
924
992
  #
993
+ # Related: #capable?, #capability, #capabilities_cached?
994
+ def clear_cached_capabilities
995
+ synchronize do
996
+ clear_responses("CAPABILITY")
997
+ @capabilities = nil
998
+ end
999
+ end
1000
+
1001
+ # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
1002
+ # and returns an array of capabilities that are supported by the server.
1003
+ # The result is stored for use by #capable? and #capabilities.
1004
+ #
1005
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
1006
+ # behaviour according to the server's advertised #capabilities.</em>
1007
+ #
1008
+ # Net::IMAP automatically stores and discards capability data according to
1009
+ # the requirements and recommendations in
1010
+ # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
1011
+ # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
1012
+ # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
1013
+ # Use #capable?, #auth_capable?, or #capabilities to this cache and avoid
1014
+ # sending the #capability command unnecessarily.
1015
+ #
1016
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
1017
+ #
1018
+ # Related: #capable?, #auth_capable?, #capability, #enable
925
1019
  def capability
926
1020
  synchronize do
927
1021
  send_command("CAPABILITY")
928
- return @responses.delete("CAPABILITY")[-1]
1022
+ @capabilities = clear_responses("CAPABILITY").last.freeze
929
1023
  end
930
1024
  end
931
1025
 
@@ -936,8 +1030,7 @@ module Net
936
1030
  # Note that the user should first check if the server supports the ID
937
1031
  # capability. For example:
938
1032
  #
939
- # capabilities = imap.capability
940
- # if capabilities.include?("ID")
1033
+ # if capable?(:ID)
941
1034
  # id = imap.id(
942
1035
  # name: "my IMAP client (ruby)",
943
1036
  # version: MyIMAP::VERSION,
@@ -955,7 +1048,7 @@ module Net
955
1048
  def id(client_id=nil)
956
1049
  synchronize do
957
1050
  send_command("ID", ClientID.new(client_id))
958
- @responses.delete("ID")&.last
1051
+ clear_responses("ID").last
959
1052
  end
960
1053
  end
961
1054
 
@@ -979,15 +1072,43 @@ module Net
979
1072
  # to inform the command to inform the server that the client is done with
980
1073
  # the connection.
981
1074
  #
982
- # Related: #disconnect
1075
+ # Related: #disconnect, #logout!
983
1076
  def logout
984
1077
  send_command("LOGOUT")
985
1078
  end
986
1079
 
1080
+ # Calls #logout then, after receiving the TaggedResponse for the +LOGOUT+,
1081
+ # calls #disconnect. Returns the TaggedResponse from +LOGOUT+. Returns
1082
+ # +nil+ when the client is already disconnected, in contrast to #logout
1083
+ # which raises an exception.
1084
+ #
1085
+ # If #logout raises a StandardError, a warning will be printed but the
1086
+ # exception will not be re-raised.
1087
+ #
1088
+ # This is useful in situations where the connection must be dropped, for
1089
+ # example for security or after tests. If logout errors need to be handled,
1090
+ # use #logout and #disconnect instead.
1091
+ #
1092
+ # Related: #logout, #disconnect
1093
+ def logout!
1094
+ logout unless disconnected?
1095
+ rescue => ex
1096
+ warn "%s during <Net::IMAP %s:%s> logout!: %s" % [
1097
+ ex.class, host, port, ex
1098
+ ]
1099
+ ensure
1100
+ disconnect
1101
+ end
1102
+
987
1103
  # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1]
988
1104
  # to start a TLS session.
989
1105
  #
990
- # Any +options+ are forwarded to OpenSSL::SSL::SSLContext#set_params.
1106
+ # Any +options+ are forwarded directly to
1107
+ # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
1108
+ # the keys are names of attribute assignment methods on
1109
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
1110
+ #
1111
+ # See DeprecatedClientOptions#starttls for deprecated arguments.
991
1112
  #
992
1113
  # This method returns after TLS negotiation and hostname verification are
993
1114
  # both successful. Any error indicates that the connection has not been
@@ -997,132 +1118,156 @@ module Net
997
1118
  # >>>
998
1119
  # Any #response_handlers added before STARTTLS should be aware that the
999
1120
  # TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation.
1000
- # TLS negotiation starts immediately after that response.
1121
+ # TLS starts immediately _after_ that response. Any response code sent
1122
+ # with the response (e.g. CAPABILITY) is insecure and cannot be trusted.
1001
1123
  #
1002
1124
  # Related: Net::IMAP.new, #login, #authenticate
1003
1125
  #
1004
1126
  # ===== Capability
1005
- #
1006
- # The server's capabilities must include +STARTTLS+.
1127
+ # Clients should not call #starttls unless the server advertises the
1128
+ # +STARTTLS+ capability.
1007
1129
  #
1008
1130
  # 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.
1131
+ # Cached #capabilities will be cleared when this method completes.
1015
1132
  #
1016
- def starttls(options = {}, verify = true)
1133
+ def starttls(**options)
1134
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
1017
1135
  send_command("STARTTLS") do |resp|
1018
1136
  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)
1137
+ clear_cached_capabilities
1138
+ clear_responses
1139
+ start_tls_session
1026
1140
  end
1027
1141
  end
1028
1142
  end
1029
1143
 
1030
1144
  # :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
1145
+ # authenticate(mechanism, *, sasl_ir: true, **, &) -> ok_resp
1036
1146
  #
1037
1147
  # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
1038
1148
  # to authenticate the client. If successful, the connection enters the
1039
1149
  # "_authenticated_" state.
1040
1150
  #
1041
1151
  # +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
1152
  #
1047
- # An exception Net::IMAP::NoResponseError is raised if authentication fails.
1153
+ # +sasl_ir+ allows or disallows sending an "initial response" (see the
1154
+ # +SASL-IR+ capability, below).
1048
1155
  #
1049
- # Related: #login, #starttls
1156
+ # All other arguments are forwarded to the registered SASL authenticator for
1157
+ # the requested mechanism. <em>The documentation for each individual
1158
+ # mechanism must be consulted for its specific parameters.</em>
1050
1159
  #
1051
- # ==== Supported SASL Mechanisms
1160
+ # Related: #login, #starttls, #auth_capable?, #auth_mechanisms
1052
1161
  #
1053
- # +PLAIN+:: See PlainAuthenticator.
1054
- # Login using clear-text username and password.
1162
+ # ==== Mechanisms
1055
1163
  #
1056
- # +XOAUTH2+:: See XOauth2Authenticator.
1057
- # Login using a username and OAuth2 access token.
1058
- # Non-standard and obsoleted by +OAUTHBEARER+, but widely
1059
- # supported.
1164
+ # Each mechanism has different properties and requirements. Please consult
1165
+ # the documentation for the specific mechanisms you are using:
1060
1166
  #
1061
- # >>>
1062
- # *Deprecated:* <em>Obsolete mechanisms are available for backwards
1063
- # compatibility.</em>
1167
+ # +ANONYMOUS+::
1168
+ # See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
1064
1169
  #
1065
- # For +DIGEST-MD5+ see DigestMD5Authenticator.
1170
+ # Allows the user to gain access to public services or resources without
1171
+ # authenticating or disclosing an identity.
1066
1172
  #
1067
- # For +LOGIN+, see LoginAuthenticator.
1173
+ # +EXTERNAL+::
1174
+ # See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
1068
1175
  #
1069
- # For +CRAM-MD5+, see CramMD5Authenticator.
1176
+ # Authenticates using already established credentials, such as a TLS
1177
+ # certificate or IPsec.
1070
1178
  #
1071
- # <em>Using a deprecated mechanism will print a warning.</em>
1179
+ # +OAUTHBEARER+::
1180
+ # See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator].
1072
1181
  #
1073
- # See Net::IMAP::Authenticators for information on plugging in
1074
- # authenticators for other mechanisms. See the {SASL mechanism
1182
+ # Login using an OAuth2 Bearer token. This is the standard mechanism
1183
+ # for using OAuth2 with \SASL, but it is not yet deployed as widely as
1184
+ # +XOAUTH2+.
1185
+ #
1186
+ # +PLAIN+::
1187
+ # See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator].
1188
+ #
1189
+ # Login using clear-text username and password.
1190
+ #
1191
+ # +SCRAM-SHA-1+::
1192
+ # +SCRAM-SHA-256+::
1193
+ # See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator].
1194
+ #
1195
+ # Login by username and password. The password is not sent to the
1196
+ # server but is used in a salted challenge/response exchange.
1197
+ # +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
1198
+ # Net::IMAP::SASL. New authenticators can easily be added for any other
1199
+ # <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
1200
+ # OpenSSL::Digest.
1201
+ #
1202
+ # +XOAUTH2+::
1203
+ # See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator].
1204
+ #
1205
+ # Login using a username and an OAuth2 access token. Non-standard and
1206
+ # obsoleted by +OAUTHBEARER+, but widely supported.
1207
+ #
1208
+ # See the {SASL mechanism
1075
1209
  # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
1076
- # for information on these and other SASL mechanisms.
1210
+ # for a list of all SASL mechanisms and their specifications. To register
1211
+ # new authenticators, see Authenticators.
1077
1212
  #
1078
- # ===== Capabilities
1213
+ # ===== Deprecated mechanisms
1079
1214
  #
1080
- # Clients MUST NOT attempt to authenticate with a mechanism unless
1081
- # <tt>"AUTH=#{mechanism}"</tt> for that mechanism is a server capability.
1215
+ # <em>Obsolete mechanisms should be avoided, but are still available for
1216
+ # backwards compatibility. See</em> Net::IMAP::SASL@Deprecated+mechanisms.
1217
+ # <em>Using a deprecated mechanism will print a warning.</em>
1082
1218
  #
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
1219
+ # ==== Capabilities
1220
+ #
1221
+ # <tt>"AUTH=#{mechanism}"</tt> capabilities indicate server support for
1222
+ # mechanisms. Use #auth_capable? or #auth_mechanisms to check for support
1223
+ # before using a particular mechanism.
1224
+ #
1225
+ # if imap.auth_capable? "XOAUTH2"
1226
+ # imap.authenticate "XOAUTH2", username, oauth2_access_token
1227
+ # elsif imap.auth_capable? "PLAIN"
1228
+ # imap.authenticate "PLAIN", username, password
1229
+ # elsif !imap.capability? "LOGINDISABLED"
1113
1230
  # imap.login username, password
1231
+ # else
1232
+ # raise "No acceptable authentication mechanism is available"
1114
1233
  # end
1115
1234
  #
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
1235
+ # Although servers should list all supported \SASL mechanisms, they may
1236
+ # allow authentication with an unlisted +mechanism+.
1237
+ #
1238
+ # If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported
1239
+ # and the appropriate <tt>"AUTH=#{mechanism}"</tt> capability is present,
1240
+ # an "initial response" may be sent as an argument to the +AUTHENTICATE+
1241
+ # command, saving a round-trip. The SASL exchange allows for server
1242
+ # challenges and client responses, but many mechanisms expect the client to
1243
+ # "respond" first. The initial response will only be sent for
1244
+ # "client-first" mechanisms.
1245
+ #
1246
+ # Server capabilities may change after #starttls, #login, and #authenticate.
1247
+ # Previously cached #capabilities will be cleared when this method
1248
+ # completes. If the TaggedResponse to #authenticate includes updated
1249
+ # capabilities, they will be cached.
1250
+ def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback)
1251
+ mechanism = mechanism.to_s.tr("_", "-").upcase
1252
+ authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
1253
+ cmdargs = ["AUTHENTICATE", mechanism]
1254
+ if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
1255
+ authenticator.respond_to?(:initial_response?) &&
1256
+ authenticator.initial_response?
1257
+ response = authenticator.process(nil)
1258
+ cmdargs << (response.empty? ? "=" : [response].pack("m0"))
1259
+ end
1260
+ result = send_command_with_continuations(*cmdargs) {|data|
1261
+ challenge = data.unpack1("m")
1262
+ response = authenticator.process challenge
1263
+ [response].pack("m0")
1264
+ }
1265
+ if authenticator.respond_to?(:done?) && !authenticator.done?
1266
+ logout!
1267
+ raise SASL::AuthenticationIncomplete, result
1125
1268
  end
1269
+ @capabilities = capabilities_from_resp_code result
1270
+ result
1126
1271
  end
1127
1272
 
1128
1273
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1130,16 +1275,25 @@ module Net
1130
1275
  # this +user+. If successful, the connection enters the "_authenticated_"
1131
1276
  # state.
1132
1277
  #
1133
- # Using #authenticate is generally preferred over #login. The LOGIN command
1134
- # is not the same as #authenticate with the "LOGIN" +mechanism+.
1278
+ # Using #authenticate {should be
1279
+ # preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command]
1280
+ # over #login. The LOGIN command is not the same as #authenticate with the
1281
+ # "LOGIN" +mechanism+.
1135
1282
  #
1136
1283
  # A Net::IMAP::NoResponseError is raised if authentication fails.
1137
1284
  #
1138
1285
  # Related: #authenticate, #starttls
1139
1286
  #
1140
- # ==== Capabilities
1141
- # Clients MUST NOT call #login if +LOGINDISABLED+ is listed with the
1142
- # capabilities.
1287
+ # ===== Capabilities
1288
+ #
1289
+ # An IMAP client MUST NOT call #login when the server advertises the
1290
+ # +LOGINDISABLED+ capability.
1291
+ #
1292
+ # if imap.capability? "LOGINDISABLED"
1293
+ # raise "Remote server has disabled the login command"
1294
+ # else
1295
+ # imap.login username, password
1296
+ # end
1143
1297
  #
1144
1298
  # Server capabilities may change after #starttls, #login, and #authenticate.
1145
1299
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1148,17 +1302,18 @@ module Net
1148
1302
  #
1149
1303
  def login(user, password)
1150
1304
  send_command("LOGIN", user, password)
1305
+ .tap { @capabilities = capabilities_from_resp_code _1 }
1151
1306
  end
1152
1307
 
1153
1308
  # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
1154
1309
  # to select a +mailbox+ so that messages in the +mailbox+ can be accessed.
1155
1310
  #
1156
1311
  # 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.
1312
+ # that mailbox from <tt>imap.responses("EXISTS", &:last)</tt>, and the
1313
+ # number of recent messages from <tt>imap.responses("RECENT", &:last)</tt>.
1314
+ # Note that these values can change if new messages arrive during a session
1315
+ # or when existing messages are expunged; see #add_response_handler for a
1316
+ # way to detect these events.
1162
1317
  #
1163
1318
  # A Net::IMAP::NoResponseError is raised if the mailbox does not
1164
1319
  # exist or is for some reason non-selectable.
@@ -1289,7 +1444,7 @@ module Net
1289
1444
  def list(refname, mailbox)
1290
1445
  synchronize do
1291
1446
  send_command("LIST", refname, mailbox)
1292
- return @responses.delete("LIST")
1447
+ clear_responses("LIST")
1293
1448
  end
1294
1449
  end
1295
1450
 
@@ -1327,8 +1482,7 @@ module Net
1327
1482
  #
1328
1483
  # ===== For example:
1329
1484
  #
1330
- # capabilities = imap.capability
1331
- # if capabilities.include?("NAMESPACE")
1485
+ # if capable?("NAMESPACE")
1332
1486
  # namespaces = imap.namespace
1333
1487
  # if namespace = namespaces.personal.first
1334
1488
  # prefix = namespace.prefix # e.g. "" or "INBOX."
@@ -1347,7 +1501,7 @@ module Net
1347
1501
  def namespace
1348
1502
  synchronize do
1349
1503
  send_command("NAMESPACE")
1350
- return @responses.delete("NAMESPACE")[-1]
1504
+ clear_responses("NAMESPACE").last
1351
1505
  end
1352
1506
  end
1353
1507
 
@@ -1391,7 +1545,7 @@ module Net
1391
1545
  def xlist(refname, mailbox)
1392
1546
  synchronize do
1393
1547
  send_command("XLIST", refname, mailbox)
1394
- return @responses.delete("XLIST")
1548
+ clear_responses("XLIST")
1395
1549
  end
1396
1550
  end
1397
1551
 
@@ -1410,8 +1564,8 @@ module Net
1410
1564
  synchronize do
1411
1565
  send_command("GETQUOTAROOT", mailbox)
1412
1566
  result = []
1413
- result.concat(@responses.delete("QUOTAROOT"))
1414
- result.concat(@responses.delete("QUOTA"))
1567
+ result.concat(clear_responses("QUOTAROOT"))
1568
+ result.concat(clear_responses("QUOTA"))
1415
1569
  return result
1416
1570
  end
1417
1571
  end
@@ -1430,7 +1584,7 @@ module Net
1430
1584
  def getquota(mailbox)
1431
1585
  synchronize do
1432
1586
  send_command("GETQUOTA", mailbox)
1433
- return @responses.delete("QUOTA")
1587
+ clear_responses("QUOTA")
1434
1588
  end
1435
1589
  end
1436
1590
 
@@ -1486,7 +1640,7 @@ module Net
1486
1640
  def getacl(mailbox)
1487
1641
  synchronize do
1488
1642
  send_command("GETACL", mailbox)
1489
- return @responses.delete("ACL")[-1]
1643
+ clear_responses("ACL").last
1490
1644
  end
1491
1645
  end
1492
1646
 
@@ -1501,7 +1655,7 @@ module Net
1501
1655
  def lsub(refname, mailbox)
1502
1656
  synchronize do
1503
1657
  send_command("LSUB", refname, mailbox)
1504
- return @responses.delete("LSUB")
1658
+ clear_responses("LSUB")
1505
1659
  end
1506
1660
  end
1507
1661
 
@@ -1525,7 +1679,7 @@ module Net
1525
1679
  def status(mailbox, attr)
1526
1680
  synchronize do
1527
1681
  send_command("STATUS", mailbox, attr)
1528
- return @responses.delete("STATUS")[-1].attr
1682
+ clear_responses("STATUS").last&.attr
1529
1683
  end
1530
1684
  end
1531
1685
 
@@ -1614,7 +1768,7 @@ module Net
1614
1768
  def expunge
1615
1769
  synchronize do
1616
1770
  send_command("EXPUNGE")
1617
- return @responses.delete("EXPUNGE")
1771
+ clear_responses("EXPUNGE")
1618
1772
  end
1619
1773
  end
1620
1774
 
@@ -1646,7 +1800,7 @@ module Net
1646
1800
  def uid_expunge(uid_set)
1647
1801
  synchronize do
1648
1802
  send_command("UID EXPUNGE", MessageSet.new(uid_set))
1649
- return @responses.delete("EXPUNGE")
1803
+ clear_responses("EXPUNGE")
1650
1804
  end
1651
1805
  end
1652
1806
 
@@ -1665,7 +1819,7 @@ module Net
1665
1819
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
1666
1820
  # in addition to documentation for
1667
1821
  # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
1668
- # reported by #capability which may define additional search filters, e.g:
1822
+ # reported by #capabilities which may define additional search filters, e.g:
1669
1823
  # +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
1670
1824
  # +SAVEDATE+. The following are some common search criteria:
1671
1825
  #
@@ -1960,6 +2114,87 @@ module Net
1960
2114
  return thread_internal("UID THREAD", algorithm, search_keys, charset)
1961
2115
  end
1962
2116
 
2117
+ # Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1]
2118
+ # {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1]
2119
+ # to enable the specified server +capabilities+. Each capability may be an
2120
+ # array, string, or symbol. Returns a list of the capabilities that were
2121
+ # enabled.
2122
+ #
2123
+ # The +ENABLE+ command is only valid in the _authenticated_ state, before
2124
+ # any mailbox is selected.
2125
+ #
2126
+ # Related: #capable?, #capabilities, #capability
2127
+ #
2128
+ # ===== Capabilities
2129
+ #
2130
+ # The server's capabilities must include
2131
+ # +ENABLE+ [RFC5161[https://tools.ietf.org/html/rfc5161]]
2132
+ # or +IMAP4REV2+ [RFC9051[https://tools.ietf.org/html/rfc9051]].
2133
+ #
2134
+ # Additionally, the server capabilities must include a capability matching
2135
+ # each enabled extension (usually the same name as the enabled extension).
2136
+ # The following capabilities may be enabled:
2137
+ #
2138
+ # [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
2139
+ #
2140
+ # In a future release, <tt>enable(:utf8)</tt> will enable either
2141
+ # <tt>"UTF8=ACCEPT"</tt> or <tt>"IMAP4rev2"</tt>, depending on server
2142
+ # capabilities.
2143
+ #
2144
+ # [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2145
+ #
2146
+ # The server's capabilities must include <tt>UTF8=ACCEPT</tt> _or_
2147
+ # <tt>UTF8=ONLY</tt>.
2148
+ #
2149
+ # This allows the server to send strings encoded as UTF-8 which might
2150
+ # otherwise need to use a 7-bit encoding, such as {modified
2151
+ # UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for
2152
+ # message headers.
2153
+ #
2154
+ # *Note:* <em>A future update may set string encodings slightly
2155
+ # differently</em>, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8"
2156
+ # when it is. Currently, the encoding of strings sent as "quoted" or
2157
+ # "text" will _always_ be "UTF-8", even when only ASCII characters are
2158
+ # used (e.g. "Subject: Agenda") And currently, string "literals" sent
2159
+ # by the server will always have an "ASCII-8BIT" (binary)
2160
+ # encoding, even if they generally contain UTF-8 data, if they are
2161
+ # text at all.
2162
+ #
2163
+ # [<tt>"UTF8=ONLY"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2164
+ #
2165
+ # A server that reports the <tt>UTF8=ONLY</tt> capability _requires_ that
2166
+ # the client <tt>enable("UTF8=ACCEPT")</tt> before any mailboxes may be
2167
+ # selected. For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
2168
+ # <tt>enable("UTF8=ACCEPT")</tt>.
2169
+ #
2170
+ # ===== Unsupported capabilities
2171
+ #
2172
+ # *Note:* Some extensions that use ENABLE permit the server to send syntax
2173
+ # that Net::IMAP cannot parse, which may raise an exception and disconnect.
2174
+ # Some extensions may work, but the support may be incomplete, untested, or
2175
+ # experimental.
2176
+ #
2177
+ # Until a capability is documented here as supported, enabling it may result
2178
+ # in undocumented behavior and a future release may update with incompatible
2179
+ # behavior <em>without warning or deprecation</em>.
2180
+ #
2181
+ # <em>Caution is advised.</em>
2182
+ #
2183
+ def enable(*capabilities)
2184
+ capabilities = capabilities
2185
+ .flatten
2186
+ .map {|e| ENABLE_ALIASES[e] || e }
2187
+ .uniq
2188
+ .join(' ')
2189
+ synchronize do
2190
+ send_command("ENABLE #{capabilities}")
2191
+ result = clear_responses("ENABLED").last
2192
+ @utf8_strings ||= result.include? "UTF8=ACCEPT"
2193
+ @utf8_strings ||= result.include? "IMAP4REV2"
2194
+ result
2195
+ end
2196
+ end
2197
+
1963
2198
  # Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3]
1964
2199
  # {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13]
1965
2200
  # that waits for notifications of new or expunged messages. Yields
@@ -2024,6 +2259,104 @@ module Net
2024
2259
  end
2025
2260
  end
2026
2261
 
2262
+ # :call-seq:
2263
+ # responses {|hash| ...} -> block result
2264
+ # responses(type) {|array| ...} -> block result
2265
+ #
2266
+ # Yields unhandled responses and returns the result of the block.
2267
+ #
2268
+ # Unhandled responses are stored in a hash, with arrays of
2269
+ # <em>non-+nil+</em> UntaggedResponse#data keyed by UntaggedResponse#name
2270
+ # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
2271
+ # yield the entire responses hash. Call with +type+ to yield only the array
2272
+ # of responses for that type.
2273
+ #
2274
+ # For example:
2275
+ #
2276
+ # imap.select("inbox")
2277
+ # p imap.responses("EXISTS", &:last)
2278
+ # #=> 2
2279
+ # p imap.responses("UIDVALIDITY", &:last)
2280
+ # #=> 968263756
2281
+ #
2282
+ # >>>
2283
+ # *Note:* Access to the responses hash is synchronized for thread-safety.
2284
+ # The receiver thread and response_handlers cannot process new responses
2285
+ # until the block completes. Accessing either the response hash or its
2286
+ # response type arrays outside of the block is unsafe.
2287
+ #
2288
+ # Calling without a block is unsafe and deprecated. Future releases will
2289
+ # raise ArgumentError unless a block is given.
2290
+ #
2291
+ # Previously unhandled responses are automatically cleared before entering a
2292
+ # mailbox with #select or #examine. Long-lived connections can receive many
2293
+ # unhandled server responses, which must be pruned or they will continually
2294
+ # consume more memory. Update or clear the responses hash or arrays inside
2295
+ # the block, or use #clear_responses.
2296
+ #
2297
+ # Only non-+nil+ data is stored. Many important response codes have no data
2298
+ # of their own, but are used as "tags" on the ResponseText object they are
2299
+ # attached to. ResponseText will be accessible by its response types:
2300
+ # "+OK+", "+NO+", "+BAD+", "+BYE+", or "+PREAUTH+".
2301
+ #
2302
+ # TaggedResponse#data is not saved to #responses, nor is any
2303
+ # ResponseCode#data on tagged responses. Although some command methods do
2304
+ # return the TaggedResponse directly, #add_response_handler must be used to
2305
+ # handle all response codes.
2306
+ #
2307
+ # Related: #clear_responses, #response_handlers, #greeting
2308
+ def responses(type = nil)
2309
+ if block_given?
2310
+ synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
2311
+ elsif type
2312
+ raise ArgumentError, "Pass a block or use #clear_responses"
2313
+ else
2314
+ # warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1)
2315
+ @responses
2316
+ end
2317
+ end
2318
+
2319
+ # :call-seq:
2320
+ # clear_responses -> hash
2321
+ # clear_responses(type) -> array
2322
+ #
2323
+ # Clears and returns the unhandled #responses hash or the unhandled
2324
+ # responses array for a single response +type+.
2325
+ #
2326
+ # Clearing responses is synchronized with other threads. The lock is
2327
+ # released before returning.
2328
+ #
2329
+ # Related: #responses, #response_handlers
2330
+ def clear_responses(type = nil)
2331
+ synchronize {
2332
+ if type
2333
+ @responses.delete(type) || []
2334
+ else
2335
+ @responses.dup.transform_values(&:freeze)
2336
+ .tap { _1.default = [].freeze }
2337
+ .tap { @responses.clear }
2338
+ end
2339
+ }
2340
+ .freeze
2341
+ end
2342
+
2343
+ # Returns all response handlers, including those that are added internally
2344
+ # by commands. Each response handler will be called with every new
2345
+ # UntaggedResponse, TaggedResponse, and ContinuationRequest.
2346
+ #
2347
+ # Response handlers are called with a mutex inside the receiver thread. New
2348
+ # responses cannot be processed and commands from other threads must wait
2349
+ # until all response_handlers return. An exception will shut-down the
2350
+ # receiver thread and close the connection.
2351
+ #
2352
+ # For thread-safety, the returned array is a frozen copy of the internal
2353
+ # array.
2354
+ #
2355
+ # Related: #add_response_handler, #remove_response_handler
2356
+ def response_handlers
2357
+ synchronize { @response_handlers.clone.freeze }
2358
+ end
2359
+
2027
2360
  # Adds a response handler. For example, to detect when
2028
2361
  # the server sends a new EXISTS response (which normally
2029
2362
  # indicates new messages being added to the mailbox),
@@ -2036,19 +2369,21 @@ module Net
2036
2369
  # end
2037
2370
  # }
2038
2371
  #
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
2372
  # Related: #remove_response_handler, #response_handlers
2044
2373
  def add_response_handler(handler = nil, &block)
2045
2374
  raise ArgumentError, "two Procs are passed" if handler && block
2046
- @response_handlers.push(block || handler)
2375
+ synchronize do
2376
+ @response_handlers.push(block || handler)
2377
+ end
2047
2378
  end
2048
2379
 
2049
2380
  # Removes the response handler.
2381
+ #
2382
+ # Related: #add_response_handler, #response_handlers
2050
2383
  def remove_response_handler(handler)
2051
- @response_handlers.delete(handler)
2384
+ synchronize do
2385
+ @response_handlers.delete(handler)
2386
+ end
2052
2387
  end
2053
2388
 
2054
2389
  private
@@ -2059,104 +2394,29 @@ module Net
2059
2394
 
2060
2395
  @@debug = false
2061
2396
 
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
2397
+ def start_imap_connection
2398
+ @greeting = get_server_greeting
2399
+ @capabilities = capabilities_from_resp_code @greeting
2400
+ @receiver_thread = start_receiver_thread
2401
+ rescue Exception
2402
+ @sock.close
2403
+ raise
2404
+ end
2405
+
2406
+ def get_server_greeting
2407
+ greeting = get_response
2408
+ raise Error, "No server greeting - connection closed" unless greeting
2409
+ record_untagged_response_code greeting
2410
+ raise ByeResponseError, greeting if greeting.name == "BYE"
2411
+ greeting
2412
+ end
2413
+
2414
+ def start_receiver_thread
2415
+ Thread.start do
2416
+ receive_responses
2417
+ rescue Exception => ex
2418
+ @receiver_thread_exception = ex
2419
+ # don't exit the thread with an exception
2160
2420
  end
2161
2421
  end
2162
2422
 
@@ -2205,11 +2465,7 @@ module Net
2205
2465
  @continuation_request_arrival.signal
2206
2466
  end
2207
2467
  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
2468
+ record_untagged_response(resp)
2213
2469
  if resp.name == "BYE" && @logout_command_tag.nil?
2214
2470
  @sock.close
2215
2471
  @exception = ByeResponseError.new(resp)
@@ -2268,19 +2524,61 @@ module Net
2268
2524
  end
2269
2525
 
2270
2526
  def get_response
2271
- buff = @reader.read_response_buffer
2527
+ buff = String.new
2528
+ while true
2529
+ s = @sock.gets(CRLF)
2530
+ break unless s
2531
+ buff.concat(s)
2532
+ if /\{(\d+)\}\r\n/n =~ s
2533
+ s = @sock.read($1.to_i)
2534
+ buff.concat(s)
2535
+ else
2536
+ break
2537
+ end
2538
+ end
2272
2539
  return nil if buff.length == 0
2273
- $stderr.print(buff.gsub(/^/n, "S: ")) if @@debug
2274
- @parser.parse(buff)
2540
+ if @@debug
2541
+ $stderr.print(buff.gsub(/^/n, "S: "))
2542
+ end
2543
+ return @parser.parse(buff)
2275
2544
  end
2276
2545
 
2277
2546
  #############################
2547
+ # built-in response handlers
2278
2548
 
2279
- def record_response(name, data)
2280
- unless @responses.has_key?(name)
2281
- @responses[name] = []
2549
+ # store name => [..., data]
2550
+ def record_untagged_response(resp)
2551
+ @responses[resp.name] << resp.data
2552
+ record_untagged_response_code resp
2553
+ end
2554
+
2555
+ # store code.name => [..., code.data]
2556
+ def record_untagged_response_code(resp)
2557
+ return unless resp.data.is_a?(ResponseText)
2558
+ return unless (code = resp.data.code)
2559
+ @responses[code.name] << code.data
2560
+ end
2561
+
2562
+ # NOTE: only call this for greeting, login, and authenticate
2563
+ def capabilities_from_resp_code(resp)
2564
+ return unless %w[PREAUTH OK].any? { _1.casecmp? resp.name }
2565
+ return unless (code = resp.data.code)
2566
+ return unless code.name.casecmp?("CAPABILITY")
2567
+ code.data.freeze
2568
+ end
2569
+
2570
+ #############################
2571
+
2572
+ # Calls send_command, yielding the text of each ContinuationRequest and
2573
+ # responding with each block result. Returns TaggedResponse. Raises
2574
+ # NoResponseError or BadResponseError.
2575
+ def send_command_with_continuations(cmd, *args)
2576
+ send_command(cmd, *args) do |server_response|
2577
+ if server_response.instance_of?(ContinuationRequest)
2578
+ client_response = yield server_response.data.text
2579
+ put_string(client_response + CRLF)
2580
+ end
2282
2581
  end
2283
- @responses[name].push(data)
2284
2582
  end
2285
2583
 
2286
2584
  def send_command(cmd, *args, &block)
@@ -2322,8 +2620,8 @@ module Net
2322
2620
  if @debug_output_bol
2323
2621
  $stderr.print("C: ")
2324
2622
  end
2325
- $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
2326
- if /\r\n\z/n.match(str)
2623
+ $stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " })
2624
+ if /\n\z/n.match(str)
2327
2625
  @debug_output_bol = true
2328
2626
  else
2329
2627
  @debug_output_bol = false
@@ -2343,7 +2641,7 @@ module Net
2343
2641
  else
2344
2642
  send_command(cmd, *keys)
2345
2643
  end
2346
- return @responses.delete("SEARCH")[-1]
2644
+ clear_responses("SEARCH").last
2347
2645
  end
2348
2646
  end
2349
2647
 
@@ -2358,13 +2656,13 @@ module Net
2358
2656
  end
2359
2657
 
2360
2658
  synchronize do
2361
- @responses.delete("FETCH")
2659
+ clear_responses("FETCH")
2362
2660
  if mod
2363
2661
  send_command(cmd, MessageSet.new(set), attr, mod)
2364
2662
  else
2365
2663
  send_command(cmd, MessageSet.new(set), attr)
2366
2664
  end
2367
- return @responses.delete("FETCH")
2665
+ clear_responses("FETCH")
2368
2666
  end
2369
2667
  end
2370
2668
 
@@ -2373,9 +2671,9 @@ module Net
2373
2671
  attr = RawData.new(attr)
2374
2672
  end
2375
2673
  synchronize do
2376
- @responses.delete("FETCH")
2674
+ clear_responses("FETCH")
2377
2675
  send_command(cmd, MessageSet.new(set), attr, flags)
2378
- return @responses.delete("FETCH")
2676
+ clear_responses("FETCH")
2379
2677
  end
2380
2678
  end
2381
2679
 
@@ -2392,7 +2690,7 @@ module Net
2392
2690
  normalize_searching_criteria(search_keys)
2393
2691
  synchronize do
2394
2692
  send_command(cmd, sort_keys, charset, *search_keys)
2395
- return @responses.delete("SORT")[-1]
2693
+ clear_responses("SORT").last
2396
2694
  end
2397
2695
  end
2398
2696
 
@@ -2403,8 +2701,10 @@ module Net
2403
2701
  normalize_searching_criteria(search_keys)
2404
2702
  end
2405
2703
  normalize_searching_criteria(search_keys)
2406
- send_command(cmd, algorithm, charset, *search_keys)
2407
- return @responses.delete("THREAD")[-1]
2704
+ synchronize do
2705
+ send_command(cmd, algorithm, charset, *search_keys)
2706
+ clear_responses("THREAD").last
2707
+ end
2408
2708
  end
2409
2709
 
2410
2710
  def normalize_searching_criteria(keys)
@@ -2418,50 +2718,45 @@ module Net
2418
2718
  end
2419
2719
  end
2420
2720
 
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
2721
+ def build_ssl_ctx(ssl)
2722
+ if ssl
2723
+ params = (Hash.try_convert(ssl) || {}).freeze
2724
+ context = SSLContext.new
2725
+ context.set_params(params)
2726
+ if defined?(VerifyCallbackProc)
2727
+ context.verify_callback = VerifyCallbackProc
2428
2728
  end
2429
- end
2430
- if verify
2431
- params[:verify_mode] = VERIFY_PEER
2729
+ context.freeze
2730
+ [params, context]
2432
2731
  else
2433
- params[:verify_mode] = VERIFY_NONE
2732
+ false
2434
2733
  end
2435
- return params
2436
2734
  end
2437
2735
 
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
2454
- end
2455
- @sock = SSLSocket.new(@sock, context)
2456
- @reader = ResponseReader.new(self, @sock)
2736
+ def start_tls_session
2737
+ raise "SSL extension not installed" unless defined?(OpenSSL::SSL)
2738
+ raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
2739
+ raise "cannot start TLS without SSLContext" unless ssl_ctx
2740
+ @sock = SSLSocket.new(@sock, ssl_ctx)
2457
2741
  @sock.sync_close = true
2458
2742
  @sock.hostname = @host if @sock.respond_to? :hostname=
2459
2743
  ssl_socket_connect(@sock, @open_timeout)
2460
- if context.verify_mode != VERIFY_NONE
2744
+ if ssl_ctx.verify_mode != VERIFY_NONE
2461
2745
  @sock.post_connection_check(@host)
2746
+ @tls_verified = true
2462
2747
  end
2463
2748
  end
2464
2749
 
2750
+ #--
2751
+ # We could get the saslprep method by extending the SASLprep module
2752
+ # directly. It's done indirectly, so SASLprep can be lazily autoloaded,
2753
+ # because most users won't need it.
2754
+ #++
2755
+ # Delegates to Net::IMAP::StringPrep::SASLprep#saslprep.
2756
+ def self.saslprep(string, **opts)
2757
+ Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
2758
+ end
2759
+
2465
2760
  end
2466
2761
  end
2467
2762
 
@@ -2472,4 +2767,6 @@ require_relative "imap/flags"
2472
2767
  require_relative "imap/response_data"
2473
2768
  require_relative "imap/response_parser"
2474
2769
  require_relative "imap/authenticators"
2475
- require_relative "imap/sasl"
2770
+
2771
+ require_relative "imap/deprecated_client_options"
2772
+ Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions