@@ -45,10 +45,16 @@ module Net
45
45
# To work on the messages within a mailbox, the client must
46
46
# first select that mailbox, using either #select or #examine
47
47
# (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
49
49
# mailbox becomes the _current_ mailbox, on which mail-item
50
50
# related commands implicitly operate.
51
51
#
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
+ #
52
58
# === Sequence numbers and UIDs
53
59
#
54
60
# Messages have two sorts of identifiers: message sequence
@@ -126,6 +132,41 @@ module Net
126
132
#
127
133
# This script invokes the FETCH command and the SEARCH command concurrently.
128
134
#
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
+ #
129
170
# == Errors
130
171
#
131
172
# An \IMAP server can send three different types of responses to indicate
@@ -187,7 +228,7 @@ module Net
187
228
# - Net::IMAP.new: A new client connects immediately and waits for a
188
229
# successful server greeting before returning the new client object.
189
230
# - #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.
191
232
# - #disconnect: Disconnects the connection (without sending #logout first).
192
233
# - #disconnected?: True if the connection has been closed.
193
234
#
@@ -230,40 +271,39 @@ module Net
230
271
# <em>Capabilities may change after</em> #starttls, #authenticate, or #login
231
272
# <em>and cached capabilities must be reloaded.</em>
232
273
# - #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.
234
275
#
235
276
# ==== \IMAP commands for the "Not Authenticated" state
236
277
#
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:
239
280
#
240
281
# - #starttls: Upgrades a clear-text connection to use TLS.
241
282
#
242
283
# <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.
246
287
#
247
288
# <em>Requires the <tt>AUTH=#{mechanism}</tt> capability for the chosen
248
289
# mechanism.</em>
249
290
# - #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.
252
292
#
253
293
# <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
254
294
#
255
295
# ==== \IMAP commands for the "Authenticated" state
256
296
#
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:
259
299
#
260
300
#--
261
301
# - #enable: <em>Not implemented by Net::IMAP, yet.</em>
262
302
#
263
303
# <em>Requires the +ENABLE+ capability.</em>
264
304
#++
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.
267
307
# - #create: Creates a new mailbox.
268
308
# - #delete: Permanently remove a mailbox.
269
309
# - #rename: Change the name of a mailbox.
@@ -289,12 +329,12 @@ module Net
289
329
#
290
330
# ==== \IMAP commands for the "Selected" state
291
331
#
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:
294
334
#
295
- # - #close: Closes the mailbox and returns to the "_authenticated_" state,
335
+ # - #close: Closes the mailbox and returns to the +authenticated+ state,
296
336
# 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,
298
338
# without expunging any messages.
299
339
#
300
340
# <em>Requires the +UNSELECT+ capability.</em>
@@ -384,7 +424,7 @@ module Net
384
424
# ==== RFC3691: +UNSELECT+
385
425
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
386
426
# 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,
388
428
# without expunging any messages.
389
429
#
390
430
# ==== RFC4314: +ACL+
@@ -699,7 +739,9 @@ module Net
699
739
# * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
700
740
#
701
741
class IMAP < Protocol
702
- VERSION = "0.3.8"
742
+ VERSION = "0.3.9"
743
+
744
+ autoload :ResponseReader , File . expand_path ( "imap/response_reader" , __dir__ )
703
745
704
746
include MonitorMixin
705
747
if defined? ( OpenSSL ::SSL )
@@ -734,6 +776,40 @@ class IMAP < Protocol
734
776
# Seconds to wait until an IDLE response is received.
735
777
attr_reader :idle_response_timeout
736
778
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
+
737
813
attr_accessor :client_thread # :nodoc:
738
814
739
815
# Returns the debug mode.
@@ -1960,6 +2036,11 @@ def idle_done
1960
2036
# end
1961
2037
# }
1962
2038
#
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
1963
2044
def add_response_handler ( handler = nil , &block )
1964
2045
raise ArgumentError , "two Procs are passed" if handler && block
1965
2046
@response_handlers . push ( block || handler )
@@ -1995,6 +2076,13 @@ def remove_response_handler(handler)
1995
2076
# OpenSSL::SSL::SSLContext#set_params as parameters.
1996
2077
# open_timeout:: Seconds to wait until a connection is opened
1997
2078
# 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.
1998
2086
#
1999
2087
# The most common errors are:
2000
2088
#
@@ -2025,8 +2113,10 @@ def initialize(host, port_or_options = {},
2025
2113
@tagno = 0
2026
2114
@open_timeout = options [ :open_timeout ] || 30
2027
2115
@idle_response_timeout = options [ :idle_response_timeout ] || 5
2116
+ @max_response_size = options [ :max_response_size ]
2028
2117
@parser = ResponseParser . new
2029
2118
@sock = tcp_socket ( @host , @port )
2119
+ @reader = ResponseReader . new ( self , @sock )
2030
2120
begin
2031
2121
if options [ :ssl ]
2032
2122
start_tls_session ( options [ :ssl ] )
@@ -2037,6 +2127,7 @@ def initialize(host, port_or_options = {},
2037
2127
@responses = Hash . new ( [ ] . freeze )
2038
2128
@tagged_responses = { }
2039
2129
@response_handlers = [ ]
2130
+ options [ :response_handlers ] &.each do |h | add_response_handler ( h ) end
2040
2131
@tagged_response_arrival = new_cond
2041
2132
@continued_command_tag = nil
2042
2133
@continuation_request_arrival = new_cond
@@ -2053,6 +2144,7 @@ def initialize(host, port_or_options = {},
2053
2144
if @greeting . name == "BYE"
2054
2145
raise ByeResponseError , @greeting
2055
2146
end
2147
+ @response_handlers . each do |handler | handler . call ( @greeting ) end
2056
2148
2057
2149
@client_thread = Thread . current
2058
2150
@receiver_thread = Thread . start {
@@ -2176,25 +2268,14 @@ def get_tagged_response(tag, cmd, timeout = nil)
2176
2268
end
2177
2269
2178
2270
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
2191
2272
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 )
2196
2275
end
2197
2276
2277
+ #############################
2278
+
2198
2279
def record_response ( name , data )
2199
2280
unless @responses . has_key? ( name )
2200
2281
@responses [ name ] = [ ]
@@ -2372,6 +2453,7 @@ def start_tls_session(params = {})
2372
2453
context . verify_callback = VerifyCallbackProc
2373
2454
end
2374
2455
@sock = SSLSocket . new ( @sock , context )
2456
+ @reader = ResponseReader . new ( self , @sock )
2375
2457
@sock . sync_close = true
2376
2458
@sock . hostname = @host if @sock . respond_to? :hostname=
2377
2459
ssl_socket_connect ( @sock , @open_timeout )
0 commit comments