net-imap 0.3.7 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) 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/response_data.rb +46 -41
  14. data/lib/net/imap/response_parser/parser_utils.rb +230 -0
  15. data/lib/net/imap/response_parser.rb +665 -627
  16. data/lib/net/imap/sasl/anonymous_authenticator.rb +68 -0
  17. data/lib/net/imap/sasl/authenticators.rb +112 -0
  18. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +15 -9
  19. data/lib/net/imap/{authenticators/digest_md5.rb → sasl/digest_md5_authenticator.rb} +74 -21
  20. data/lib/net/imap/sasl/external_authenticator.rb +62 -0
  21. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  22. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +19 -14
  23. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +164 -0
  24. data/lib/net/imap/sasl/plain_authenticator.rb +93 -0
  25. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  26. data/lib/net/imap/sasl/scram_authenticator.rb +278 -0
  27. data/lib/net/imap/sasl/stringprep.rb +6 -66
  28. data/lib/net/imap/sasl/xoauth2_authenticator.rb +88 -0
  29. data/lib/net/imap/sasl.rb +139 -44
  30. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  31. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  32. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  33. data/lib/net/imap/stringprep/tables.rb +146 -0
  34. data/lib/net/imap/stringprep/trace.rb +85 -0
  35. data/lib/net/imap/stringprep.rb +159 -0
  36. data/lib/net/imap.rb +967 -588
  37. data/net-imap.gemspec +1 -1
  38. data/rakelib/saslprep.rake +4 -4
  39. data/rakelib/string_prep_tables_generator.rb +82 -60
  40. metadata +26 -11
  41. data/lib/net/imap/authenticators/plain.rb +0 -41
  42. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  43. data/lib/net/imap/sasl/saslprep.rb +0 -55
  44. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  45. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
data/lib/net/imap.rb CHANGED
@@ -24,11 +24,9 @@ end
24
24
  module Net
25
25
 
26
26
  # Net::IMAP implements Internet Message Access Protocol (\IMAP) client
27
- # functionality. The protocol is described in
28
- # [IMAP4rev1[https://tools.ietf.org/html/rfc3501]].
29
- #--
30
- # TODO: and [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
31
- #++
27
+ # functionality. The protocol is described
28
+ # in {IMAP4rev1 [RFC3501]}[https://tools.ietf.org/html/rfc3501]
29
+ # and {IMAP4rev2 [RFC9051]}[https://tools.ietf.org/html/rfc9051].
32
30
  #
33
31
  # == \IMAP Overview
34
32
  #
@@ -77,31 +75,22 @@ module Net
77
75
  # UIDs have to be reassigned. An \IMAP client thus cannot
78
76
  # rearrange message orders.
79
77
  #
80
- # === Server capabilities and protocol extensions
78
+ # === Examples of Usage
81
79
  #
82
- # Net::IMAP <em>does not modify its behavior</em> according to server
83
- # #capability. Users of the class must check for required capabilities before
84
- # issuing commands. Special care should be taken to follow all #capability
85
- # requirements for #starttls, #login, and #authenticate.
86
- #
87
- # See the #capability method for more information.
88
- #
89
- # == Examples of Usage
90
- #
91
- # === List sender and subject of all recent messages in the default mailbox
80
+ # ==== List sender and subject of all recent messages in the default mailbox
92
81
  #
93
82
  # imap = Net::IMAP.new('mail.example.com')
94
- # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
83
+ # imap.authenticate('PLAIN', 'joe_user', 'joes_password')
95
84
  # imap.examine('INBOX')
96
85
  # imap.search(["RECENT"]).each do |message_id|
97
86
  # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
98
87
  # puts "#{envelope.from[0].name}: \t#{envelope.subject}"
99
88
  # end
100
89
  #
101
- # === Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
90
+ # ==== Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
102
91
  #
103
92
  # imap = Net::IMAP.new('mail.example.com')
104
- # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
93
+ # imap.authenticate('PLAIN', 'joe_user', 'joes_password')
105
94
  # imap.select('Mail/sent-mail')
106
95
  # if not imap.list('Mail/', 'sent-apr03')
107
96
  # imap.create('Mail/sent-apr03')
@@ -112,12 +101,96 @@ module Net
112
101
  # end
113
102
  # imap.expunge
114
103
  #
104
+ # == Capabilities
105
+ #
106
+ # Most Net::IMAP methods do not _currently_ modify their behaviour according
107
+ # to the server's advertised #capabilities. Users of this class must check
108
+ # that the server is capable of extension commands or command arguments before
109
+ # sending them. Special care should be taken to follow the #capabilities
110
+ # requirements for #starttls, #login, and #authenticate.
111
+ #
112
+ # See #capable?, #auth_capable?, #capabilities, #auth_mechanisms to discover
113
+ # server capabilities. For relevant capability requirements, see the
114
+ # documentation on each \IMAP command.
115
+ #
116
+ # imap = Net::IMAP.new("mail.example.com")
117
+ # imap.capable?(:IMAP4rev1) or raise "Not an IMAP4rev1 server"
118
+ # imap.capable?(:starttls) or raise "Cannot start TLS"
119
+ # imap.starttls
120
+ #
121
+ # if imap.auth_capable?("PLAIN")
122
+ # imap.authenticate "PLAIN", username, password
123
+ # elsif !imap.capability?("LOGINDISABLED")
124
+ # imap.login username, password
125
+ # else
126
+ # raise "No acceptable authentication mechanisms"
127
+ # end
128
+ #
129
+ # # Support for "UTF8=ACCEPT" implies support for "ENABLE"
130
+ # imap.enable :utf8 if imap.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
+ #
115
188
  # == Thread Safety
116
189
  #
117
190
  # Net::IMAP supports concurrent threads. For example,
118
191
  #
119
192
  # imap = Net::IMAP.new("imap.foo.net", "imap2")
120
- # imap.authenticate("cram-md5", "bar", "password")
193
+ # imap.authenticate("scram-md5", "bar", "password")
121
194
  # imap.select("inbox")
122
195
  # fetch_thread = Thread.start { imap.fetch(1..-1, "UID") }
123
196
  # search_result = imap.search(["BODY", "hello"])
@@ -173,24 +246,54 @@ module Net
173
246
  # == What's here?
174
247
  #
175
248
  # * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods]
176
- # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
177
- # * {...for any state}[rdoc-ref:Net::IMAP@IMAP+commands+for+any+state]
178
- # * {...for the "not authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Not+Authenticated-22+state]
179
- # * {...for the "authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Authenticated-22+state]
180
- # * {...for the "selected" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Selected-22+state]
181
- # * {...for the "logout" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Logout-22+state]
182
- # * {Supported IMAP extensions}[rdoc-ref:Net::IMAP@Supported+IMAP+extensions]
249
+ # * {Server capabilities}[rdoc-ref:Net::IMAP@Server+capabilities]
183
250
  # * {Handling server responses}[rdoc-ref:Net::IMAP@Handling+server+responses]
251
+ # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
252
+ # * {for any state}[rdoc-ref:Net::IMAP@Any+state]
253
+ # * {for the "not authenticated" state}[rdoc-ref:Net::IMAP@Not+Authenticated+state]
254
+ # * {for the "authenticated" state}[rdoc-ref:Net::IMAP@Authenticated+state]
255
+ # * {for the "selected" state}[rdoc-ref:Net::IMAP@Selected+state]
256
+ # * {for the "logout" state}[rdoc-ref:Net::IMAP@Logout+state]
257
+ # * {IMAP extension support}[rdoc-ref:Net::IMAP@IMAP+extension+support]
184
258
  #
185
259
  # === Connection control methods
186
260
  #
187
- # - Net::IMAP.new: A new client connects immediately and waits for a
188
- # successful server greeting before returning the new client object.
261
+ # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
262
+ # waits for a successful server greeting before the method returns.
189
263
  # - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
190
264
  # - #logout: Tells the server to end the session. Enters the "_logout_" state.
191
265
  # - #disconnect: Disconnects the connection (without sending #logout first).
192
266
  # - #disconnected?: True if the connection has been closed.
193
267
  #
268
+ # === Server capabilities
269
+ #
270
+ # - #capable?: Returns whether the server supports a given capability.
271
+ # - #capabilities: Returns the server's capabilities as an array of strings.
272
+ # - #auth_capable?: Returns whether the server advertises support for a given
273
+ # SASL mechanism, for use with #authenticate.
274
+ # - #auth_mechanisms: Returns the #authenticate SASL mechanisms which
275
+ # the server claims to support as an array of strings.
276
+ # - #clear_cached_capabilities: Clears cached capabilities.
277
+ #
278
+ # <em>The capabilities cache is automatically cleared after completing
279
+ # #starttls, #login, or #authenticate.</em>
280
+ # - #capability: Sends the +CAPABILITY+ command and returns the #capabilities.
281
+ #
282
+ # <em>In general, #capable? should be used rather than explicitly sending a
283
+ # +CAPABILITY+ command to the server.</em>
284
+ #
285
+ # === Handling server responses
286
+ #
287
+ # - #greeting: The server's initial untagged response, which can indicate a
288
+ # pre-authenticated connection.
289
+ # - #responses: Yields unhandled UntaggedResponse#data and <em>non-+nil+</em>
290
+ # ResponseCode#data.
291
+ # - #clear_responses: Deletes unhandled data from #responses and returns it.
292
+ # - #add_response_handler: Add a block to be called inside the receiver thread
293
+ # with every server response.
294
+ # - #response_handlers: Returns the list of response handlers.
295
+ # - #remove_response_handler: Remove a previously added response handler.
296
+ #
194
297
  # === Core \IMAP commands
195
298
  #
196
299
  # The following commands are defined either by
@@ -199,69 +302,48 @@ module Net
199
302
  # [IDLE[https://tools.ietf.org/html/rfc2177]],
200
303
  # [NAMESPACE[https://tools.ietf.org/html/rfc2342]],
201
304
  # [UNSELECT[https://tools.ietf.org/html/rfc3691]],
202
- #--
203
- # TODO: [ENABLE[https://tools.ietf.org/html/rfc5161]],
204
- # TODO: [LIST-EXTENDED[https://tools.ietf.org/html/rfc5258]],
205
- # TODO: [LIST-STATUS[https://tools.ietf.org/html/rfc5819]],
206
- #++
305
+ # [ENABLE[https://tools.ietf.org/html/rfc5161]],
207
306
  # [MOVE[https://tools.ietf.org/html/rfc6851]].
208
307
  # These extensions are widely supported by modern IMAP4rev1 servers and have
209
308
  # all been integrated into [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
210
- # <em>Note: Net::IMAP doesn't fully support IMAP4rev2 yet.</em>
211
- #
212
- #--
213
- # TODO: When IMAP4rev2 is supported, add the following to the each of the
214
- # appropriate commands below.
215
- # Note:: CHECK has been removed from IMAP4rev2.
216
- # Note:: LSUB is obsoleted by +LIST-EXTENDED and has been removed from IMAP4rev2.
217
- # <em>Some arguments require the +LIST-EXTENDED+ or +IMAP4rev2+ capability.</em>
218
- # <em>Requires either the +ENABLE+ or +IMAP4rev2+ capability.</em>
219
- # <em>Requires either the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
220
- # <em>Requires either the +IDLE+ or +IMAP4rev2+ capability.</em>
221
- # <em>Requires either the +UNSELECT+ or +IMAP4rev2+ capability.</em>
222
- # <em>Requires either the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
223
- # <em>Requires either the +MOVE+ or +IMAP4rev2+ capability.</em>
224
- #++
225
- #
226
- # ==== \IMAP commands for any state
309
+ # <em>*NOTE:* Net::IMAP doesn't support IMAP4rev2 yet.</em>
310
+ #
311
+ # ==== Any state
227
312
  #
228
313
  # - #capability: Returns the server's capabilities as an array of strings.
229
314
  #
230
- # <em>Capabilities may change after</em> #starttls, #authenticate, or #login
231
- # <em>and cached capabilities must be reloaded.</em>
315
+ # <em>In general, #capable? should be used rather than explicitly sending a
316
+ # +CAPABILITY+ command to the server.</em>
232
317
  # - #noop: Allows the server to send unsolicited untagged #responses.
233
318
  # - #logout: Tells the server to end the session. Enters the "_logout_" state.
234
319
  #
235
- # ==== \IMAP commands for the "Not Authenticated" state
320
+ # ==== Not Authenticated state
236
321
  #
237
- # In addition to the universal commands, the following commands are valid in
238
- # the "<em>not authenticated</em>" state:
322
+ # In addition to the commands for any state, the following commands are valid
323
+ # in the "<em>not authenticated</em>" state:
239
324
  #
240
325
  # - #starttls: Upgrades a clear-text connection to use TLS.
241
326
  #
242
327
  # <em>Requires the +STARTTLS+ capability.</em>
243
- # - #authenticate: Identifies the client to the server using a {SASL
244
- # mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml].
245
- # Enters the "_authenticated_" state.
328
+ # - #authenticate: Identifies the client to the server using the given
329
+ # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
330
+ # and credentials. Enters the "_authenticated_" state.
246
331
  #
247
- # <em>Requires the <tt>AUTH=#{mechanism}</tt> capability for the chosen
248
- # mechanism.</em>
332
+ # <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
333
+ # supported mechanisms.</em>
249
334
  # - #login: Identifies the client to the server using a plain text password.
250
335
  # Using #authenticate is generally preferred. Enters the "_authenticated_"
251
336
  # state.
252
337
  #
253
338
  # <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
254
339
  #
255
- # ==== \IMAP commands for the "Authenticated" state
256
- #
257
- # In addition to the universal commands, the following commands are valid in
258
- # the "_authenticated_" state:
340
+ # ==== Authenticated state
259
341
  #
260
- #--
261
- # - #enable: <em>Not implemented by Net::IMAP, yet.</em>
342
+ # In addition to the commands for any state, the following commands are valid
343
+ # in the "_authenticated_" state:
262
344
  #
263
- # <em>Requires the +ENABLE+ capability.</em>
264
- #++
345
+ # - #enable: Enables backwards incompatible server extensions.
346
+ # <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
265
347
  # - #select: Open a mailbox and enter the "_selected_" state.
266
348
  # - #examine: Open a mailbox read-only, and enter the "_selected_" state.
267
349
  # - #create: Creates a new mailbox.
@@ -271,37 +353,31 @@ module Net
271
353
  # - #unsubscribe: Removes a mailbox from the "subscribed" set.
272
354
  # - #list: Returns names and attributes of mailboxes matching a given pattern.
273
355
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
274
- #
275
- # <em>Requires the +NAMESPACE+ capability.</em>
356
+ # <em>Requires the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
276
357
  # - #status: Returns mailbox information, e.g. message count, unseen message
277
358
  # count, +UIDVALIDITY+ and +UIDNEXT+.
278
359
  # - #append: Appends a message to the end of a mailbox.
279
360
  # - #idle: Allows the server to send updates to the client, without the client
280
361
  # needing to poll using #noop.
362
+ # <em>Requires the +IDLE+ or +IMAP4rev2+ capability.</em>
363
+ # - *Obsolete* #lsub: <em>Replaced by <tt>LIST-EXTENDED</tt> and removed from
364
+ # +IMAP4rev2+.</em> Lists mailboxes in the "subscribed" set.
281
365
  #
282
- # <em>Requires the +IDLE+ capability.</em>
283
- # - #lsub: Lists mailboxes the user has declared "active" or "subscribed".
284
- #--
285
- # <em>Replaced by</em> <tt>LIST-EXTENDED</tt> <em>and removed from</em>
286
- # +IMAP4rev2+. <em>However, Net::IMAP hasn't implemented</em>
287
- # <tt>LIST-EXTENDED</tt> _yet_.
288
- #++
366
+ # <em>*Note:* Net::IMAP hasn't implemented <tt>LIST-EXTENDED</tt> yet.</em>
289
367
  #
290
- # ==== \IMAP commands for the "Selected" state
368
+ # ==== Selected state
291
369
  #
292
- # In addition to the universal commands and the "authenticated" commands, the
293
- # following commands are valid in the "_selected_" state:
370
+ # In addition to the commands for any state and the "_authenticated_"
371
+ # commands, the following commands are valid in the "_selected_" state:
294
372
  #
295
373
  # - #close: Closes the mailbox and returns to the "_authenticated_" state,
296
374
  # expunging deleted messages, unless the mailbox was opened as read-only.
297
375
  # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
298
376
  # without expunging any messages.
299
- #
300
- # <em>Requires the +UNSELECT+ capability.</em>
377
+ # <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
301
378
  # - #expunge: Permanently removes messages which have the Deleted flag set.
302
- # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
303
- #
304
- # <em>Requires the +UIDPLUS+ capability.</em>
379
+ # - #uid_expunge: Restricts expunge to only remove the specified UIDs.
380
+ # <em>Requires the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
305
381
  # - #search, #uid_search: Returns sequence numbers or UIDs of messages that
306
382
  # match the given searching criteria.
307
383
  # - #fetch, #uid_fetch: Returns data associated with a set of messages,
@@ -311,45 +387,35 @@ module Net
311
387
  # specified destination mailbox.
312
388
  # - #move, #uid_move: Moves the specified messages to the end of the
313
389
  # specified destination mailbox, expunging them from the current mailbox.
390
+ # <em>Requires the +MOVE+ or +IMAP4rev2+ capability.</em>
391
+ # - #check: <em>*Obsolete:* removed from +IMAP4rev2+.</em>
392
+ # Can be replaced with #noop or #idle.
314
393
  #
315
- # <em>Requires the +MOVE+ capability.</em>
316
- # - #check: Mostly obsolete. Can be replaced with #noop or #idle.
317
- #--
318
- # <em>Removed from IMAP4rev2.</em>
319
- #++
320
- #
321
- # ==== \IMAP commands for the "Logout" state
394
+ # ==== Logout state
322
395
  #
323
- # No \IMAP commands are valid in the +logout+ state. If the socket is still
396
+ # No \IMAP commands are valid in the "_logout_" state. If the socket is still
324
397
  # open, Net::IMAP will close it after receiving server confirmation.
325
398
  # Exceptions will be raised by \IMAP commands that have already started and
326
399
  # are waiting for a response, as well as any that are called after logout.
327
400
  #
328
- # === Supported \IMAP extensions
401
+ # === \IMAP extension support
329
402
  #
330
403
  # ==== RFC9051: +IMAP4rev2+
331
404
  #
332
- # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is <em>not supported
333
- # yet</em>, Net::IMAP supports several extensions that have been folded into
334
- # it: +IDLE+, +MOVE+, +NAMESPACE+, +UIDPLUS+, and +UNSELECT+.
335
- #--
336
- # TODO: RFC4466, ABNF extensions (automatic support for other extensions)
337
- # TODO: +ESEARCH+, ExtendedSearchData
338
- # TODO: +SEARCHRES+,
339
- # TODO: +ENABLE+,
340
- # TODO: +SASL-IR+,
341
- # TODO: +LIST-EXTENDED+,
342
- # TODO: +LIST-STATUS+,
343
- # TODO: +LITERAL-+,
344
- # TODO: +BINARY+ (only the FETCH side)
345
- # TODO: +SPECIAL-USE+
346
- # implicitly supported, but we can do better: Response codes: RFC5530, etc
347
- # implicitly supported, but we can do better: <tt>STATUS=SIZE</tt>
348
- # implicitly supported, but we can do better: <tt>STATUS DELETED</tt>
349
- #++
350
- # Commands for these extensions are included with the {Core IMAP
351
- # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above. Other supported
352
- # extensons are listed below.
405
+ # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is not supported
406
+ # yet, Net::IMAP supports several extensions that have been folded into it:
407
+ # +ENABLE+, +IDLE+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, 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>
353
419
  #
354
420
  # ==== RFC2087: +QUOTA+
355
421
  # - #getquota: returns the resource usage and limits for a quota root
@@ -358,92 +424,47 @@ module Net
358
424
  # - #setquota: sets the resource limits for a given quota root.
359
425
  #
360
426
  # ==== RFC2177: +IDLE+
361
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
362
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
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].
363
429
  # - #idle: Allows the server to send updates to the client, without the client
364
430
  # needing to poll using #noop.
365
431
  #
366
432
  # ==== RFC2342: +NAMESPACE+
367
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
368
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
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].
369
435
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
370
436
  #
371
437
  # ==== RFC2971: +ID+
372
438
  # - #id: exchanges client and server implementation information.
373
439
  #
374
- #--
375
- # ==== RFC3502: +MULTIAPPEND+
376
- # TODO...
377
- #++
378
- #
379
- #--
380
- # ==== RFC3516: +BINARY+
381
- # TODO...
382
- #++
383
- #
384
440
  # ==== RFC3691: +UNSELECT+
385
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
386
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
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].
387
443
  # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
388
444
  # without expunging any messages.
389
445
  #
390
446
  # ==== RFC4314: +ACL+
391
447
  # - #getacl: lists the authenticated user's access rights to a mailbox.
392
448
  # - #setacl: sets the access rights for a user on a mailbox
393
- #--
394
- # TODO: #deleteacl, #listrights, #myrights
395
- #++
396
- # - *_Note:_* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
449
+ # >>>
450
+ # *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
397
451
  #
398
452
  # ==== RFC4315: +UIDPLUS+
399
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
400
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
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].
401
455
  # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
402
456
  # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode
403
457
  # - Updates #append with the +APPENDUID+ ResponseCode
404
458
  # - Updates #copy, #move with the +COPYUID+ ResponseCode
405
459
  #
406
- #--
407
- # ==== RFC4466: Collected Extensions to IMAP4 ABNF
408
- # TODO...
409
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this RFC updates
410
- # the protocol to enable new optional parameters to many commands: #select,
411
- # #examine, #create, #rename, #fetch, #uid_fetch, #store, #uid_store, #search,
412
- # #uid_search, and #append. However, specific parameters are not defined.
413
- # Extensions to these commands use this syntax whenever possible. Net::IMAP
414
- # may be partially compatible with extensions to these commands, even without
415
- # any explicit support.
416
- #++
417
- #
418
- #--
419
- # ==== RFC4731 +ESEARCH+
420
- # TODO...
421
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
422
- # - Updates #search, #uid_search to accept result options: +MIN+, +MAX+,
423
- # +ALL+, +COUNT+, and to return ExtendedSearchData.
424
- #++
425
- #
426
- #--
427
460
  # ==== RFC4959: +SASL-IR+
428
- # TODO...
429
461
  # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
430
- # - Updates #authenticate to reduce round-trips for supporting mechanisms.
431
- #++
432
- #
433
- #--
434
- # ==== RFC4978: COMPRESS=DEFLATE
435
- # TODO...
436
- #++
462
+ # - Updates #authenticate with the option to send an initial response.
437
463
  #
438
- #--
439
- # ==== RFC5182 +SEARCHRES+
440
- # TODO...
441
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
442
- # - Updates #search, #uid_search with the +SAVE+ result option.
443
- # - Updates #copy, #uid_copy, #fetch, #uid_fetch, #move, #uid_move, #search,
444
- # #uid_search, #store, #uid_store, and #uid_expunge with ability to
445
- # reference the saved result of a previous #search or #uid_search command.
446
- #++
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.
447
468
  #
448
469
  # ==== RFC5256: +SORT+
449
470
  # - #sort, #uid_sort: An alternate version of #search or #uid_search which
@@ -453,75 +474,20 @@ module Net
453
474
  # which arranges the results into ordered groups or threads according to a
454
475
  # chosen algorithm.
455
476
  #
456
- #--
457
- # ==== RFC5258 +LIST-EXTENDED+
458
- # TODO...
459
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this updates the
460
- # protocol with new optional parameters to the #list command, adding a few of
461
- # its own. Net::IMAP may be forward-compatible with future #list extensions,
462
- # even without any explicit support.
463
- # - Updates #list to accept selection options: +SUBSCRIBED+, +REMOTE+, and
464
- # +RECURSIVEMATCH+, and return options: +SUBSCRIBED+ and +CHILDREN+.
465
- #++
466
- #
467
- #--
468
- # ==== RFC5819 +LIST-STATUS+
469
- # TODO...
470
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
471
- # - Updates #list with +STATUS+ return option.
472
- #++
473
- #
474
477
  # ==== +XLIST+ (non-standard, deprecated)
475
478
  # - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses.
476
479
  #
477
- #--
478
- # ==== RFC6154 +SPECIAL-USE+
479
- # TODO...
480
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
481
- # - Updates #list with the +SPECIAL-USE+ selection and return options.
482
- #++
483
- #
484
480
  # ==== RFC6851: +MOVE+
485
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
486
- # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
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].
487
483
  # - #move, #uid_move: Moves the specified messages to the end of the
488
484
  # specified destination mailbox, expunging them from the current mailbox.
489
485
  #
490
- #--
491
- # ==== RFC6855: UTF8=ACCEPT
492
- # TODO...
493
- # ==== RFC6855: UTF8=ONLY
494
- # TODO...
495
- #++
496
- #
497
- #--
498
- # ==== RFC7888: <tt>LITERAL+</tt>, +LITERAL-+
499
- # TODO...
500
- # ==== RFC7162: +QRESYNC+
501
- # TODO...
502
- # ==== RFC7162: +CONDSTORE+
503
- # TODO...
504
- # ==== RFC8474: +OBJECTID+
505
- # TODO...
506
- # ==== RFC9208: +QUOTA+
507
- # TODO...
508
- #++
509
- #
510
- # === Handling server responses
511
- #
512
- # - #greeting: The server's initial untagged response, which can indicate a
513
- # pre-authenticated connection.
514
- # - #responses: A hash with arrays of unhandled <em>non-+nil+</em>
515
- # UntaggedResponse and ResponseCode +#data+, keyed by +#name+.
516
- # - #add_response_handler: Add a block to be called inside the receiver thread
517
- # with every server response.
518
- # - #remove_response_handler: Remove a previously added response handler.
486
+ # ==== RFC6855: <tt>UTF8=ACCEPT</tt>, <tt>UTF8=ONLY</tt>
519
487
  #
488
+ # - See #enable for information about support for UTF-8 string encoding.
520
489
  #
521
490
  # == References
522
- #--
523
- # TODO: Consider moving references list to REFERENCES.md or REFERENCES.rdoc.
524
- #++
525
491
  #
526
492
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
527
493
  # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
@@ -622,27 +588,21 @@ module Net
622
588
  # RFC 1864, DOI 10.17487/RFC1864, October 1995,
623
589
  # <https://www.rfc-editor.org/info/rfc1864>.
624
590
  #
625
- #--
626
- # TODO: Document IMAP keywords.
627
- #
628
- # [RFC3503[https://tools.ietf.org/html/rfc3503]]
629
- # Melnikov, A., "Message Disposition Notification (MDN)
630
- # profile for Internet Message Access Protocol (IMAP)",
631
- # RFC 3503, DOI 10.17487/RFC3503, March 2003,
632
- # <https://www.rfc-editor.org/info/rfc3503>.
633
- #++
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>.
634
596
  #
635
- # === Supported \IMAP Extensions
597
+ # === \IMAP Extensions
636
598
  #
637
- # [QUOTA[https://tools.ietf.org/html/rfc2087]]::
638
- # Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
639
- # January 1997, <https://www.rfc-editor.org/info/rfc2087>.
640
- #--
641
- # TODO: test compatibility with updated QUOTA extension:
642
599
  # [QUOTA[https://tools.ietf.org/html/rfc9208]]::
643
600
  # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
644
601
  # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
645
- #++
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>
646
606
  # [IDLE[https://tools.ietf.org/html/rfc2177]]::
647
607
  # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
648
608
  # June 1997, <https://www.rfc-editor.org/info/rfc2177>.
@@ -675,31 +635,43 @@ module Net
675
635
  # Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
676
636
  # (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
677
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>.
678
643
  #
679
644
  # === IANA registries
680
- #
681
645
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
682
646
  # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
683
647
  # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
684
648
  # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
685
649
  # * {IMAP Threading Algorithms}[https://www.iana.org/assignments/imap-threading-algorithms/imap-threading-algorithms.xhtml]
686
- #--
687
- # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
688
- # * [{LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
689
- # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
690
- # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
691
- # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
692
- # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
693
- #++
694
650
  # * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
695
651
  # * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]:
696
652
  # +imap+: tcp/143, +imaps+: tcp/993
697
653
  # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
698
654
  # +imap+
699
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]
700
663
  #
701
664
  class IMAP < Protocol
702
- VERSION = "0.3.7"
665
+ VERSION = "0.4.0"
666
+
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__)
703
675
 
704
676
  include MonitorMixin
705
677
  if defined?(OpenSSL::SSL)
@@ -707,35 +679,6 @@ module Net
707
679
  include SSL
708
680
  end
709
681
 
710
- # Returns the initial greeting the server, an UntaggedResponse.
711
- attr_reader :greeting
712
-
713
- # Returns a hash with arrays of unhandled <em>non-+nil+</em>
714
- # UntaggedResponse#data keyed by UntaggedResponse#name, and
715
- # ResponseCode#data keyed by ResponseCode#name.
716
- #
717
- # For example:
718
- #
719
- # imap.select("inbox")
720
- # p imap.responses["EXISTS"][-1]
721
- # #=> 2
722
- # p imap.responses["UIDVALIDITY"][-1]
723
- # #=> 968263756
724
- attr_reader :responses
725
-
726
- # Returns all response handlers.
727
- attr_reader :response_handlers
728
-
729
- # Seconds to wait until a connection is opened.
730
- # If the IMAP object cannot open a connection within this time,
731
- # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
732
- attr_reader :open_timeout
733
-
734
- # Seconds to wait until an IDLE response is received.
735
- attr_reader :idle_response_timeout
736
-
737
- attr_accessor :client_thread # :nodoc:
738
-
739
682
  # Returns the debug mode.
740
683
  def self.debug
741
684
  return @@debug
@@ -762,9 +705,175 @@ module Net
762
705
  alias default_ssl_port default_tls_port
763
706
  end
764
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
+
765
874
  # Disconnects from the server.
766
875
  #
767
- # Related: #logout
876
+ # Related: #logout, #logout!
768
877
  def disconnect
769
878
  return if disconnected?
770
879
  begin
@@ -794,62 +903,123 @@ module Net
794
903
  return @sock.closed?
795
904
  end
796
905
 
797
- # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
798
- # and returns an array of capabilities that the server supports. Each
799
- # capability is a string.
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.
800
909
  #
801
- # See the {IANA IMAP4 capabilities
802
- # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list
803
- # of all standard capabilities, and their reference RFCs.
910
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
911
+ # behaviour according to the server's advertised #capabilities.</em>
804
912
  #
805
- # >>>
806
- # <em>*Note* that Net::IMAP does not currently modify its
807
- # behaviour according to the capabilities of the server;
808
- # it is up to the user of the class to ensure that
809
- # a certain capability is supported by a server before
810
- # using it.</em>
913
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
811
914
  #
812
- # Capability requirements—other than +IMAP4rev1+—are listed in the
813
- # documentation for each command method.
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.
814
923
  #
815
- # ===== Basic IMAP4rev1 capabilities
924
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
925
+ # behaviour according to the server's advertised #capabilities.</em>
816
926
  #
817
- # All IMAP4rev1 servers must include +IMAP4rev1+ in their capabilities list.
818
- # All IMAP4rev1 servers must _implement_ the +STARTTLS+,
819
- # <tt>AUTH=PLAIN</tt>, and +LOGINDISABLED+ capabilities, and clients must
820
- # respect their presence or absence. See the capabilites requirements on
821
- # #starttls, #login, and #authenticate.
927
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
822
928
  #
823
- # ===== Using IMAP4rev1 extensions
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.
824
936
  #
825
- # IMAP4rev1 servers must not activate incompatible behavior until an
826
- # explicit client action invokes a capability, e.g. sending a command or
827
- # command argument specific to that capability. Extensions with backward
828
- # compatible behavior, such as response codes or mailbox attributes, may
829
- # be sent at any time.
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.
830
940
  #
831
- # Invoking capabilities which are unknown to Net::IMAP may cause unexpected
832
- # behavior and errors, for example ResponseParseError is raised when unknown
833
- # response syntax is received. Invoking commands or command parameters that
834
- # are unsupported by the server may raise NoResponseError, BadResponseError,
835
- # or cause other unexpected behavior.
941
+ # imap = Net::IMAP.new(hostname, ssl: false)
942
+ # imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"]
943
+ # imap.auth_mechanisms # => []
836
944
  #
837
- # ===== Caching +CAPABILITY+ responses
945
+ # imap.starttls
946
+ # imap.capabilities # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2",
947
+ # # "AUTH=OAUTHBEARER"]
948
+ # imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"]
838
949
  #
839
- # Servers may send their capability list, unsolicited, using the
840
- # +CAPABILITY+ response code or an untagged +CAPABILITY+ response. These
841
- # responses can be retrieved and cached using #responses or
842
- # #add_response_handler.
950
+ # imap.authenticate("XOAUTH2", username, oauth2_access_token)
951
+ # imap.auth_mechanisms # => []
952
+ #
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.
965
+ #
966
+ # imap.capable? "AUTH=PLAIN" # => true
967
+ # imap.auth_capable? "PLAIN" # => true
968
+ # imap.auth_capable? "blurdybloop" # => false
969
+ #
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.
977
+ #
978
+ # See Net::IMAP@Capabilities for more about \IMAP capabilities.
979
+ #
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.
988
+ #
989
+ # Net::IMAP automatically discards its cached capabilities when they can
990
+ # change. Explicitly calling this _should_ be unnecessary for well-behaved
991
+ # servers.
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.
843
1004
  #
844
- # But cached capabilities _must_ be discarded after #starttls, #login, or
845
- # #authenticate. The OK TaggedResponse to #login and #authenticate may
846
- # include +CAPABILITY+ response code data, but the TaggedResponse for
847
- # #starttls is sent clear-text and cannot be trusted.
1005
+ # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
1006
+ # behaviour according to the server's advertised #capabilities.</em>
848
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
849
1019
  def capability
850
1020
  synchronize do
851
1021
  send_command("CAPABILITY")
852
- return @responses.delete("CAPABILITY")[-1]
1022
+ @capabilities = clear_responses("CAPABILITY").last.freeze
853
1023
  end
854
1024
  end
855
1025
 
@@ -860,8 +1030,7 @@ module Net
860
1030
  # Note that the user should first check if the server supports the ID
861
1031
  # capability. For example:
862
1032
  #
863
- # capabilities = imap.capability
864
- # if capabilities.include?("ID")
1033
+ # if capable?(:ID)
865
1034
  # id = imap.id(
866
1035
  # name: "my IMAP client (ruby)",
867
1036
  # version: MyIMAP::VERSION,
@@ -879,7 +1048,7 @@ module Net
879
1048
  def id(client_id=nil)
880
1049
  synchronize do
881
1050
  send_command("ID", ClientID.new(client_id))
882
- @responses.delete("ID")&.last
1051
+ clear_responses("ID").last
883
1052
  end
884
1053
  end
885
1054
 
@@ -903,15 +1072,43 @@ module Net
903
1072
  # to inform the command to inform the server that the client is done with
904
1073
  # the connection.
905
1074
  #
906
- # Related: #disconnect
1075
+ # Related: #disconnect, #logout!
907
1076
  def logout
908
1077
  send_command("LOGOUT")
909
1078
  end
910
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
+
911
1103
  # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1]
912
1104
  # to start a TLS session.
913
1105
  #
914
- # 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.
915
1112
  #
916
1113
  # This method returns after TLS negotiation and hostname verification are
917
1114
  # both successful. Any error indicates that the connection has not been
@@ -921,132 +1118,156 @@ module Net
921
1118
  # >>>
922
1119
  # Any #response_handlers added before STARTTLS should be aware that the
923
1120
  # TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation.
924
- # 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.
925
1123
  #
926
1124
  # Related: Net::IMAP.new, #login, #authenticate
927
1125
  #
928
1126
  # ===== Capability
929
- #
930
- # The server's capabilities must include +STARTTLS+.
1127
+ # Clients should not call #starttls unless the server advertises the
1128
+ # +STARTTLS+ capability.
931
1129
  #
932
1130
  # Server capabilities may change after #starttls, #login, and #authenticate.
933
- # Cached capabilities _must_ be invalidated after this method completes.
934
- #
935
- # The TaggedResponse to #starttls is sent clear-text, so the server <em>must
936
- # *not*</em> send capabilities in the #starttls response and clients <em>must
937
- # not</em> use them if they are sent. Servers will generally send an
938
- # unsolicited untagged response immeditely _after_ #starttls completes.
1131
+ # Cached #capabilities will be cleared when this method completes.
939
1132
  #
940
- def starttls(options = {}, verify = true)
1133
+ def starttls(**options)
1134
+ @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
941
1135
  send_command("STARTTLS") do |resp|
942
1136
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
943
- begin
944
- # for backward compatibility
945
- certs = options.to_str
946
- options = create_ssl_params(certs, verify)
947
- rescue NoMethodError
948
- end
949
- start_tls_session(options)
1137
+ clear_cached_capabilities
1138
+ clear_responses
1139
+ start_tls_session
950
1140
  end
951
1141
  end
952
1142
  end
953
1143
 
954
1144
  # :call-seq:
955
- # authenticate(mechanism, ...) -> ok_resp
956
- # authenticate(mech, *creds, **props) {|prop, auth| val } -> ok_resp
957
- # authenticate(mechanism, authnid, credentials, authzid=nil) -> ok_resp
958
- # authenticate(mechanism, **properties) -> ok_resp
959
- # authenticate(mechanism) {|propname, authctx| prop_value } -> ok_resp
1145
+ # authenticate(mechanism, *, sasl_ir: true, **, &) -> ok_resp
960
1146
  #
961
1147
  # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
962
1148
  # to authenticate the client. If successful, the connection enters the
963
1149
  # "_authenticated_" state.
964
1150
  #
965
1151
  # +mechanism+ is the name of the \SASL authentication mechanism to be used.
966
- # All other arguments are forwarded to the authenticator for the requested
967
- # mechanism. The listed call signatures are suggestions. <em>The
968
- # documentation for each individual mechanism must be consulted for its
969
- # specific parameters.</em>
970
1152
  #
971
- # 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).
972
1155
  #
973
- # 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>
974
1159
  #
975
- # ==== Supported SASL Mechanisms
1160
+ # Related: #login, #starttls, #auth_capable?, #auth_mechanisms
976
1161
  #
977
- # +PLAIN+:: See PlainAuthenticator.
978
- # Login using clear-text username and password.
1162
+ # ==== Mechanisms
979
1163
  #
980
- # +XOAUTH2+:: See XOauth2Authenticator.
981
- # Login using a username and OAuth2 access token.
982
- # Non-standard and obsoleted by +OAUTHBEARER+, but widely
983
- # supported.
1164
+ # Each mechanism has different properties and requirements. Please consult
1165
+ # the documentation for the specific mechanisms you are using:
984
1166
  #
985
- # >>>
986
- # *Deprecated:* <em>Obsolete mechanisms are available for backwards
987
- # compatibility.</em>
1167
+ # +ANONYMOUS+::
1168
+ # See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
1169
+ #
1170
+ # Allows the user to gain access to public services or resources without
1171
+ # authenticating or disclosing an identity.
1172
+ #
1173
+ # +EXTERNAL+::
1174
+ # See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
1175
+ #
1176
+ # Authenticates using already established credentials, such as a TLS
1177
+ # certificate or IPsec.
1178
+ #
1179
+ # +OAUTHBEARER+::
1180
+ # See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator].
1181
+ #
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+.
988
1185
  #
989
- # For +DIGEST-MD5+ see DigestMD5Authenticator.
1186
+ # +PLAIN+::
1187
+ # See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator].
990
1188
  #
991
- # For +LOGIN+, see LoginAuthenticator.
1189
+ # Login using clear-text username and password.
992
1190
  #
993
- # For +CRAM-MD5+, see CramMD5Authenticator.
1191
+ # +SCRAM-SHA-1+::
1192
+ # +SCRAM-SHA-256+::
1193
+ # See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator].
994
1194
  #
995
- # <em>Using a deprecated mechanism will print a warning.</em>
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.
996
1201
  #
997
- # See Net::IMAP::Authenticators for information on plugging in
998
- # authenticators for other mechanisms. See the {SASL mechanism
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
999
1209
  # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
1000
- # 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.
1001
1212
  #
1002
- # ===== Capabilities
1213
+ # ===== Deprecated mechanisms
1003
1214
  #
1004
- # Clients MUST NOT attempt to authenticate with a mechanism unless
1005
- # <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>
1006
1218
  #
1007
- # Server capabilities may change after #starttls, #login, and #authenticate.
1008
- # Cached capabilities _must_ be invalidated after this method completes.
1009
- # The TaggedResponse to #authenticate may include updated capabilities in
1010
- # its ResponseCode.
1011
- #
1012
- # ===== Example
1013
- # If the authenticators ignore unhandled keyword arguments, the same config
1014
- # can be used for multiple mechanisms:
1015
- #
1016
- # password = nil # saved locally, so we don't ask more than once
1017
- # accesstok = nil # saved locally...
1018
- # creds = {
1019
- # authcid: username,
1020
- # password: proc { password ||= ui.prompt_for_password },
1021
- # oauth2_token: proc { accesstok ||= kms.fresh_access_token },
1022
- # }
1023
- # capa = imap.capability
1024
- # if capa.include? "AUTH=OAUTHBEARER"
1025
- # imap.authenticate "OAUTHBEARER", **creds # authcid, oauth2_token
1026
- # elsif capa.include? "AUTH=XOAUTH2"
1027
- # imap.authenticate "XOAUTH2", **creds # authcid, oauth2_token
1028
- # elsif capa.include? "AUTH=SCRAM-SHA-256"
1029
- # imap.authenticate "SCRAM-SHA-256", **creds # authcid, password
1030
- # elsif capa.include? "AUTH=PLAIN"
1031
- # imap.authenticate "PLAIN", **creds # authcid, password
1032
- # elsif capa.include? "AUTH=DIGEST-MD5"
1033
- # imap.authenticate "DIGEST-MD5", **creds # authcid, password
1034
- # elsif capa.include? "LOGINDISABLED"
1035
- # raise "the server has disabled login"
1036
- # else
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"
1037
1230
  # imap.login username, password
1231
+ # else
1232
+ # raise "No acceptable authentication mechanism is available"
1038
1233
  # end
1039
1234
  #
1040
- def authenticate(mechanism, *args, **props, &cb)
1041
- authenticator = self.class.authenticator(mechanism, *args, **props, &cb)
1042
- send_command("AUTHENTICATE", mechanism) do |resp|
1043
- if resp.instance_of?(ContinuationRequest)
1044
- data = authenticator.process(resp.data.text.unpack("m")[0])
1045
- s = [data].pack("m0")
1046
- send_string_data(s)
1047
- put_string(CRLF)
1048
- end
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"))
1049
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
1268
+ end
1269
+ @capabilities = capabilities_from_resp_code result
1270
+ result
1050
1271
  end
1051
1272
 
1052
1273
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1054,16 +1275,25 @@ module Net
1054
1275
  # this +user+. If successful, the connection enters the "_authenticated_"
1055
1276
  # state.
1056
1277
  #
1057
- # Using #authenticate is generally preferred over #login. The LOGIN command
1058
- # 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+.
1059
1282
  #
1060
1283
  # A Net::IMAP::NoResponseError is raised if authentication fails.
1061
1284
  #
1062
1285
  # Related: #authenticate, #starttls
1063
1286
  #
1064
- # ==== Capabilities
1065
- # Clients MUST NOT call #login if +LOGINDISABLED+ is listed with the
1066
- # 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
1067
1297
  #
1068
1298
  # Server capabilities may change after #starttls, #login, and #authenticate.
1069
1299
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1072,17 +1302,18 @@ module Net
1072
1302
  #
1073
1303
  def login(user, password)
1074
1304
  send_command("LOGIN", user, password)
1305
+ .tap { @capabilities = capabilities_from_resp_code _1 }
1075
1306
  end
1076
1307
 
1077
1308
  # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
1078
1309
  # to select a +mailbox+ so that messages in the +mailbox+ can be accessed.
1079
1310
  #
1080
1311
  # After you have selected a mailbox, you may retrieve the number of items in
1081
- # that mailbox from <tt>imap.responses["EXISTS"][-1]</tt>, and the number of
1082
- # recent messages from <tt>imap.responses["RECENT"][-1]</tt>. Note that
1083
- # these values can change if new messages arrive during a session or when
1084
- # existing messages are expunged; see #add_response_handler for a way to
1085
- # detect these events.
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.
1086
1317
  #
1087
1318
  # A Net::IMAP::NoResponseError is raised if the mailbox does not
1088
1319
  # exist or is for some reason non-selectable.
@@ -1213,7 +1444,7 @@ module Net
1213
1444
  def list(refname, mailbox)
1214
1445
  synchronize do
1215
1446
  send_command("LIST", refname, mailbox)
1216
- return @responses.delete("LIST")
1447
+ clear_responses("LIST")
1217
1448
  end
1218
1449
  end
1219
1450
 
@@ -1251,8 +1482,7 @@ module Net
1251
1482
  #
1252
1483
  # ===== For example:
1253
1484
  #
1254
- # capabilities = imap.capability
1255
- # if capabilities.include?("NAMESPACE")
1485
+ # if capable?("NAMESPACE")
1256
1486
  # namespaces = imap.namespace
1257
1487
  # if namespace = namespaces.personal.first
1258
1488
  # prefix = namespace.prefix # e.g. "" or "INBOX."
@@ -1271,7 +1501,7 @@ module Net
1271
1501
  def namespace
1272
1502
  synchronize do
1273
1503
  send_command("NAMESPACE")
1274
- return @responses.delete("NAMESPACE")[-1]
1504
+ clear_responses("NAMESPACE").last
1275
1505
  end
1276
1506
  end
1277
1507
 
@@ -1315,7 +1545,7 @@ module Net
1315
1545
  def xlist(refname, mailbox)
1316
1546
  synchronize do
1317
1547
  send_command("XLIST", refname, mailbox)
1318
- return @responses.delete("XLIST")
1548
+ clear_responses("XLIST")
1319
1549
  end
1320
1550
  end
1321
1551
 
@@ -1334,8 +1564,8 @@ module Net
1334
1564
  synchronize do
1335
1565
  send_command("GETQUOTAROOT", mailbox)
1336
1566
  result = []
1337
- result.concat(@responses.delete("QUOTAROOT"))
1338
- result.concat(@responses.delete("QUOTA"))
1567
+ result.concat(clear_responses("QUOTAROOT"))
1568
+ result.concat(clear_responses("QUOTA"))
1339
1569
  return result
1340
1570
  end
1341
1571
  end
@@ -1354,7 +1584,7 @@ module Net
1354
1584
  def getquota(mailbox)
1355
1585
  synchronize do
1356
1586
  send_command("GETQUOTA", mailbox)
1357
- return @responses.delete("QUOTA")
1587
+ clear_responses("QUOTA")
1358
1588
  end
1359
1589
  end
1360
1590
 
@@ -1410,7 +1640,7 @@ module Net
1410
1640
  def getacl(mailbox)
1411
1641
  synchronize do
1412
1642
  send_command("GETACL", mailbox)
1413
- return @responses.delete("ACL")[-1]
1643
+ clear_responses("ACL").last
1414
1644
  end
1415
1645
  end
1416
1646
 
@@ -1425,7 +1655,7 @@ module Net
1425
1655
  def lsub(refname, mailbox)
1426
1656
  synchronize do
1427
1657
  send_command("LSUB", refname, mailbox)
1428
- return @responses.delete("LSUB")
1658
+ clear_responses("LSUB")
1429
1659
  end
1430
1660
  end
1431
1661
 
@@ -1449,7 +1679,7 @@ module Net
1449
1679
  def status(mailbox, attr)
1450
1680
  synchronize do
1451
1681
  send_command("STATUS", mailbox, attr)
1452
- return @responses.delete("STATUS")[-1].attr
1682
+ clear_responses("STATUS").last&.attr
1453
1683
  end
1454
1684
  end
1455
1685
 
@@ -1538,7 +1768,7 @@ module Net
1538
1768
  def expunge
1539
1769
  synchronize do
1540
1770
  send_command("EXPUNGE")
1541
- return @responses.delete("EXPUNGE")
1771
+ clear_responses("EXPUNGE")
1542
1772
  end
1543
1773
  end
1544
1774
 
@@ -1570,7 +1800,7 @@ module Net
1570
1800
  def uid_expunge(uid_set)
1571
1801
  synchronize do
1572
1802
  send_command("UID EXPUNGE", MessageSet.new(uid_set))
1573
- return @responses.delete("EXPUNGE")
1803
+ clear_responses("EXPUNGE")
1574
1804
  end
1575
1805
  end
1576
1806
 
@@ -1589,7 +1819,7 @@ module Net
1589
1819
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
1590
1820
  # in addition to documentation for
1591
1821
  # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
1592
- # reported by #capability which may define additional search filters, e.g:
1822
+ # reported by #capabilities which may define additional search filters, e.g:
1593
1823
  # +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
1594
1824
  # +SAVEDATE+. The following are some common search criteria:
1595
1825
  #
@@ -1884,6 +2114,87 @@ module Net
1884
2114
  return thread_internal("UID THREAD", algorithm, search_keys, charset)
1885
2115
  end
1886
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
+
1887
2198
  # Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3]
1888
2199
  # {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13]
1889
2200
  # that waits for notifications of new or expunged messages. Yields
@@ -1948,6 +2259,104 @@ module Net
1948
2259
  end
1949
2260
  end
1950
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
+
1951
2360
  # Adds a response handler. For example, to detect when
1952
2361
  # the server sends a new EXISTS response (which normally
1953
2362
  # indicates new messages being added to the mailbox),
@@ -1960,14 +2369,21 @@ module Net
1960
2369
  # end
1961
2370
  # }
1962
2371
  #
2372
+ # Related: #remove_response_handler, #response_handlers
1963
2373
  def add_response_handler(handler = nil, &block)
1964
2374
  raise ArgumentError, "two Procs are passed" if handler && block
1965
- @response_handlers.push(block || handler)
2375
+ synchronize do
2376
+ @response_handlers.push(block || handler)
2377
+ end
1966
2378
  end
1967
2379
 
1968
2380
  # Removes the response handler.
2381
+ #
2382
+ # Related: #add_response_handler, #response_handlers
1969
2383
  def remove_response_handler(handler)
1970
- @response_handlers.delete(handler)
2384
+ synchronize do
2385
+ @response_handlers.delete(handler)
2386
+ end
1971
2387
  end
1972
2388
 
1973
2389
  private
@@ -1978,93 +2394,29 @@ module Net
1978
2394
 
1979
2395
  @@debug = false
1980
2396
 
1981
- # :call-seq:
1982
- # Net::IMAP.new(host, options = {})
1983
- #
1984
- # Creates a new Net::IMAP object and connects it to the specified
1985
- # +host+.
1986
- #
1987
- # +options+ is an option hash, each key of which is a symbol.
1988
- #
1989
- # The available options are:
1990
- #
1991
- # port:: Port number (default value is 143 for imap, or 993 for imaps)
1992
- # ssl:: If +options[:ssl]+ is true, then an attempt will be made
1993
- # to use SSL (now TLS) to connect to the server.
1994
- # If +options[:ssl]+ is a hash, it's passed to
1995
- # OpenSSL::SSL::SSLContext#set_params as parameters.
1996
- # open_timeout:: Seconds to wait until a connection is opened
1997
- # idle_response_timeout:: Seconds to wait until an IDLE response is received
1998
- #
1999
- # The most common errors are:
2000
- #
2001
- # Errno::ECONNREFUSED:: Connection refused by +host+ or an intervening
2002
- # firewall.
2003
- # Errno::ETIMEDOUT:: Connection timed out (possibly due to packets
2004
- # being dropped by an intervening firewall).
2005
- # Errno::ENETUNREACH:: There is no route to that network.
2006
- # SocketError:: Hostname not known or other socket error.
2007
- # Net::IMAP::ByeResponseError:: The connected to the host was successful, but
2008
- # it immediately said goodbye.
2009
- def initialize(host, port_or_options = {},
2010
- usessl = false, certs = nil, verify = true)
2011
- super()
2012
- @host = host
2013
- begin
2014
- options = port_or_options.to_hash
2015
- rescue NoMethodError
2016
- # for backward compatibility
2017
- options = {}
2018
- options[:port] = port_or_options
2019
- if usessl
2020
- options[:ssl] = create_ssl_params(certs, verify)
2021
- end
2022
- end
2023
- @port = options[:port] || (options[:ssl] ? SSL_PORT : PORT)
2024
- @tag_prefix = "RUBY"
2025
- @tagno = 0
2026
- @open_timeout = options[:open_timeout] || 30
2027
- @idle_response_timeout = options[:idle_response_timeout] || 5
2028
- @parser = ResponseParser.new
2029
- @sock = tcp_socket(@host, @port)
2030
- begin
2031
- if options[:ssl]
2032
- start_tls_session(options[:ssl])
2033
- @usessl = true
2034
- else
2035
- @usessl = false
2036
- end
2037
- @responses = Hash.new([].freeze)
2038
- @tagged_responses = {}
2039
- @response_handlers = []
2040
- @tagged_response_arrival = new_cond
2041
- @continued_command_tag = nil
2042
- @continuation_request_arrival = new_cond
2043
- @continuation_request_exception = nil
2044
- @idle_done_cond = nil
2045
- @logout_command_tag = nil
2046
- @debug_output_bol = true
2047
- @exception = nil
2048
-
2049
- @greeting = get_response
2050
- if @greeting.nil?
2051
- raise Error, "connection closed"
2052
- end
2053
- if @greeting.name == "BYE"
2054
- raise ByeResponseError, @greeting
2055
- end
2056
-
2057
- @client_thread = Thread.current
2058
- @receiver_thread = Thread.start {
2059
- begin
2060
- receive_responses
2061
- rescue Exception
2062
- end
2063
- }
2064
- @receiver_thread_terminating = false
2065
- rescue Exception
2066
- @sock.close
2067
- raise
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
2068
2420
  end
2069
2421
  end
2070
2422
 
@@ -2113,11 +2465,7 @@ module Net
2113
2465
  @continuation_request_arrival.signal
2114
2466
  end
2115
2467
  when UntaggedResponse
2116
- record_response(resp.name, resp.data)
2117
- if resp.data.instance_of?(ResponseText) &&
2118
- (code = resp.data.code)
2119
- record_response(code.name, code.data)
2120
- end
2468
+ record_untagged_response(resp)
2121
2469
  if resp.name == "BYE" && @logout_command_tag.nil?
2122
2470
  @sock.close
2123
2471
  @exception = ByeResponseError.new(resp)
@@ -2195,11 +2543,42 @@ module Net
2195
2543
  return @parser.parse(buff)
2196
2544
  end
2197
2545
 
2198
- def record_response(name, data)
2199
- unless @responses.has_key?(name)
2200
- @responses[name] = []
2546
+ #############################
2547
+ # built-in response handlers
2548
+
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
2201
2581
  end
2202
- @responses[name].push(data)
2203
2582
  end
2204
2583
 
2205
2584
  def send_command(cmd, *args, &block)
@@ -2241,8 +2620,8 @@ module Net
2241
2620
  if @debug_output_bol
2242
2621
  $stderr.print("C: ")
2243
2622
  end
2244
- $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
2245
- if /\r\n\z/n.match(str)
2623
+ $stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " })
2624
+ if /\n\z/n.match(str)
2246
2625
  @debug_output_bol = true
2247
2626
  else
2248
2627
  @debug_output_bol = false
@@ -2262,7 +2641,7 @@ module Net
2262
2641
  else
2263
2642
  send_command(cmd, *keys)
2264
2643
  end
2265
- return @responses.delete("SEARCH")[-1]
2644
+ clear_responses("SEARCH").last
2266
2645
  end
2267
2646
  end
2268
2647
 
@@ -2277,13 +2656,13 @@ module Net
2277
2656
  end
2278
2657
 
2279
2658
  synchronize do
2280
- @responses.delete("FETCH")
2659
+ clear_responses("FETCH")
2281
2660
  if mod
2282
2661
  send_command(cmd, MessageSet.new(set), attr, mod)
2283
2662
  else
2284
2663
  send_command(cmd, MessageSet.new(set), attr)
2285
2664
  end
2286
- return @responses.delete("FETCH")
2665
+ clear_responses("FETCH")
2287
2666
  end
2288
2667
  end
2289
2668
 
@@ -2292,9 +2671,9 @@ module Net
2292
2671
  attr = RawData.new(attr)
2293
2672
  end
2294
2673
  synchronize do
2295
- @responses.delete("FETCH")
2674
+ clear_responses("FETCH")
2296
2675
  send_command(cmd, MessageSet.new(set), attr, flags)
2297
- return @responses.delete("FETCH")
2676
+ clear_responses("FETCH")
2298
2677
  end
2299
2678
  end
2300
2679
 
@@ -2311,7 +2690,7 @@ module Net
2311
2690
  normalize_searching_criteria(search_keys)
2312
2691
  synchronize do
2313
2692
  send_command(cmd, sort_keys, charset, *search_keys)
2314
- return @responses.delete("SORT")[-1]
2693
+ clear_responses("SORT").last
2315
2694
  end
2316
2695
  end
2317
2696
 
@@ -2322,8 +2701,10 @@ module Net
2322
2701
  normalize_searching_criteria(search_keys)
2323
2702
  end
2324
2703
  normalize_searching_criteria(search_keys)
2325
- send_command(cmd, algorithm, charset, *search_keys)
2326
- return @responses.delete("THREAD")[-1]
2704
+ synchronize do
2705
+ send_command(cmd, algorithm, charset, *search_keys)
2706
+ clear_responses("THREAD").last
2707
+ end
2327
2708
  end
2328
2709
 
2329
2710
  def normalize_searching_criteria(keys)
@@ -2337,49 +2718,45 @@ module Net
2337
2718
  end
2338
2719
  end
2339
2720
 
2340
- def create_ssl_params(certs = nil, verify = true)
2341
- params = {}
2342
- if certs
2343
- if File.file?(certs)
2344
- params[:ca_file] = certs
2345
- elsif File.directory?(certs)
2346
- params[:ca_path] = certs
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
2347
2728
  end
2348
- end
2349
- if verify
2350
- params[:verify_mode] = VERIFY_PEER
2729
+ context.freeze
2730
+ [params, context]
2351
2731
  else
2352
- params[:verify_mode] = VERIFY_NONE
2732
+ false
2353
2733
  end
2354
- return params
2355
2734
  end
2356
2735
 
2357
- def start_tls_session(params = {})
2358
- unless defined?(OpenSSL::SSL)
2359
- raise "SSL extension not installed"
2360
- end
2361
- if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
2362
- raise RuntimeError, "already using SSL"
2363
- end
2364
- begin
2365
- params = params.to_hash
2366
- rescue NoMethodError
2367
- params = {}
2368
- end
2369
- context = SSLContext.new
2370
- context.set_params(params)
2371
- if defined?(VerifyCallbackProc)
2372
- context.verify_callback = VerifyCallbackProc
2373
- end
2374
- @sock = SSLSocket.new(@sock, context)
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)
2375
2741
  @sock.sync_close = true
2376
2742
  @sock.hostname = @host if @sock.respond_to? :hostname=
2377
2743
  ssl_socket_connect(@sock, @open_timeout)
2378
- if context.verify_mode != VERIFY_NONE
2744
+ if ssl_ctx.verify_mode != VERIFY_NONE
2379
2745
  @sock.post_connection_check(@host)
2746
+ @tls_verified = true
2380
2747
  end
2381
2748
  end
2382
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
+
2383
2760
  end
2384
2761
  end
2385
2762
 
@@ -2390,4 +2767,6 @@ require_relative "imap/flags"
2390
2767
  require_relative "imap/response_data"
2391
2768
  require_relative "imap/response_parser"
2392
2769
  require_relative "imap/authenticators"
2393
- require_relative "imap/sasl"
2770
+
2771
+ require_relative "imap/deprecated_client_options"
2772
+ Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions