@@ -43,10 +43,16 @@ module Net
4343  # To work on the messages within a mailbox, the client must 
4444  # first select that mailbox, using either #select or #examine 
4545  # (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 
4747  # mailbox becomes the _current_ mailbox, on which mail-item 
4848  # related commands implicitly operate. 
4949  # 
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+   # 
5056  # === Sequence numbers and UIDs 
5157  # 
5258  # Messages have two sorts of identifiers: message sequence 
@@ -199,6 +205,42 @@ module Net
199205  # 
200206  # This script invokes the FETCH command and the SEARCH command concurrently. 
201207  # 
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+   # 
202244  # == Errors 
203245  # 
204246  # An \IMAP server can send three different types of responses to indicate 
@@ -260,8 +302,9 @@ module Net
260302  # 
261303  # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and 
262304  #   waits for a successful server greeting before the method returns. 
305+   # - #connection_state: Returns the connection state. 
263306  # - #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. 
265308  # - #disconnect: Disconnects the connection (without sending #logout first). 
266309  # - #disconnected?: True if the connection has been closed. 
267310  # 
@@ -317,37 +360,36 @@ module Net
317360  #   <em>In general, #capable? should be used rather than explicitly sending a 
318361  #   +CAPABILITY+ command to the server.</em> 
319362  # - #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. 
321364  # 
322365  # ==== Not Authenticated state 
323366  # 
324367  # 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: 
326369  # 
327370  # - #starttls: Upgrades a clear-text connection to use TLS. 
328371  # 
329372  #   <em>Requires the +STARTTLS+ capability.</em> 
330373  # - #authenticate: Identifies the client to the server using the given 
331374  #   {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. 
333376  # 
334377  #   <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for 
335378  #   supported mechanisms.</em> 
336379  # - #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. 
339381  # 
340382  #   <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em> 
341383  # 
342384  # ==== Authenticated state 
343385  # 
344386  # In addition to the commands for any state, the following commands are valid 
345-   # in the "_authenticated_"  state: 
387+   # in the +authenticated+  state: 
346388  # 
347389  # - #enable: Enables backwards incompatible server extensions. 
348390  #   <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. 
351393  # - #create: Creates a new mailbox. 
352394  # - #delete: Permanently remove a mailbox. 
353395  # - #rename: Change the name of a mailbox. 
@@ -369,12 +411,12 @@ module Net
369411  # 
370412  # ==== Selected state 
371413  # 
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: 
374416  # 
375-   # - #close: Closes the mailbox and returns to the "_authenticated_"  state, 
417+   # - #close: Closes the mailbox and returns to the +authenticated+  state, 
376418  #   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, 
378420  #   without expunging any messages. 
379421  #   <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em> 
380422  # - #expunge: Permanently removes messages which have the Deleted flag set. 
@@ -395,7 +437,7 @@ module Net
395437  # 
396438  # ==== Logout state 
397439  # 
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 
399441  # open, Net::IMAP will close it after receiving server confirmation. 
400442  # Exceptions will be raised by \IMAP commands that have already started and 
401443  # are waiting for a response, as well as any that are called after logout. 
@@ -449,7 +491,7 @@ module Net
449491  # ==== RFC3691: +UNSELECT+ 
450492  # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included 
451493  # 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, 
453495  #   without expunging any messages. 
454496  # 
455497  # ==== RFC4314: +ACL+ 
@@ -719,14 +761,15 @@ module Net
719761  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml] 
720762  # 
721763  class  IMAP  < Protocol 
722-     VERSION  =  "0.4.19 " 
764+     VERSION  =  "0.4.21 " 
723765
724766    # Aliases for supported capabilities, to be used with the #enable command. 
725767    ENABLE_ALIASES  =  { 
726768      utf8 :          "UTF8=ACCEPT" , 
727769      "UTF8=ONLY"  =>  "UTF8=ACCEPT" , 
728770    } . freeze 
729771
772+     autoload  :ResponseReader ,  File . expand_path ( "imap/response_reader" ,  __dir__ ) 
730773    autoload  :SASL ,         File . expand_path ( "imap/sasl" ,          __dir__ ) 
731774    autoload  :SASLAdapter ,  File . expand_path ( "imap/sasl_adapter" ,  __dir__ ) 
732775    autoload  :StringPrep ,   File . expand_path ( "imap/stringprep" ,    __dir__ ) 
@@ -741,9 +784,11 @@ class IMAP < Protocol
741784    def  self . config ;  Config . global  end 
742785
743786    # Returns the global debug mode. 
787+     # Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug]. 
744788    def  self . debug ;  config . debug  end 
745789
746790    # Sets the global debug mode. 
791+     # Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=]. 
747792    def  self . debug = ( val ) 
748793      config . debug  =  val 
749794    end 
@@ -764,7 +809,7 @@ class << self
764809      alias  default_ssl_port  default_tls_port 
765810    end 
766811
767-     # Returns the initial greeting the server, an UntaggedResponse. 
812+     # Returns the initial greeting sent by  the server, an UntaggedResponse. 
768813    attr_reader  :greeting 
769814
770815    # The client configuration.  See Net::IMAP::Config. 
@@ -773,13 +818,28 @@ class << self
773818    # Net::IMAP.config. 
774819    attr_reader  :config 
775820
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]. 
780825
826+     ## 
827+     # :attr_reader: idle_response_timeout 
781828    # 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: 
783843
784844    # The hostname this client connected to 
785845    attr_reader  :host 
@@ -835,6 +895,12 @@ def idle_response_timeout; config.idle_response_timeout end
835895    # 
836896    #   See DeprecatedClientOptions.new for deprecated SSL arguments. 
837897    # 
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+     # 
838904    # [config] 
839905    #   A Net::IMAP::Config object to use as the basis for #config.  By default, 
840906    #   the global Net::IMAP.config is used. 
@@ -906,7 +972,7 @@ def idle_response_timeout; config.idle_response_timeout end
906972    # [Net::IMAP::ByeResponseError] 
907973    #   Connected to the host successfully, but it immediately said goodbye. 
908974    # 
909-     def  initialize ( host ,  port : nil ,  ssl :  nil , 
975+     def  initialize ( host ,  port : nil ,  ssl : nil ,   response_handlers :  nil , 
910976                   config : Config . global ,  **config_options ) 
911977      super ( ) 
912978      # Config options 
@@ -929,6 +995,7 @@ def initialize(host, port: nil, ssl:  nil,
929995      @receiver_thread  =  nil 
930996      @receiver_thread_exception  =  nil 
931997      @receiver_thread_terminating  =  false 
998+       response_handlers &.each  do  add_response_handler ( _1 )  end 
932999
9331000      # Client Protocol Sender (including state for currently running commands) 
9341001      @tag_prefix  =  "RUBY" 
@@ -944,6 +1011,7 @@ def initialize(host, port: nil, ssl:  nil,
9441011      # Connection 
9451012      @tls_verified  =  false 
9461013      @sock  =  tcp_socket ( @host ,  @port ) 
1014+       @reader  =  ResponseReader . new ( self ,  @sock ) 
9471015      start_tls_session  if  ssl_ctx 
9481016      start_imap_connection 
9491017
@@ -1204,6 +1272,10 @@ def logout!
12041272    # both successful.  Any error indicates that the connection has not been 
12051273    # secured. 
12061274    # 
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+     # 
12071279    # *Note:* 
12081280    # >>> 
12091281    #   Any #response_handlers added before STARTTLS should be aware that the 
@@ -2706,6 +2778,10 @@ def response_handlers
27062778    #     end 
27072779    #   } 
27082780    # 
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+     # 
27092785    # Related: #remove_response_handler, #response_handlers 
27102786    def  add_response_handler ( handler  =  nil ,  &block ) 
27112787      raise  ArgumentError ,  "two Procs are passed"  if  handler  && block 
@@ -2732,6 +2808,7 @@ def remove_response_handler(handler)
27322808    def  start_imap_connection 
27332809      @greeting         =  get_server_greeting 
27342810      @capabilities     =  capabilities_from_resp_code  @greeting 
2811+       @response_handlers . each  do  |handler | handler . call ( @greeting )  end 
27352812      @receiver_thread  =  start_receiver_thread 
27362813    rescue  Exception 
27372814      @sock . close 
@@ -2860,23 +2937,10 @@ def get_tagged_response(tag, cmd, timeout = nil)
28602937    end 
28612938
28622939    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 
28752941      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 ) 
28802944    end 
28812945
28822946    ############################# 
@@ -3077,6 +3141,7 @@ def start_tls_session
30773141      raise  "already using SSL"  if  @sock . kind_of? ( OpenSSL ::SSL ::SSLSocket ) 
30783142      raise  "cannot start TLS without SSLContext"  unless  ssl_ctx 
30793143      @sock  =  SSLSocket . new ( @sock ,  ssl_ctx ) 
3144+       @reader  =  ResponseReader . new ( self ,  @sock ) 
30803145      @sock . sync_close  =  true 
30813146      @sock . hostname  =  @host  if  @sock . respond_to?  :hostname= 
30823147      ssl_socket_connect ( @sock ,  open_timeout ) 
0 commit comments