@@ -45,10 +45,16 @@ module Net
4545  # To work on the messages within a mailbox, the client must 
4646  # first select that mailbox, using either #select or #examine 
4747  # (for read-only access).  Once the client has successfully 
48-   # selected a mailbox, they enter the "_selected_"  state, and that 
48+   # selected a mailbox, they enter the +selected+  state, and that 
4949  # mailbox becomes the _current_ mailbox, on which mail-item 
5050  # related commands implicitly operate. 
5151  # 
52+   # === Connection state 
53+   # 
54+   # Once an IMAP connection is established, the connection is in one of four 
55+   # states: <tt>not authenticated</tt>, +authenticated+, +selected+, and 
56+   # +logout+.  Most commands are valid only in certain states. 
57+   # 
5258  # === Sequence numbers and UIDs 
5359  # 
5460  # Messages have two sorts of identifiers: message sequence 
@@ -126,6 +132,41 @@ module Net
126132  # 
127133  # This script invokes the FETCH command and the SEARCH command concurrently. 
128134  # 
135+   # When running multiple commands, care must be taken to avoid ambiguity.  For 
136+   # example, SEARCH responses are ambiguous about which command they are 
137+   # responding to, so search commands should not run simultaneously, unless the 
138+   # server supports +ESEARCH+ {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731] or 
139+   # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].  See {RFC9051 
140+   # §5.5}[https://www.rfc-editor.org/rfc/rfc9051.html#section-5.5] for 
141+   # other examples of command sequences which should not be pipelined. 
142+   # 
143+   # == Unbounded memory use 
144+   # 
145+   # Net::IMAP reads server responses in a separate receiver thread per client. 
146+   # Unhandled response data is saved to #responses, and response_handlers run 
147+   # inside the receiver thread.  See the list of methods for {handling server 
148+   # responses}[rdoc-ref:Net::IMAP@Handling+server+responses], below. 
149+   # 
150+   # Because the receiver thread continuously reads and saves new responses, some 
151+   # scenarios must be careful to avoid unbounded memory use: 
152+   # 
153+   # * Commands such as #list or #fetch can have an enormous number of responses. 
154+   # * Commands such as #fetch can result in an enormous size per response. 
155+   # * Long-lived connections will gradually accumulate unsolicited server 
156+   #   responses, especially +EXISTS+, +FETCH+, and +EXPUNGE+ responses. 
157+   # * A buggy or untrusted server could send inappropriate responses, which 
158+   #   could be very numerous, very large, and very rapid. 
159+   # 
160+   # Use paginated or limited versions of commands whenever possible. 
161+   # 
162+   # Use #max_response_size to impose a limit on incoming server responses 
163+   # as they are being read.  <em>This is especially important for untrusted 
164+   # servers.</em> 
165+   # 
166+   # Use #add_response_handler to handle responses after each one is received. 
167+   # Use the +response_handlers+ argument to ::new to assign response handlers 
168+   # before the receiver thread is started. 
169+   # 
129170  # == Errors 
130171  # 
131172  # An \IMAP server can send three different types of responses to indicate 
@@ -187,7 +228,7 @@ module Net
187228  # - Net::IMAP.new: A new client connects immediately and waits for a 
188229  #   successful server greeting before returning the new client object. 
189230  # - #starttls: Asks the server to upgrade a clear-text connection to use TLS. 
190-   # - #logout: Tells the server to end the session. Enters the "_logout_"  state. 
231+   # - #logout: Tells the server to end the session.   Enters the +logout+  state. 
191232  # - #disconnect: Disconnects the connection (without sending #logout first). 
192233  # - #disconnected?: True if the connection has been closed. 
193234  # 
@@ -230,40 +271,39 @@ module Net
230271  #   <em>Capabilities may change after</em> #starttls, #authenticate, or #login 
231272  #   <em>and cached capabilities must be reloaded.</em> 
232273  # - #noop: Allows the server to send unsolicited untagged #responses. 
233-   # - #logout: Tells the server to end the session. Enters the "_logout_"  state. 
274+   # - #logout: Tells the server to end the session. Enters the +logout+  state. 
234275  # 
235276  # ==== \IMAP commands for the "Not Authenticated" state 
236277  # 
237-   # In addition to the universal  commands, the following commands are valid in  
238-   # the "<em>not authenticated</em>"  state: 
278+   # In addition to the commands for any state , the following commands are valid 
279+   # in  the +not_authenticated+  state: 
239280  # 
240281  # - #starttls: Upgrades a clear-text connection to use TLS. 
241282  # 
242283  #   <em>Requires the +STARTTLS+ capability.</em> 
243-   # - #authenticate: Identifies the client to the server using a  {SASL 
244-   #   mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml].  
245-   #   Enters the "_authenticated_"  state. 
284+   # - #authenticate: Identifies the client to the server using the given  {SASL 
285+   #   mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] 
286+   #   and credentials.   Enters the +authenticated+  state. 
246287  # 
247288  #   <em>Requires the <tt>AUTH=#{mechanism}</tt> capability for the chosen 
248289  #   mechanism.</em> 
249290  # - #login: Identifies the client to the server using a plain text password. 
250-   #   Using #authenticate is generally preferred.  Enters the "_authenticated_" 
251-   #   state. 
291+   #   Using #authenticate is preferred.  Enters the +authenticated+ state. 
252292  # 
253293  #   <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em> 
254294  # 
255295  # ==== \IMAP commands for the "Authenticated" state 
256296  # 
257-   # In addition to the universal  commands, the following commands are valid in  
258-   # the "_authenticated_"  state: 
297+   # In addition to the commands for any state , the following commands are valid 
298+   # in  the +authenticated+  state: 
259299  # 
260300  #-- 
261301  # - #enable: <em>Not implemented by Net::IMAP, yet.</em> 
262302  # 
263303  #   <em>Requires the +ENABLE+ capability.</em> 
264304  #++ 
265-   # - #select:  Open a mailbox and enter the "_selected_"  state. 
266-   # - #examine: Open a mailbox read-only, and enter the "_selected_"  state. 
305+   # - #select:  Open a mailbox and enter the +selected+  state. 
306+   # - #examine: Open a mailbox read-only, and enter the +selected+  state. 
267307  # - #create: Creates a new mailbox. 
268308  # - #delete: Permanently remove a mailbox. 
269309  # - #rename: Change the name of a mailbox. 
@@ -289,12 +329,12 @@ module Net
289329  # 
290330  # ==== \IMAP commands for the "Selected" state 
291331  # 
292-   # In addition to the universal  commands and the "authenticated" commands,  the 
293-   # following commands are valid in the "_selected_"  state: 
332+   # In addition to the commands for any state and  the +authenticated+  
333+   # commands, the  following commands are valid in the +selected+  state: 
294334  # 
295-   # - #close: Closes the mailbox and returns to the "_authenticated_"  state, 
335+   # - #close: Closes the mailbox and returns to the +authenticated+  state, 
296336  #   expunging deleted messages, unless the mailbox was opened as read-only. 
297-   # - #unselect: Closes the mailbox and returns to the "_authenticated_"  state, 
337+   # - #unselect: Closes the mailbox and returns to the +authenticated+  state, 
298338  #   without expunging any messages. 
299339  # 
300340  #   <em>Requires the +UNSELECT+ capability.</em> 
@@ -384,7 +424,7 @@ module Net
384424  # ==== RFC3691: +UNSELECT+ 
385425  # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also 
386426  # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. 
387-   # - #unselect: Closes the mailbox and returns to the "_authenticated_"  state, 
427+   # - #unselect: Closes the mailbox and returns to the +authenticated+  state, 
388428  #   without expunging any messages. 
389429  # 
390430  # ==== RFC4314: +ACL+ 
@@ -699,7 +739,9 @@ module Net
699739  # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml] 
700740  # 
701741  class  IMAP  < Protocol 
702-     VERSION  =  "0.3.8" 
742+     VERSION  =  "0.3.9" 
743+ 
744+     autoload  :ResponseReader ,  File . expand_path ( "imap/response_reader" ,  __dir__ ) 
703745
704746    include  MonitorMixin 
705747    if  defined? ( OpenSSL ::SSL ) 
@@ -734,6 +776,40 @@ class IMAP < Protocol
734776    # Seconds to wait until an IDLE response is received. 
735777    attr_reader  :idle_response_timeout 
736778
779+     # The maximum allowed server response size.  When +nil+, there is no limit 
780+     # on response size. 
781+     # 
782+     # The default value is _unlimited_ (after +v0.5.8+, the default is 512 MiB). 
783+     # A _much_ lower value should be used with untrusted servers (for example, 
784+     # when connecting to a user-provided hostname).  When using a lower limit, 
785+     # message bodies should be fetched in chunks rather than all at once. 
786+     # 
787+     # <em>Please Note:</em> this only limits the size per response.  It does 
788+     # not prevent a flood of individual responses and it does not limit how 
789+     # many unhandled responses may be stored on the responses hash.  See 
790+     # Net::IMAP@Unbounded+memory+use. 
791+     # 
792+     # Socket reads are limited to the maximum remaining bytes for the current 
793+     # response: max_response_size minus the bytes that have already been read. 
794+     # When the limit is reached, or reading a +literal+ _would_ go over the 
795+     # limit, ResponseTooLargeError is raised and the connection is closed. 
796+     # See also #socket_read_limit. 
797+     # 
798+     # Note that changes will not take effect immediately, because the receiver 
799+     # thread may already be waiting for the next response using the previous 
800+     # value.  Net::IMAP#noop can force a response and enforce the new setting 
801+     # immediately. 
802+     # 
803+     # ==== Versioned Defaults 
804+     # 
805+     # Net::IMAP#max_response_size <em>was added in +v0.2.5+ and +v0.3.9+ as an 
806+     # attr_accessor, and in +v0.4.20+ and +v0.5.7+ as a delegator to a config 
807+     # attribute.</em> 
808+     # 
809+     # * original: +nil+ <em>(no limit)</em> 
810+     # * +0.5+: 512 MiB 
811+     attr_accessor  :max_response_size 
812+ 
737813    attr_accessor  :client_thread  # :nodoc: 
738814
739815    # Returns the debug mode. 
@@ -1960,6 +2036,11 @@ def idle_done
19602036    #     end 
19612037    #   } 
19622038    # 
2039+     # Response handlers can also be added when the client is created before the 
2040+     # receiver thread is started, by the +response_handlers+ argument to ::new. 
2041+     # This ensures every server response is handled, including the #greeting. 
2042+     # 
2043+     # Related: #remove_response_handler, #response_handlers 
19632044    def  add_response_handler ( handler  =  nil ,  &block ) 
19642045      raise  ArgumentError ,  "two Procs are passed"  if  handler  && block 
19652046      @response_handlers . push ( block  || handler ) 
@@ -1995,6 +2076,13 @@ def remove_response_handler(handler)
19952076    #         OpenSSL::SSL::SSLContext#set_params as parameters. 
19962077    # open_timeout:: Seconds to wait until a connection is opened 
19972078    # idle_response_timeout:: Seconds to wait until an IDLE response is received 
2079+     # response_handlers:: A list of response handlers to be added before the 
2080+     #                     receiver thread is started.  This ensures every server 
2081+     #                     response is handled, including the #greeting.  Note 
2082+     #                     that the greeting is handled in the current thread, 
2083+     #                     but all other responses are handled in the receiver 
2084+     #                     thread. 
2085+     # max_response_size:: See #max_response_size. 
19982086    # 
19992087    # The most common errors are: 
20002088    # 
@@ -2025,8 +2113,10 @@ def initialize(host, port_or_options = {},
20252113      @tagno  =  0 
20262114      @open_timeout  =  options [ :open_timeout ]  || 30 
20272115      @idle_response_timeout  =  options [ :idle_response_timeout ]  || 5 
2116+       @max_response_size      =  options [ :max_response_size ] 
20282117      @parser  =  ResponseParser . new 
20292118      @sock  =  tcp_socket ( @host ,  @port ) 
2119+       @reader  =  ResponseReader . new ( self ,  @sock ) 
20302120      begin 
20312121        if  options [ :ssl ] 
20322122          start_tls_session ( options [ :ssl ] ) 
@@ -2037,6 +2127,7 @@ def initialize(host, port_or_options = {},
20372127        @responses  =  Hash . new ( [ ] . freeze ) 
20382128        @tagged_responses  =  { } 
20392129        @response_handlers  =  [ ] 
2130+         options [ :response_handlers ] &.each  do  |h | add_response_handler ( h )  end 
20402131        @tagged_response_arrival  =  new_cond 
20412132        @continued_command_tag  =  nil 
20422133        @continuation_request_arrival  =  new_cond 
@@ -2053,6 +2144,7 @@ def initialize(host, port_or_options = {},
20532144        if  @greeting . name  == "BYE" 
20542145          raise  ByeResponseError ,  @greeting 
20552146        end 
2147+         @response_handlers . each  do  |handler | handler . call ( @greeting )  end 
20562148
20572149        @client_thread  =  Thread . current 
20582150        @receiver_thread  =  Thread . start  { 
@@ -2176,25 +2268,14 @@ def get_tagged_response(tag, cmd, timeout = nil)
21762268    end 
21772269
21782270    def  get_response 
2179-       buff  =  String . new 
2180-       while  true 
2181-         s  =  @sock . gets ( CRLF ) 
2182-         break  unless  s 
2183-         buff . concat ( s ) 
2184-         if  /\{ (\d +)\} \r \n /n  =~ s 
2185-           s  =  @sock . read ( $1. to_i ) 
2186-           buff . concat ( s ) 
2187-         else 
2188-           break 
2189-         end 
2190-       end 
2271+       buff  =  @reader . read_response_buffer 
21912272      return  nil  if  buff . length  == 0 
2192-       if  @@debug 
2193-         $stderr. print ( buff . gsub ( /^/n ,  "S: " ) ) 
2194-       end 
2195-       return  @parser . parse ( buff ) 
2273+       $stderr. print ( buff . gsub ( /^/n ,  "S: " ) )  if  @@debug 
2274+       @parser . parse ( buff ) 
21962275    end 
21972276
2277+     ############################# 
2278+ 
21982279    def  record_response ( name ,  data ) 
21992280      unless  @responses . has_key? ( name ) 
22002281        @responses [ name ]  =  [ ] 
@@ -2372,6 +2453,7 @@ def start_tls_session(params = {})
23722453        context . verify_callback  =  VerifyCallbackProc 
23732454      end 
23742455      @sock  =  SSLSocket . new ( @sock ,  context ) 
2456+       @reader  =  ResponseReader . new ( self ,  @sock ) 
23752457      @sock . sync_close  =  true 
23762458      @sock . hostname  =  @host  if  @sock . respond_to?  :hostname= 
23772459      ssl_socket_connect ( @sock ,  @open_timeout ) 
0 commit comments