@@ -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
#
@@ -317,37 +360,36 @@ module Net
317
360
# <em>In general, #capable? should be used rather than explicitly sending a
318
361
# +CAPABILITY+ command to the server.</em>
319
362
# - #noop: Allows the server to send unsolicited untagged #responses.
320
- # - #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.
321
364
#
322
365
# ==== Not Authenticated state
323
366
#
324
367
# In addition to the commands for any state, the following commands are valid
325
- # in the "<em>not authenticated</em>" state:
368
+ # in the +not_authenticated+ state:
326
369
#
327
370
# - #starttls: Upgrades a clear-text connection to use TLS.
328
371
#
329
372
# <em>Requires the +STARTTLS+ capability.</em>
330
373
# - #authenticate: Identifies the client to the server using the given
331
374
# {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
332
- # and credentials. Enters the "_authenticated_" state.
375
+ # and credentials. Enters the +authenticated+ state.
333
376
#
334
377
# <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
335
378
# supported mechanisms.</em>
336
379
# - #login: Identifies the client to the server using a plain text password.
337
- # Using #authenticate is generally preferred. Enters the "_authenticated_"
338
- # state.
380
+ # Using #authenticate is preferred. Enters the +authenticated+ state.
339
381
#
340
382
# <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
341
383
#
342
384
# ==== Authenticated state
343
385
#
344
386
# In addition to the commands for any state, the following commands are valid
345
- # in the "_authenticated_" state:
387
+ # in the +authenticated+ state:
346
388
#
347
389
# - #enable: Enables backwards incompatible server extensions.
348
390
# <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
349
- # - #select: Open a mailbox and enter the "_selected_" state.
350
- # - #examine: Open a mailbox read-only, and enter the "_selected_" state.
391
+ # - #select: Open a mailbox and enter the +selected+ state.
392
+ # - #examine: Open a mailbox read-only, and enter the +selected+ state.
351
393
# - #create: Creates a new mailbox.
352
394
# - #delete: Permanently remove a mailbox.
353
395
# - #rename: Change the name of a mailbox.
@@ -369,12 +411,12 @@ module Net
369
411
#
370
412
# ==== Selected state
371
413
#
372
- # In addition to the commands for any state and the "_authenticated_"
373
- # 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:
374
416
#
375
- # - #close: Closes the mailbox and returns to the "_authenticated_" state,
417
+ # - #close: Closes the mailbox and returns to the +authenticated+ state,
376
418
# expunging deleted messages, unless the mailbox was opened as read-only.
377
- # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
419
+ # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
378
420
# without expunging any messages.
379
421
# <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
380
422
# - #expunge: Permanently removes messages which have the Deleted flag set.
@@ -395,7 +437,7 @@ module Net
395
437
#
396
438
# ==== Logout state
397
439
#
398
- # 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
399
441
# open, Net::IMAP will close it after receiving server confirmation.
400
442
# Exceptions will be raised by \IMAP commands that have already started and
401
443
# are waiting for a response, as well as any that are called after logout.
@@ -449,7 +491,7 @@ module Net
449
491
# ==== RFC3691: +UNSELECT+
450
492
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
451
493
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
452
- # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
494
+ # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
453
495
# without expunging any messages.
454
496
#
455
497
# ==== RFC4314: +ACL+
@@ -719,14 +761,15 @@ module Net
719
761
# * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
720
762
#
721
763
class IMAP < Protocol
722
- VERSION = "0.4.19 "
764
+ VERSION = "0.4.21 "
723
765
724
766
# Aliases for supported capabilities, to be used with the #enable command.
725
767
ENABLE_ALIASES = {
726
768
utf8 : "UTF8=ACCEPT" ,
727
769
"UTF8=ONLY" => "UTF8=ACCEPT" ,
728
770
} . freeze
729
771
772
+ autoload :ResponseReader , File . expand_path ( "imap/response_reader" , __dir__ )
730
773
autoload :SASL , File . expand_path ( "imap/sasl" , __dir__ )
731
774
autoload :SASLAdapter , File . expand_path ( "imap/sasl_adapter" , __dir__ )
732
775
autoload :StringPrep , File . expand_path ( "imap/stringprep" , __dir__ )
@@ -741,9 +784,11 @@ class IMAP < Protocol
741
784
def self . config ; Config . global end
742
785
743
786
# Returns the global debug mode.
787
+ # Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
744
788
def self . debug ; config . debug end
745
789
746
790
# Sets the global debug mode.
791
+ # Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
747
792
def self . debug = ( val )
748
793
config . debug = val
749
794
end
@@ -764,7 +809,7 @@ class << self
764
809
alias default_ssl_port default_tls_port
765
810
end
766
811
767
- # Returns the initial greeting the server, an UntaggedResponse.
812
+ # Returns the initial greeting sent by the server, an UntaggedResponse.
768
813
attr_reader :greeting
769
814
770
815
# The client configuration. See Net::IMAP::Config.
@@ -773,13 +818,28 @@ class << self
773
818
# Net::IMAP.config.
774
819
attr_reader :config
775
820
776
- # Seconds to wait until a connection is opened.
777
- # If the IMAP object cannot open a connection within this time,
778
- # it raises a Net::OpenTimeout exception. The default value is 30 seconds .
779
- def open_timeout ; config . open_timeout end
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].
780
825
826
+ ##
827
+ # :attr_reader: idle_response_timeout
781
828
# Seconds to wait until an IDLE response is received.
782
- def idle_response_timeout ; config . idle_response_timeout end
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:
783
843
784
844
# The hostname this client connected to
785
845
attr_reader :host
@@ -835,6 +895,12 @@ def idle_response_timeout; config.idle_response_timeout end
835
895
#
836
896
# See DeprecatedClientOptions.new for deprecated SSL arguments.
837
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
+ #
838
904
# [config]
839
905
# A Net::IMAP::Config object to use as the basis for #config. By default,
840
906
# the global Net::IMAP.config is used.
@@ -906,7 +972,7 @@ def idle_response_timeout; config.idle_response_timeout end
906
972
# [Net::IMAP::ByeResponseError]
907
973
# Connected to the host successfully, but it immediately said goodbye.
908
974
#
909
- def initialize ( host , port : nil , ssl : nil ,
975
+ def initialize ( host , port : nil , ssl : nil , response_handlers : nil ,
910
976
config : Config . global , **config_options )
911
977
super ( )
912
978
# Config options
@@ -929,6 +995,7 @@ def initialize(host, port: nil, ssl: nil,
929
995
@receiver_thread = nil
930
996
@receiver_thread_exception = nil
931
997
@receiver_thread_terminating = false
998
+ response_handlers &.each do add_response_handler ( _1 ) end
932
999
933
1000
# Client Protocol Sender (including state for currently running commands)
934
1001
@tag_prefix = "RUBY"
@@ -944,6 +1011,7 @@ def initialize(host, port: nil, ssl: nil,
944
1011
# Connection
945
1012
@tls_verified = false
946
1013
@sock = tcp_socket ( @host , @port )
1014
+ @reader = ResponseReader . new ( self , @sock )
947
1015
start_tls_session if ssl_ctx
948
1016
start_imap_connection
949
1017
@@ -1204,6 +1272,10 @@ def logout!
1204
1272
# both successful. Any error indicates that the connection has not been
1205
1273
# secured.
1206
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
+ #
1207
1279
# *Note:*
1208
1280
# >>>
1209
1281
# Any #response_handlers added before STARTTLS should be aware that the
@@ -2706,6 +2778,10 @@ def response_handlers
2706
2778
# end
2707
2779
# }
2708
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
+ #
2709
2785
# Related: #remove_response_handler, #response_handlers
2710
2786
def add_response_handler ( handler = nil , &block )
2711
2787
raise ArgumentError , "two Procs are passed" if handler && block
@@ -2732,6 +2808,7 @@ def remove_response_handler(handler)
2732
2808
def start_imap_connection
2733
2809
@greeting = get_server_greeting
2734
2810
@capabilities = capabilities_from_resp_code @greeting
2811
+ @response_handlers . each do |handler | handler . call ( @greeting ) end
2735
2812
@receiver_thread = start_receiver_thread
2736
2813
rescue Exception
2737
2814
@sock . close
@@ -2860,23 +2937,10 @@ def get_tagged_response(tag, cmd, timeout = nil)
2860
2937
end
2861
2938
2862
2939
def get_response
2863
- buff = String . new
2864
- while true
2865
- s = @sock . gets ( CRLF )
2866
- break unless s
2867
- buff . concat ( s )
2868
- if /\{ (\d +)\} \r \n /n =~ s
2869
- s = @sock . read ( $1. to_i )
2870
- buff . concat ( s )
2871
- else
2872
- break
2873
- end
2874
- end
2940
+ buff = @reader . read_response_buffer
2875
2941
return nil if buff . length == 0
2876
- if config . debug?
2877
- $stderr. print ( buff . gsub ( /^/n , "S: " ) )
2878
- end
2879
- return @parser . parse ( buff )
2942
+ $stderr. print ( buff . gsub ( /^/n , "S: " ) ) if config . debug?
2943
+ @parser . parse ( buff )
2880
2944
end
2881
2945
2882
2946
#############################
@@ -3077,6 +3141,7 @@ def start_tls_session
3077
3141
raise "already using SSL" if @sock . kind_of? ( OpenSSL ::SSL ::SSLSocket )
3078
3142
raise "cannot start TLS without SSLContext" unless ssl_ctx
3079
3143
@sock = SSLSocket . new ( @sock , ssl_ctx )
3144
+ @reader = ResponseReader . new ( self , @sock )
3080
3145
@sock . sync_close = true
3081
3146
@sock . hostname = @host if @sock . respond_to? :hostname=
3082
3147
ssl_socket_connect ( @sock , open_timeout )
0 commit comments