net-imap 0.4.12 → 0.4.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/net/imap/command_data.rb +2 -2
- data/lib/net/imap/config/attr_accessors.rb +75 -0
- data/lib/net/imap/config/attr_inheritance.rb +90 -0
- data/lib/net/imap/config/attr_type_coercion.rb +65 -0
- data/lib/net/imap/config.rb +524 -0
- data/lib/net/imap/deprecated_client_options.rb +2 -2
- data/lib/net/imap/errors.rb +33 -0
- data/lib/net/imap/response_data.rb +3 -54
- data/lib/net/imap/response_parser/parser_utils.rb +6 -6
- data/lib/net/imap/response_parser.rb +34 -15
- data/lib/net/imap/response_reader.rb +75 -0
- data/lib/net/imap/sequence_set.rb +274 -123
- data/lib/net/imap/uidplus_data.rb +326 -0
- data/lib/net/imap.rb +297 -86
- data/net-imap.gemspec +2 -2
- metadata +9 -9
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/pages.yml +0 -46
- data/.github/workflows/push_gem.yml +0 -48
- data/.github/workflows/test.yml +0 -31
- data/.gitignore +0 -12
- data/.mailmap +0 -13
data/lib/net/imap.rb
CHANGED
@@ -43,10 +43,16 @@ module Net
|
|
43
43
|
# To work on the messages within a mailbox, the client must
|
44
44
|
# first select that mailbox, using either #select or #examine
|
45
45
|
# (for read-only access). Once the client has successfully
|
46
|
-
# selected a mailbox, they enter the
|
46
|
+
# selected a mailbox, they enter the +selected+ state, and that
|
47
47
|
# mailbox becomes the _current_ mailbox, on which mail-item
|
48
48
|
# related commands implicitly operate.
|
49
49
|
#
|
50
|
+
# === Connection state
|
51
|
+
#
|
52
|
+
# Once an IMAP connection is established, the connection is in one of four
|
53
|
+
# states: <tt>not authenticated</tt>, +authenticated+, +selected+, and
|
54
|
+
# +logout+. Most commands are valid only in certain states.
|
55
|
+
#
|
50
56
|
# === Sequence numbers and UIDs
|
51
57
|
#
|
52
58
|
# Messages have two sorts of identifiers: message sequence
|
@@ -199,6 +205,42 @@ module Net
|
|
199
205
|
#
|
200
206
|
# This script invokes the FETCH command and the SEARCH command concurrently.
|
201
207
|
#
|
208
|
+
# When running multiple commands, care must be taken to avoid ambiguity. For
|
209
|
+
# example, SEARCH responses are ambiguous about which command they are
|
210
|
+
# responding to, so search commands should not run simultaneously, unless the
|
211
|
+
# server supports +ESEARCH+ {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731] or
|
212
|
+
# IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]. See {RFC9051
|
213
|
+
# §5.5}[https://www.rfc-editor.org/rfc/rfc9051.html#section-5.5] for
|
214
|
+
# other examples of command sequences which should not be pipelined.
|
215
|
+
#
|
216
|
+
# == Unbounded memory use
|
217
|
+
#
|
218
|
+
# Net::IMAP reads server responses in a separate receiver thread per client.
|
219
|
+
# Unhandled response data is saved to #responses, and response_handlers run
|
220
|
+
# inside the receiver thread. See the list of methods for {handling server
|
221
|
+
# responses}[rdoc-ref:Net::IMAP@Handling+server+responses], below.
|
222
|
+
#
|
223
|
+
# Because the receiver thread continuously reads and saves new responses, some
|
224
|
+
# scenarios must be careful to avoid unbounded memory use:
|
225
|
+
#
|
226
|
+
# * Commands such as #list or #fetch can have an enormous number of responses.
|
227
|
+
# * Commands such as #fetch can result in an enormous size per response.
|
228
|
+
# * Long-lived connections will gradually accumulate unsolicited server
|
229
|
+
# responses, especially +EXISTS+, +FETCH+, and +EXPUNGE+ responses.
|
230
|
+
# * A buggy or untrusted server could send inappropriate responses, which
|
231
|
+
# could be very numerous, very large, and very rapid.
|
232
|
+
#
|
233
|
+
# Use paginated or limited versions of commands whenever possible.
|
234
|
+
#
|
235
|
+
# Use Config#max_response_size to impose a limit on incoming server responses
|
236
|
+
# as they are being read. <em>This is especially important for untrusted
|
237
|
+
# servers.</em>
|
238
|
+
#
|
239
|
+
# Use #add_response_handler to handle responses after each one is received.
|
240
|
+
# Use the +response_handlers+ argument to ::new to assign response handlers
|
241
|
+
# before the receiver thread is started. Use #extract_responses,
|
242
|
+
# #clear_responses, or #responses (with a block) to prune responses.
|
243
|
+
#
|
202
244
|
# == Errors
|
203
245
|
#
|
204
246
|
# An \IMAP server can send three different types of responses to indicate
|
@@ -260,8 +302,9 @@ module Net
|
|
260
302
|
#
|
261
303
|
# - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
|
262
304
|
# waits for a successful server greeting before the method returns.
|
305
|
+
# - #connection_state: Returns the connection state.
|
263
306
|
# - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
|
264
|
-
# - #logout: Tells the server to end the session.
|
307
|
+
# - #logout: Tells the server to end the session. Enters the +logout+ state.
|
265
308
|
# - #disconnect: Disconnects the connection (without sending #logout first).
|
266
309
|
# - #disconnected?: True if the connection has been closed.
|
267
310
|
#
|
@@ -288,6 +331,8 @@ module Net
|
|
288
331
|
# pre-authenticated connection.
|
289
332
|
# - #responses: Yields unhandled UntaggedResponse#data and <em>non-+nil+</em>
|
290
333
|
# ResponseCode#data.
|
334
|
+
# - #extract_responses: Removes and returns the responses for which the block
|
335
|
+
# returns a true value.
|
291
336
|
# - #clear_responses: Deletes unhandled data from #responses and returns it.
|
292
337
|
# - #add_response_handler: Add a block to be called inside the receiver thread
|
293
338
|
# with every server response.
|
@@ -315,37 +360,36 @@ module Net
|
|
315
360
|
# <em>In general, #capable? should be used rather than explicitly sending a
|
316
361
|
# +CAPABILITY+ command to the server.</em>
|
317
362
|
# - #noop: Allows the server to send unsolicited untagged #responses.
|
318
|
-
# - #logout: Tells the server to end the session. Enters the
|
363
|
+
# - #logout: Tells the server to end the session. Enters the +logout+ state.
|
319
364
|
#
|
320
365
|
# ==== Not Authenticated state
|
321
366
|
#
|
322
367
|
# In addition to the commands for any state, the following commands are valid
|
323
|
-
# in the
|
368
|
+
# in the +not_authenticated+ state:
|
324
369
|
#
|
325
370
|
# - #starttls: Upgrades a clear-text connection to use TLS.
|
326
371
|
#
|
327
372
|
# <em>Requires the +STARTTLS+ capability.</em>
|
328
373
|
# - #authenticate: Identifies the client to the server using the given
|
329
374
|
# {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
330
|
-
# and credentials. Enters the
|
375
|
+
# and credentials. Enters the +authenticated+ state.
|
331
376
|
#
|
332
377
|
# <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
|
333
378
|
# supported mechanisms.</em>
|
334
379
|
# - #login: Identifies the client to the server using a plain text password.
|
335
|
-
# Using #authenticate is
|
336
|
-
# state.
|
380
|
+
# Using #authenticate is preferred. Enters the +authenticated+ state.
|
337
381
|
#
|
338
382
|
# <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
|
339
383
|
#
|
340
384
|
# ==== Authenticated state
|
341
385
|
#
|
342
386
|
# In addition to the commands for any state, the following commands are valid
|
343
|
-
# in the
|
387
|
+
# in the +authenticated+ state:
|
344
388
|
#
|
345
389
|
# - #enable: Enables backwards incompatible server extensions.
|
346
390
|
# <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
|
347
|
-
# - #select: Open a mailbox and enter the
|
348
|
-
# - #examine: Open a mailbox read-only, and enter the
|
391
|
+
# - #select: Open a mailbox and enter the +selected+ state.
|
392
|
+
# - #examine: Open a mailbox read-only, and enter the +selected+ state.
|
349
393
|
# - #create: Creates a new mailbox.
|
350
394
|
# - #delete: Permanently remove a mailbox.
|
351
395
|
# - #rename: Change the name of a mailbox.
|
@@ -367,12 +411,12 @@ module Net
|
|
367
411
|
#
|
368
412
|
# ==== Selected state
|
369
413
|
#
|
370
|
-
# In addition to the commands for any state and the
|
371
|
-
# commands, the following commands are valid in the
|
414
|
+
# In addition to the commands for any state and the +authenticated+
|
415
|
+
# commands, the following commands are valid in the +selected+ state:
|
372
416
|
#
|
373
|
-
# - #close: Closes the mailbox and returns to the
|
417
|
+
# - #close: Closes the mailbox and returns to the +authenticated+ state,
|
374
418
|
# expunging deleted messages, unless the mailbox was opened as read-only.
|
375
|
-
# - #unselect: Closes the mailbox and returns to the
|
419
|
+
# - #unselect: Closes the mailbox and returns to the +authenticated+ state,
|
376
420
|
# without expunging any messages.
|
377
421
|
# <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
|
378
422
|
# - #expunge: Permanently removes messages which have the Deleted flag set.
|
@@ -393,7 +437,7 @@ module Net
|
|
393
437
|
#
|
394
438
|
# ==== Logout state
|
395
439
|
#
|
396
|
-
# No \IMAP commands are valid in the
|
440
|
+
# No \IMAP commands are valid in the +logout+ state. If the socket is still
|
397
441
|
# open, Net::IMAP will close it after receiving server confirmation.
|
398
442
|
# Exceptions will be raised by \IMAP commands that have already started and
|
399
443
|
# are waiting for a response, as well as any that are called after logout.
|
@@ -447,7 +491,7 @@ module Net
|
|
447
491
|
# ==== RFC3691: +UNSELECT+
|
448
492
|
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
|
449
493
|
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
450
|
-
# - #unselect: Closes the mailbox and returns to the
|
494
|
+
# - #unselect: Closes the mailbox and returns to the +authenticated+ state,
|
451
495
|
# without expunging any messages.
|
452
496
|
#
|
453
497
|
# ==== RFC4314: +ACL+
|
@@ -717,7 +761,7 @@ module Net
|
|
717
761
|
# * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
|
718
762
|
#
|
719
763
|
class IMAP < Protocol
|
720
|
-
VERSION = "0.4.
|
764
|
+
VERSION = "0.4.21"
|
721
765
|
|
722
766
|
# Aliases for supported capabilities, to be used with the #enable command.
|
723
767
|
ENABLE_ALIASES = {
|
@@ -725,6 +769,7 @@ module Net
|
|
725
769
|
"UTF8=ONLY" => "UTF8=ACCEPT",
|
726
770
|
}.freeze
|
727
771
|
|
772
|
+
autoload :ResponseReader, File.expand_path("imap/response_reader", __dir__)
|
728
773
|
autoload :SASL, File.expand_path("imap/sasl", __dir__)
|
729
774
|
autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
|
730
775
|
autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
|
@@ -735,14 +780,17 @@ module Net
|
|
735
780
|
include SSL
|
736
781
|
end
|
737
782
|
|
738
|
-
# Returns the
|
739
|
-
def self.
|
740
|
-
|
741
|
-
|
783
|
+
# Returns the global Config object
|
784
|
+
def self.config; Config.global end
|
785
|
+
|
786
|
+
# Returns the global debug mode.
|
787
|
+
# Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
|
788
|
+
def self.debug; config.debug end
|
742
789
|
|
743
|
-
# Sets the debug mode.
|
790
|
+
# Sets the global debug mode.
|
791
|
+
# Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
|
744
792
|
def self.debug=(val)
|
745
|
-
|
793
|
+
config.debug = val
|
746
794
|
end
|
747
795
|
|
748
796
|
# The default port for IMAP connections, port 143
|
@@ -761,16 +809,37 @@ module Net
|
|
761
809
|
alias default_ssl_port default_tls_port
|
762
810
|
end
|
763
811
|
|
764
|
-
# Returns the initial greeting the server, an UntaggedResponse.
|
812
|
+
# Returns the initial greeting sent by the server, an UntaggedResponse.
|
765
813
|
attr_reader :greeting
|
766
814
|
|
767
|
-
#
|
768
|
-
#
|
769
|
-
#
|
770
|
-
|
815
|
+
# The client configuration. See Net::IMAP::Config.
|
816
|
+
#
|
817
|
+
# By default, the client's local configuration inherits from the global
|
818
|
+
# Net::IMAP.config.
|
819
|
+
attr_reader :config
|
820
|
+
|
821
|
+
##
|
822
|
+
# :attr_reader: open_timeout
|
823
|
+
# Seconds to wait until a connection is opened. Also used by #starttls.
|
824
|
+
# Delegates to {config.open_timeout}[rdoc-ref:Config#open_timeout].
|
771
825
|
|
826
|
+
##
|
827
|
+
# :attr_reader: idle_response_timeout
|
772
828
|
# Seconds to wait until an IDLE response is received.
|
773
|
-
|
829
|
+
# Delegates to {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout].
|
830
|
+
|
831
|
+
##
|
832
|
+
# :attr_accessor: max_response_size
|
833
|
+
#
|
834
|
+
# The maximum allowed server response size, in bytes.
|
835
|
+
# Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size].
|
836
|
+
|
837
|
+
# :stopdoc:
|
838
|
+
def open_timeout; config.open_timeout end
|
839
|
+
def idle_response_timeout; config.idle_response_timeout end
|
840
|
+
def max_response_size; config.max_response_size end
|
841
|
+
def max_response_size=(val) config.max_response_size = val end
|
842
|
+
# :startdoc:
|
774
843
|
|
775
844
|
# The hostname this client connected to
|
776
845
|
attr_reader :host
|
@@ -809,14 +878,46 @@ module Net
|
|
809
878
|
# If +ssl+ is a hash, it's passed to
|
810
879
|
# {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
|
811
880
|
# the keys are names of attribute assignment methods on
|
812
|
-
# SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
|
881
|
+
# SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]. For example:
|
882
|
+
#
|
883
|
+
# [{ca_file}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-ca_file]]
|
884
|
+
# The path to a file containing a PEM-format CA certificate.
|
885
|
+
# [{ca_path}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-ca_path]]
|
886
|
+
# The path to a directory containing CA certificates in PEM format.
|
887
|
+
# [{min_version}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-min_version-3D]]
|
888
|
+
# Sets the lower bound on the supported SSL/TLS protocol version. Set to
|
889
|
+
# an +OpenSSL+ constant such as +OpenSSL::SSL::TLS1_2_VERSION+,
|
890
|
+
# [{verify_mode}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-verify_mode]]
|
891
|
+
# SSL session verification mode. Valid modes include
|
892
|
+
# +OpenSSL::SSL::VERIFY_PEER+ and +OpenSSL::SSL::VERIFY_NONE+.
|
893
|
+
#
|
894
|
+
# See {OpenSSL::SSL::SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] for other valid SSL context params.
|
895
|
+
#
|
896
|
+
# See DeprecatedClientOptions.new for deprecated SSL arguments.
|
897
|
+
#
|
898
|
+
# [response_handlers]
|
899
|
+
# A list of response handlers to be added before the receiver thread is
|
900
|
+
# started. This ensures every server response is handled, including the
|
901
|
+
# #greeting. Note that the greeting is handled in the current thread, but
|
902
|
+
# all other responses are handled in the receiver thread.
|
903
|
+
#
|
904
|
+
# [config]
|
905
|
+
# A Net::IMAP::Config object to use as the basis for #config. By default,
|
906
|
+
# the global Net::IMAP.config is used.
|
813
907
|
#
|
814
|
-
#
|
815
|
-
#
|
816
|
-
#
|
817
|
-
# Seconds to wait until an IDLE response is received
|
908
|
+
# >>>
|
909
|
+
# *NOTE:* +config+ does not set #config directly---it sets the _parent_
|
910
|
+
# config for inheritance. Every client creates its own unique #config.
|
818
911
|
#
|
819
|
-
#
|
912
|
+
# All other keyword arguments are forwarded to Net::IMAP::Config.new, to
|
913
|
+
# initialize the client's #config. For example:
|
914
|
+
#
|
915
|
+
# [{open_timeout}[rdoc-ref:Config#open_timeout]]
|
916
|
+
# Seconds to wait until a connection is opened
|
917
|
+
# [{idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]]
|
918
|
+
# Seconds to wait until an IDLE response is received
|
919
|
+
#
|
920
|
+
# See Net::IMAP::Config for other valid options.
|
820
921
|
#
|
821
922
|
# ==== Examples
|
822
923
|
#
|
@@ -871,14 +972,13 @@ module Net
|
|
871
972
|
# [Net::IMAP::ByeResponseError]
|
872
973
|
# Connected to the host successfully, but it immediately said goodbye.
|
873
974
|
#
|
874
|
-
def initialize(host, port: nil, ssl:
|
875
|
-
|
975
|
+
def initialize(host, port: nil, ssl: nil, response_handlers: nil,
|
976
|
+
config: Config.global, **config_options)
|
876
977
|
super()
|
877
978
|
# Config options
|
878
979
|
@host = host
|
980
|
+
@config = Config.new(config, **config_options)
|
879
981
|
@port = port || (ssl ? SSL_PORT : PORT)
|
880
|
-
@open_timeout = Integer(open_timeout)
|
881
|
-
@idle_response_timeout = Integer(idle_response_timeout)
|
882
982
|
@ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
|
883
983
|
|
884
984
|
# Basic Client State
|
@@ -889,12 +989,13 @@ module Net
|
|
889
989
|
@capabilities = nil
|
890
990
|
|
891
991
|
# Client Protocol Receiver
|
892
|
-
@parser = ResponseParser.new
|
992
|
+
@parser = ResponseParser.new(config: @config)
|
893
993
|
@responses = Hash.new {|h, k| h[k] = [] }
|
894
994
|
@response_handlers = []
|
895
995
|
@receiver_thread = nil
|
896
996
|
@receiver_thread_exception = nil
|
897
997
|
@receiver_thread_terminating = false
|
998
|
+
response_handlers&.each do add_response_handler(_1) end
|
898
999
|
|
899
1000
|
# Client Protocol Sender (including state for currently running commands)
|
900
1001
|
@tag_prefix = "RUBY"
|
@@ -910,6 +1011,7 @@ module Net
|
|
910
1011
|
# Connection
|
911
1012
|
@tls_verified = false
|
912
1013
|
@sock = tcp_socket(@host, @port)
|
1014
|
+
@reader = ResponseReader.new(self, @sock)
|
913
1015
|
start_tls_session if ssl_ctx
|
914
1016
|
start_imap_connection
|
915
1017
|
|
@@ -1170,6 +1272,10 @@ module Net
|
|
1170
1272
|
# both successful. Any error indicates that the connection has not been
|
1171
1273
|
# secured.
|
1172
1274
|
#
|
1275
|
+
# After the server agrees to start a TLS connection, this method waits up to
|
1276
|
+
# {config.open_timeout}[rdoc-ref:Config#open_timeout] before raising
|
1277
|
+
# +Net::OpenTimeout+.
|
1278
|
+
#
|
1173
1279
|
# *Note:*
|
1174
1280
|
# >>>
|
1175
1281
|
# Any #response_handlers added before STARTTLS should be aware that the
|
@@ -1188,17 +1294,25 @@ module Net
|
|
1188
1294
|
#
|
1189
1295
|
def starttls(**options)
|
1190
1296
|
@ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
|
1191
|
-
|
1297
|
+
error = nil
|
1298
|
+
ok = send_command("STARTTLS") do |resp|
|
1192
1299
|
if resp.kind_of?(TaggedResponse) && resp.name == "OK"
|
1193
1300
|
clear_cached_capabilities
|
1194
1301
|
clear_responses
|
1195
1302
|
start_tls_session
|
1196
1303
|
end
|
1304
|
+
rescue Exception => error
|
1305
|
+
raise # note that the error backtrace is in the receiver_thread
|
1197
1306
|
end
|
1307
|
+
if error
|
1308
|
+
disconnect
|
1309
|
+
raise error
|
1310
|
+
end
|
1311
|
+
ok
|
1198
1312
|
end
|
1199
1313
|
|
1200
1314
|
# :call-seq:
|
1201
|
-
# authenticate(mechanism, *, sasl_ir:
|
1315
|
+
# authenticate(mechanism, *, sasl_ir: config.sasl_ir, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp
|
1202
1316
|
#
|
1203
1317
|
# Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
|
1204
1318
|
# to authenticate the client. If successful, the connection enters the
|
@@ -1207,7 +1321,8 @@ module Net
|
|
1207
1321
|
# +mechanism+ is the name of the \SASL authentication mechanism to be used.
|
1208
1322
|
#
|
1209
1323
|
# +sasl_ir+ allows or disallows sending an "initial response" (see the
|
1210
|
-
# +SASL-IR+ capability, below).
|
1324
|
+
# +SASL-IR+ capability, below). Defaults to the #config value for
|
1325
|
+
# {sasl_ir}[rdoc-ref:Config#sasl_ir], which defaults to +true+.
|
1211
1326
|
#
|
1212
1327
|
# All other arguments are forwarded to the registered SASL authenticator for
|
1213
1328
|
# the requested mechanism. <em>The documentation for each individual
|
@@ -1303,7 +1418,9 @@ module Net
|
|
1303
1418
|
# Previously cached #capabilities will be cleared when this method
|
1304
1419
|
# completes. If the TaggedResponse to #authenticate includes updated
|
1305
1420
|
# capabilities, they will be cached.
|
1306
|
-
def authenticate(mechanism, *creds,
|
1421
|
+
def authenticate(mechanism, *creds,
|
1422
|
+
sasl_ir: config.sasl_ir,
|
1423
|
+
**props, &callback)
|
1307
1424
|
mechanism = mechanism.to_s.tr("_", "-").upcase
|
1308
1425
|
authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
|
1309
1426
|
cmdargs = ["AUTHENTICATE", mechanism]
|
@@ -2397,11 +2514,17 @@ module Net
|
|
2397
2514
|
# checks the connection for each 60 seconds.
|
2398
2515
|
#
|
2399
2516
|
# loop do
|
2400
|
-
# imap.idle(60) do |
|
2401
|
-
#
|
2517
|
+
# imap.idle(60) do |response|
|
2518
|
+
# do_something_with(response)
|
2519
|
+
# imap.idle_done if some_condition?(response)
|
2402
2520
|
# end
|
2403
2521
|
# end
|
2404
2522
|
#
|
2523
|
+
# Returns the server's response to indicate the IDLE state has ended.
|
2524
|
+
# Returns +nil+ if the server does not respond to #idle_done within
|
2525
|
+
# {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]
|
2526
|
+
# seconds.
|
2527
|
+
#
|
2405
2528
|
# Related: #idle_done, #noop, #check
|
2406
2529
|
#
|
2407
2530
|
# ===== Capabilities
|
@@ -2429,7 +2552,7 @@ module Net
|
|
2429
2552
|
unless @receiver_thread_terminating
|
2430
2553
|
remove_response_handler(response_handler)
|
2431
2554
|
put_string("DONE#{CRLF}")
|
2432
|
-
response = get_tagged_response(tag, "IDLE",
|
2555
|
+
response = get_tagged_response(tag, "IDLE", idle_response_timeout)
|
2433
2556
|
end
|
2434
2557
|
end
|
2435
2558
|
end
|
@@ -2437,7 +2560,11 @@ module Net
|
|
2437
2560
|
return response
|
2438
2561
|
end
|
2439
2562
|
|
2440
|
-
# Leaves IDLE.
|
2563
|
+
# Leaves IDLE, allowing #idle to return.
|
2564
|
+
#
|
2565
|
+
# If the server does not respond within
|
2566
|
+
# {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]
|
2567
|
+
# seconds, #idle will return +nil+.
|
2441
2568
|
#
|
2442
2569
|
# Related: #idle
|
2443
2570
|
def idle_done
|
@@ -2449,40 +2576,98 @@ module Net
|
|
2449
2576
|
end
|
2450
2577
|
end
|
2451
2578
|
|
2579
|
+
RESPONSES_DEPRECATION_MSG =
|
2580
|
+
"Pass a type or block to #responses, " \
|
2581
|
+
"set config.responses_without_block to :frozen_dup " \
|
2582
|
+
"or :silence_deprecation_warning, " \
|
2583
|
+
"or use #extract_responses or #clear_responses."
|
2584
|
+
private_constant :RESPONSES_DEPRECATION_MSG
|
2585
|
+
|
2452
2586
|
# :call-seq:
|
2587
|
+
# responses -> hash of {String => Array} (see config.responses_without_block)
|
2588
|
+
# responses(type) -> frozen array
|
2453
2589
|
# responses {|hash| ...} -> block result
|
2454
2590
|
# responses(type) {|array| ...} -> block result
|
2455
2591
|
#
|
2456
|
-
# Yields
|
2592
|
+
# Yields or returns unhandled server responses. Unhandled responses are
|
2593
|
+
# stored in a hash, with arrays of UntaggedResponse#data keyed by
|
2594
|
+
# UntaggedResponse#name and <em>non-+nil+</em> untagged ResponseCode#data
|
2595
|
+
# keyed by ResponseCode#name.
|
2596
|
+
#
|
2597
|
+
# When a block is given, yields unhandled responses and returns the block's
|
2598
|
+
# result. Without a block, returns the unhandled responses.
|
2599
|
+
#
|
2600
|
+
# [With +type+]
|
2601
|
+
# Yield or return only the array of responses for that +type+.
|
2602
|
+
# When no block is given, the returned array is a frozen copy.
|
2603
|
+
# [Without +type+]
|
2604
|
+
# Yield or return the entire responses hash.
|
2605
|
+
#
|
2606
|
+
# When no block is given, the behavior is determined by
|
2607
|
+
# Config#responses_without_block:
|
2608
|
+
# >>>
|
2609
|
+
# [+:silence_deprecation_warning+ <em>(original behavior)</em>]
|
2610
|
+
# Returns the mutable responses hash (without any warnings).
|
2611
|
+
# <em>This is not thread-safe.</em>
|
2457
2612
|
#
|
2458
|
-
#
|
2459
|
-
#
|
2460
|
-
#
|
2461
|
-
#
|
2462
|
-
#
|
2613
|
+
# [+:warn+ <em>(default since +v0.5+)</em>]
|
2614
|
+
# Prints a warning and returns the mutable responses hash.
|
2615
|
+
# <em>This is not thread-safe.</em>
|
2616
|
+
#
|
2617
|
+
# [+:frozen_dup+ <em>(planned default for +v0.6+)</em>]
|
2618
|
+
# Returns a frozen copy of the unhandled responses hash, with frozen
|
2619
|
+
# array values.
|
2620
|
+
#
|
2621
|
+
# [+:raise+]
|
2622
|
+
# Raise an +ArgumentError+ with the deprecation warning.
|
2463
2623
|
#
|
2464
2624
|
# For example:
|
2465
2625
|
#
|
2466
2626
|
# imap.select("inbox")
|
2467
|
-
# p imap.responses("EXISTS"
|
2627
|
+
# p imap.responses("EXISTS").last
|
2468
2628
|
# #=> 2
|
2629
|
+
# p imap.responses("UIDNEXT", &:last)
|
2630
|
+
# #=> 123456
|
2469
2631
|
# p imap.responses("UIDVALIDITY", &:last)
|
2470
2632
|
# #=> 968263756
|
2633
|
+
# p imap.responses {|responses|
|
2634
|
+
# {
|
2635
|
+
# exists: responses.delete("EXISTS").last,
|
2636
|
+
# uidnext: responses.delete("UIDNEXT").last,
|
2637
|
+
# uidvalidity: responses.delete("UIDVALIDITY").last,
|
2638
|
+
# }
|
2639
|
+
# }
|
2640
|
+
# #=> {:exists=>2, :uidnext=>123456, :uidvalidity=>968263756}
|
2641
|
+
# # "EXISTS", "UIDNEXT", and "UIDVALIDITY" have been removed:
|
2642
|
+
# p imap.responses(&:keys)
|
2643
|
+
# #=> ["FLAGS", "OK", "PERMANENTFLAGS", "RECENT", "HIGHESTMODSEQ"]
|
2471
2644
|
#
|
2645
|
+
# Related: #extract_responses, #clear_responses, #response_handlers, #greeting
|
2646
|
+
#
|
2647
|
+
# ===== Thread safety
|
2472
2648
|
# >>>
|
2473
2649
|
# *Note:* Access to the responses hash is synchronized for thread-safety.
|
2474
2650
|
# The receiver thread and response_handlers cannot process new responses
|
2475
2651
|
# until the block completes. Accessing either the response hash or its
|
2476
|
-
# response type arrays outside of the block is unsafe.
|
2652
|
+
# response type arrays outside of the block is unsafe. They can be safely
|
2653
|
+
# updated inside the block. Consider using #clear_responses or
|
2654
|
+
# #extract_responses instead.
|
2655
|
+
#
|
2656
|
+
# Net::IMAP will add and remove responses from the responses hash and its
|
2657
|
+
# array values, in the calling threads for commands and in the receiver
|
2658
|
+
# thread, but will not modify any responses after adding them to the
|
2659
|
+
# responses hash.
|
2477
2660
|
#
|
2478
|
-
#
|
2479
|
-
# raise ArgumentError unless a block is given.
|
2661
|
+
# ===== Clearing responses
|
2480
2662
|
#
|
2481
2663
|
# Previously unhandled responses are automatically cleared before entering a
|
2482
2664
|
# mailbox with #select or #examine. Long-lived connections can receive many
|
2483
2665
|
# unhandled server responses, which must be pruned or they will continually
|
2484
2666
|
# consume more memory. Update or clear the responses hash or arrays inside
|
2485
|
-
# the block, or
|
2667
|
+
# the block, or remove responses with #extract_responses, #clear_responses,
|
2668
|
+
# or #add_response_handler.
|
2669
|
+
#
|
2670
|
+
# ===== Missing responses
|
2486
2671
|
#
|
2487
2672
|
# Only non-+nil+ data is stored. Many important response codes have no data
|
2488
2673
|
# of their own, but are used as "tags" on the ResponseText object they are
|
@@ -2493,15 +2678,25 @@ module Net
|
|
2493
2678
|
# ResponseCode#data on tagged responses. Although some command methods do
|
2494
2679
|
# return the TaggedResponse directly, #add_response_handler must be used to
|
2495
2680
|
# handle all response codes.
|
2496
|
-
#
|
2497
|
-
# Related: #clear_responses, #response_handlers, #greeting
|
2498
2681
|
def responses(type = nil)
|
2499
2682
|
if block_given?
|
2500
2683
|
synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
|
2501
2684
|
elsif type
|
2502
|
-
|
2685
|
+
synchronize { @responses[type.to_s.upcase].dup.freeze }
|
2503
2686
|
else
|
2504
|
-
|
2687
|
+
case config.responses_without_block
|
2688
|
+
when :raise
|
2689
|
+
raise ArgumentError, RESPONSES_DEPRECATION_MSG
|
2690
|
+
when :warn
|
2691
|
+
warn(RESPONSES_DEPRECATION_MSG, uplevel: 1)
|
2692
|
+
when :frozen_dup
|
2693
|
+
synchronize {
|
2694
|
+
responses = @responses.transform_values(&:freeze)
|
2695
|
+
responses.default_proc = nil
|
2696
|
+
responses.default = [].freeze
|
2697
|
+
return responses.freeze
|
2698
|
+
}
|
2699
|
+
end
|
2505
2700
|
@responses
|
2506
2701
|
end
|
2507
2702
|
end
|
@@ -2516,7 +2711,7 @@ module Net
|
|
2516
2711
|
# Clearing responses is synchronized with other threads. The lock is
|
2517
2712
|
# released before returning.
|
2518
2713
|
#
|
2519
|
-
# Related: #responses, #response_handlers
|
2714
|
+
# Related: #extract_responses, #responses, #response_handlers
|
2520
2715
|
def clear_responses(type = nil)
|
2521
2716
|
synchronize {
|
2522
2717
|
if type
|
@@ -2530,6 +2725,30 @@ module Net
|
|
2530
2725
|
.freeze
|
2531
2726
|
end
|
2532
2727
|
|
2728
|
+
# :call-seq:
|
2729
|
+
# extract_responses(type) {|response| ... } -> array
|
2730
|
+
#
|
2731
|
+
# Yields all of the unhandled #responses for a single response +type+.
|
2732
|
+
# Removes and returns the responses for which the block returns a true
|
2733
|
+
# value.
|
2734
|
+
#
|
2735
|
+
# Extracting responses is synchronized with other threads. The lock is
|
2736
|
+
# released before returning.
|
2737
|
+
#
|
2738
|
+
# Related: #responses, #clear_responses
|
2739
|
+
def extract_responses(type)
|
2740
|
+
type = String.try_convert(type) or
|
2741
|
+
raise ArgumentError, "type must be a string"
|
2742
|
+
raise ArgumentError, "must provide a block" unless block_given?
|
2743
|
+
extracted = []
|
2744
|
+
responses(type) do |all|
|
2745
|
+
all.reject! do |response|
|
2746
|
+
extracted << response if yield response
|
2747
|
+
end
|
2748
|
+
end
|
2749
|
+
extracted
|
2750
|
+
end
|
2751
|
+
|
2533
2752
|
# Returns all response handlers, including those that are added internally
|
2534
2753
|
# by commands. Each response handler will be called with every new
|
2535
2754
|
# UntaggedResponse, TaggedResponse, and ContinuationRequest.
|
@@ -2559,6 +2778,10 @@ module Net
|
|
2559
2778
|
# end
|
2560
2779
|
# }
|
2561
2780
|
#
|
2781
|
+
# Response handlers can also be added when the client is created before the
|
2782
|
+
# receiver thread is started, by the +response_handlers+ argument to ::new.
|
2783
|
+
# This ensures every server response is handled, including the #greeting.
|
2784
|
+
#
|
2562
2785
|
# Related: #remove_response_handler, #response_handlers
|
2563
2786
|
def add_response_handler(handler = nil, &block)
|
2564
2787
|
raise ArgumentError, "two Procs are passed" if handler && block
|
@@ -2582,11 +2805,10 @@ module Net
|
|
2582
2805
|
PORT = 143 # :nodoc:
|
2583
2806
|
SSL_PORT = 993 # :nodoc:
|
2584
2807
|
|
2585
|
-
@@debug = false
|
2586
|
-
|
2587
2808
|
def start_imap_connection
|
2588
2809
|
@greeting = get_server_greeting
|
2589
2810
|
@capabilities = capabilities_from_resp_code @greeting
|
2811
|
+
@response_handlers.each do |handler| handler.call(@greeting) end
|
2590
2812
|
@receiver_thread = start_receiver_thread
|
2591
2813
|
rescue Exception
|
2592
2814
|
@sock.close
|
@@ -2611,12 +2833,12 @@ module Net
|
|
2611
2833
|
end
|
2612
2834
|
|
2613
2835
|
def tcp_socket(host, port)
|
2614
|
-
s = Socket.tcp(host, port, :connect_timeout =>
|
2836
|
+
s = Socket.tcp(host, port, :connect_timeout => open_timeout)
|
2615
2837
|
s.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, true)
|
2616
2838
|
s
|
2617
2839
|
rescue Errno::ETIMEDOUT
|
2618
2840
|
raise Net::OpenTimeout, "Timeout to open TCP connection to " +
|
2619
|
-
"#{host}:#{port} (exceeds #{
|
2841
|
+
"#{host}:#{port} (exceeds #{open_timeout} seconds)"
|
2620
2842
|
end
|
2621
2843
|
|
2622
2844
|
def receive_responses
|
@@ -2715,23 +2937,10 @@ module Net
|
|
2715
2937
|
end
|
2716
2938
|
|
2717
2939
|
def get_response
|
2718
|
-
buff =
|
2719
|
-
while true
|
2720
|
-
s = @sock.gets(CRLF)
|
2721
|
-
break unless s
|
2722
|
-
buff.concat(s)
|
2723
|
-
if /\{(\d+)\}\r\n/n =~ s
|
2724
|
-
s = @sock.read($1.to_i)
|
2725
|
-
buff.concat(s)
|
2726
|
-
else
|
2727
|
-
break
|
2728
|
-
end
|
2729
|
-
end
|
2940
|
+
buff = @reader.read_response_buffer
|
2730
2941
|
return nil if buff.length == 0
|
2731
|
-
if
|
2732
|
-
|
2733
|
-
end
|
2734
|
-
return @parser.parse(buff)
|
2942
|
+
$stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
|
2943
|
+
@parser.parse(buff)
|
2735
2944
|
end
|
2736
2945
|
|
2737
2946
|
#############################
|
@@ -2807,7 +3016,7 @@ module Net
|
|
2807
3016
|
|
2808
3017
|
def put_string(str)
|
2809
3018
|
@sock.print(str)
|
2810
|
-
if
|
3019
|
+
if config.debug?
|
2811
3020
|
if @debug_output_bol
|
2812
3021
|
$stderr.print("C: ")
|
2813
3022
|
end
|
@@ -2932,9 +3141,10 @@ module Net
|
|
2932
3141
|
raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
|
2933
3142
|
raise "cannot start TLS without SSLContext" unless ssl_ctx
|
2934
3143
|
@sock = SSLSocket.new(@sock, ssl_ctx)
|
3144
|
+
@reader = ResponseReader.new(self, @sock)
|
2935
3145
|
@sock.sync_close = true
|
2936
3146
|
@sock.hostname = @host if @sock.respond_to? :hostname=
|
2937
|
-
ssl_socket_connect(@sock,
|
3147
|
+
ssl_socket_connect(@sock, open_timeout)
|
2938
3148
|
if ssl_ctx.verify_mode != VERIFY_NONE
|
2939
3149
|
@sock.post_connection_check(@host)
|
2940
3150
|
@tls_verified = true
|
@@ -2959,6 +3169,7 @@ module Net
|
|
2959
3169
|
end
|
2960
3170
|
|
2961
3171
|
require_relative "imap/errors"
|
3172
|
+
require_relative "imap/config"
|
2962
3173
|
require_relative "imap/command_data"
|
2963
3174
|
require_relative "imap/data_encoding"
|
2964
3175
|
require_relative "imap/flags"
|