@@ -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