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.
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 "_selected_" state, and that
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. Enters the "_logout_" state.
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 "_logout_" state.
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 "<em>not authenticated</em>" state:
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 "_authenticated_" state.
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 generally preferred. Enters the "_authenticated_"
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 "_authenticated_" state:
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 "_selected_" state.
348
- # - #examine: Open a mailbox read-only, and enter the "_selected_" state.
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 "_authenticated_"
371
- # commands, the following commands are valid in the "_selected_" state:
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 "_authenticated_" state,
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 "_authenticated_" state,
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 "_logout_" state. If the socket is still
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 "_authenticated_" state,
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.12"
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 debug mode.
739
- def self.debug
740
- return @@debug
741
- end
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
- return @@debug = val
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
- # Seconds to wait until a connection is opened.
768
- # If the IMAP object cannot open a connection within this time,
769
- # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
770
- attr_reader :open_timeout
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
- attr_reader :idle_response_timeout
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
- # [open_timeout]
815
- # Seconds to wait until a connection is opened
816
- # [idle_response_timeout]
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
- # See DeprecatedClientOptions.new for deprecated arguments.
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: nil,
875
- open_timeout: 30, idle_response_timeout: 5)
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
- send_command("STARTTLS") do |resp|
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: true, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp
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, sasl_ir: true, **props, &callback)
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 |res|
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", @idle_response_timeout)
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 unhandled responses and returns the result of the block.
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
- # Unhandled responses are stored in a hash, with arrays of
2459
- # <em>non-+nil+</em> UntaggedResponse#data keyed by UntaggedResponse#name
2460
- # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
2461
- # yield the entire responses hash. Call with +type+ to yield only the array
2462
- # of responses for that type.
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", &:last)
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
- # Calling without a block is unsafe and deprecated. Future releases will
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 use #clear_responses.
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
- raise ArgumentError, "Pass a block or use #clear_responses"
2685
+ synchronize { @responses[type.to_s.upcase].dup.freeze }
2503
2686
  else
2504
- # warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1)
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 => @open_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 #{@open_timeout} seconds)"
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 = String.new
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 @@debug
2732
- $stderr.print(buff.gsub(/^/n, "S: "))
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 @@debug
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, @open_timeout)
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"