net-imap 0.3.8 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/lib/net/imap/errors.rb +54 -0
- data/lib/net/imap/response_parser.rb +1 -1
- data/lib/net/imap/response_reader.rb +75 -0
- data/lib/net/imap.rb +148 -37
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cb2a06dab230abb1358956fd53b2bdf4235a5443ab81d2e4d95df1d253fb4618
|
|
4
|
+
data.tar.gz: df7e4bf879551b5dfde3affca7c391d8fb2b2d6457247bb06567c4500e7c4a81
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 180b80a10c10cb1bfc5267e665d256b7c551260cc7069ecebfeaa0673f1a09b552284f506e0b25e3dbd9bbb75a0d5bf9501c8428ba3a645ff163326cf2215a82
|
|
7
|
+
data.tar.gz: b7f47f69d736ba0586d910154d2a94981d12ddcf5db9f95b4ec9d4909d463405ee09c3073dc7060825d66e028edbe269138d41697faa99a09128750b13455f6c
|
data/Gemfile
CHANGED
data/lib/net/imap/errors.rb
CHANGED
|
@@ -11,6 +11,40 @@ module Net
|
|
|
11
11
|
class DataFormatError < Error
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
# Error raised when the socket cannot be read, due to a configured limit.
|
|
15
|
+
class ResponseReadError < Error
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Error raised when a response is larger than IMAP#max_response_size.
|
|
19
|
+
class ResponseTooLargeError < ResponseReadError
|
|
20
|
+
attr_reader :bytes_read, :literal_size
|
|
21
|
+
attr_reader :max_response_size
|
|
22
|
+
|
|
23
|
+
def initialize(msg = nil, *args,
|
|
24
|
+
bytes_read: nil,
|
|
25
|
+
literal_size: nil,
|
|
26
|
+
max_response_size: nil,
|
|
27
|
+
**kwargs)
|
|
28
|
+
@bytes_read = bytes_read
|
|
29
|
+
@literal_size = literal_size
|
|
30
|
+
@max_response_size = max_response_size
|
|
31
|
+
msg ||= [
|
|
32
|
+
"Response size", response_size_msg, "exceeds max_response_size",
|
|
33
|
+
max_response_size && "(#{max_response_size}B)",
|
|
34
|
+
].compact.join(" ")
|
|
35
|
+
return super(msg, *args) if kwargs.empty? # ruby 2.6 compatibility
|
|
36
|
+
super(msg, *args, **kwargs)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def response_size_msg
|
|
42
|
+
if bytes_read && literal_size
|
|
43
|
+
"(#{bytes_read}B read + #{literal_size}B literal)"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
14
48
|
# Error raised when a response from the server is non-parseable.
|
|
15
49
|
class ResponseParseError < Error
|
|
16
50
|
end
|
|
@@ -47,7 +81,27 @@ module Net
|
|
|
47
81
|
class ByeResponseError < ResponseError
|
|
48
82
|
end
|
|
49
83
|
|
|
84
|
+
# Error raised when the server sends an invalid response.
|
|
85
|
+
#
|
|
86
|
+
# This is different from UnknownResponseError: the response has been
|
|
87
|
+
# rejected. Although it may be parsable, the server is forbidden from
|
|
88
|
+
# sending it in the current context. The client should automatically
|
|
89
|
+
# disconnect, abruptly (without logout).
|
|
90
|
+
#
|
|
91
|
+
# Note that InvalidResponseError does not inherit from ResponseError: it
|
|
92
|
+
# can be raised before the response is fully parsed. A related
|
|
93
|
+
# ResponseParseError or ResponseError may be the #cause.
|
|
94
|
+
class InvalidResponseError < Error
|
|
95
|
+
end
|
|
96
|
+
|
|
50
97
|
# Error raised upon an unknown response from the server.
|
|
98
|
+
#
|
|
99
|
+
# This is different from InvalidResponseError: the response may be a
|
|
100
|
+
# valid extension response and the server may be allowed to send it in
|
|
101
|
+
# this context, but Net::IMAP either does not know how to parse it or
|
|
102
|
+
# how to handle it. This could result from enabling unknown or
|
|
103
|
+
# unhandled extensions. The connection may still be usable,
|
|
104
|
+
# but—depending on context—it may be prudent to disconnect.
|
|
51
105
|
class UnknownResponseError < ResponseError
|
|
52
106
|
end
|
|
53
107
|
|
|
@@ -1382,7 +1382,7 @@ module Net
|
|
|
1382
1382
|
when T_NUMBER then [Integer(token.value)]
|
|
1383
1383
|
when T_ATOM
|
|
1384
1384
|
entries = uid_set__ranges(token.value)
|
|
1385
|
-
if (count = entries.sum(&:
|
|
1385
|
+
if (count = entries.sum(&:size)) > MAX_UID_SET_SIZE
|
|
1386
1386
|
parse_error("uid-set is too large: %d > 10k", count)
|
|
1387
1387
|
end
|
|
1388
1388
|
entries.flat_map(&:to_a)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Net
|
|
4
|
+
class IMAP
|
|
5
|
+
# See https://www.rfc-editor.org/rfc/rfc9051#section-2.2.2
|
|
6
|
+
class ResponseReader # :nodoc:
|
|
7
|
+
attr_reader :client
|
|
8
|
+
|
|
9
|
+
def initialize(client, sock)
|
|
10
|
+
@client, @sock = client, sock
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def read_response_buffer
|
|
14
|
+
@buff = String.new
|
|
15
|
+
catch :eof do
|
|
16
|
+
while true
|
|
17
|
+
read_line
|
|
18
|
+
break unless (@literal_size = get_literal_size)
|
|
19
|
+
read_literal
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
buff
|
|
23
|
+
ensure
|
|
24
|
+
@buff = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :buff, :literal_size
|
|
30
|
+
|
|
31
|
+
def bytes_read; buff.bytesize end
|
|
32
|
+
def empty?; buff.empty? end
|
|
33
|
+
def done?; line_done? && !get_literal_size end
|
|
34
|
+
def line_done?; buff.end_with?(CRLF) end
|
|
35
|
+
def get_literal_size; /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i end
|
|
36
|
+
|
|
37
|
+
def read_line
|
|
38
|
+
buff << (@sock.gets(CRLF, read_limit) or throw :eof)
|
|
39
|
+
max_response_remaining! unless line_done?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def read_literal
|
|
43
|
+
# check before allocating memory for literal
|
|
44
|
+
max_response_remaining!
|
|
45
|
+
literal = String.new(capacity: literal_size)
|
|
46
|
+
buff << (@sock.read(read_limit(literal_size), literal) or throw :eof)
|
|
47
|
+
ensure
|
|
48
|
+
@literal_size = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def read_limit(limit = nil)
|
|
52
|
+
[limit, max_response_remaining!].compact.min
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def max_response_size; client.max_response_size end
|
|
56
|
+
def max_response_remaining; max_response_size &.- bytes_read end
|
|
57
|
+
def response_too_large?; max_response_size &.< min_response_size end
|
|
58
|
+
def min_response_size; bytes_read + min_response_remaining end
|
|
59
|
+
|
|
60
|
+
def min_response_remaining
|
|
61
|
+
empty? ? 3 : done? ? 0 : (literal_size || 0) + 2
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def max_response_remaining!
|
|
65
|
+
return max_response_remaining unless response_too_large?
|
|
66
|
+
raise ResponseTooLargeError.new(
|
|
67
|
+
max_response_size: max_response_size,
|
|
68
|
+
bytes_read: bytes_read,
|
|
69
|
+
literal_size: literal_size,
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/net/imap.rb
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
|
238
|
-
# the
|
|
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
|
|
244
|
-
# mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
|
245
|
-
# Enters the
|
|
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
|
|
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
|
|
258
|
-
# the
|
|
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
|
|
266
|
-
# - #examine: Open a mailbox read-only, and enter the
|
|
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
|
|
293
|
-
# following commands are valid in the
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
742
|
+
VERSION = "0.3.10"
|
|
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 @@ module Net
|
|
|
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.
|
|
@@ -938,8 +1014,11 @@ module Net
|
|
|
938
1014
|
# unsolicited untagged response immeditely _after_ #starttls completes.
|
|
939
1015
|
#
|
|
940
1016
|
def starttls(options = {}, verify = true)
|
|
941
|
-
|
|
1017
|
+
handled = false
|
|
1018
|
+
error = nil
|
|
1019
|
+
ok = send_command("STARTTLS") do |resp|
|
|
942
1020
|
if resp.kind_of?(TaggedResponse) && resp.name == "OK"
|
|
1021
|
+
handled = true
|
|
943
1022
|
begin
|
|
944
1023
|
# for backward compatibility
|
|
945
1024
|
certs = options.to_str
|
|
@@ -948,7 +1027,21 @@ module Net
|
|
|
948
1027
|
end
|
|
949
1028
|
start_tls_session(options)
|
|
950
1029
|
end
|
|
1030
|
+
rescue Exception => error
|
|
1031
|
+
raise # note that the error backtrace is in the receiver_thread
|
|
1032
|
+
end
|
|
1033
|
+
if error
|
|
1034
|
+
disconnect
|
|
1035
|
+
raise error
|
|
951
1036
|
end
|
|
1037
|
+
unless handled
|
|
1038
|
+
disconnect
|
|
1039
|
+
raise InvalidResponseError,
|
|
1040
|
+
"STARTTLS handler was bypassed, although server responded %p" % [
|
|
1041
|
+
ok.raw_data.chomp
|
|
1042
|
+
]
|
|
1043
|
+
end
|
|
1044
|
+
ok
|
|
952
1045
|
end
|
|
953
1046
|
|
|
954
1047
|
# :call-seq:
|
|
@@ -1960,6 +2053,11 @@ module Net
|
|
|
1960
2053
|
# end
|
|
1961
2054
|
# }
|
|
1962
2055
|
#
|
|
2056
|
+
# Response handlers can also be added when the client is created before the
|
|
2057
|
+
# receiver thread is started, by the +response_handlers+ argument to ::new.
|
|
2058
|
+
# This ensures every server response is handled, including the #greeting.
|
|
2059
|
+
#
|
|
2060
|
+
# Related: #remove_response_handler, #response_handlers
|
|
1963
2061
|
def add_response_handler(handler = nil, &block)
|
|
1964
2062
|
raise ArgumentError, "two Procs are passed" if handler && block
|
|
1965
2063
|
@response_handlers.push(block || handler)
|
|
@@ -1995,6 +2093,13 @@ module Net
|
|
|
1995
2093
|
# OpenSSL::SSL::SSLContext#set_params as parameters.
|
|
1996
2094
|
# open_timeout:: Seconds to wait until a connection is opened
|
|
1997
2095
|
# idle_response_timeout:: Seconds to wait until an IDLE response is received
|
|
2096
|
+
# response_handlers:: A list of response handlers to be added before the
|
|
2097
|
+
# receiver thread is started. This ensures every server
|
|
2098
|
+
# response is handled, including the #greeting. Note
|
|
2099
|
+
# that the greeting is handled in the current thread,
|
|
2100
|
+
# but all other responses are handled in the receiver
|
|
2101
|
+
# thread.
|
|
2102
|
+
# max_response_size:: See #max_response_size.
|
|
1998
2103
|
#
|
|
1999
2104
|
# The most common errors are:
|
|
2000
2105
|
#
|
|
@@ -2025,8 +2130,10 @@ module Net
|
|
|
2025
2130
|
@tagno = 0
|
|
2026
2131
|
@open_timeout = options[:open_timeout] || 30
|
|
2027
2132
|
@idle_response_timeout = options[:idle_response_timeout] || 5
|
|
2133
|
+
@max_response_size = options[:max_response_size]
|
|
2028
2134
|
@parser = ResponseParser.new
|
|
2029
2135
|
@sock = tcp_socket(@host, @port)
|
|
2136
|
+
@reader = ResponseReader.new(self, @sock)
|
|
2030
2137
|
begin
|
|
2031
2138
|
if options[:ssl]
|
|
2032
2139
|
start_tls_session(options[:ssl])
|
|
@@ -2037,6 +2144,7 @@ module Net
|
|
|
2037
2144
|
@responses = Hash.new([].freeze)
|
|
2038
2145
|
@tagged_responses = {}
|
|
2039
2146
|
@response_handlers = []
|
|
2147
|
+
options[:response_handlers]&.each do |h| add_response_handler(h) end
|
|
2040
2148
|
@tagged_response_arrival = new_cond
|
|
2041
2149
|
@continued_command_tag = nil
|
|
2042
2150
|
@continuation_request_arrival = new_cond
|
|
@@ -2053,6 +2161,7 @@ module Net
|
|
|
2053
2161
|
if @greeting.name == "BYE"
|
|
2054
2162
|
raise ByeResponseError, @greeting
|
|
2055
2163
|
end
|
|
2164
|
+
@response_handlers.each do |handler| handler.call(@greeting) end
|
|
2056
2165
|
|
|
2057
2166
|
@client_thread = Thread.current
|
|
2058
2167
|
@receiver_thread = Thread.start {
|
|
@@ -2176,25 +2285,14 @@ module Net
|
|
|
2176
2285
|
end
|
|
2177
2286
|
|
|
2178
2287
|
def get_response
|
|
2179
|
-
buff =
|
|
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
|
|
2288
|
+
buff = @reader.read_response_buffer
|
|
2191
2289
|
return nil if buff.length == 0
|
|
2192
|
-
if @@debug
|
|
2193
|
-
|
|
2194
|
-
end
|
|
2195
|
-
return @parser.parse(buff)
|
|
2290
|
+
$stderr.print(buff.gsub(/^/n, "S: ")) if @@debug
|
|
2291
|
+
@parser.parse(buff)
|
|
2196
2292
|
end
|
|
2197
2293
|
|
|
2294
|
+
#############################
|
|
2295
|
+
|
|
2198
2296
|
def record_response(name, data)
|
|
2199
2297
|
unless @responses.has_key?(name)
|
|
2200
2298
|
@responses[name] = []
|
|
@@ -2213,6 +2311,7 @@ module Net
|
|
|
2213
2311
|
put_string(" ")
|
|
2214
2312
|
send_data(i, tag)
|
|
2215
2313
|
end
|
|
2314
|
+
guard_against_tagged_response_skipping_handler!(tag)
|
|
2216
2315
|
put_string(CRLF)
|
|
2217
2316
|
if cmd == "LOGOUT"
|
|
2218
2317
|
@logout_command_tag = tag
|
|
@@ -2228,6 +2327,17 @@ module Net
|
|
|
2228
2327
|
end
|
|
2229
2328
|
end
|
|
2230
2329
|
end
|
|
2330
|
+
rescue InvalidResponseError
|
|
2331
|
+
disconnect
|
|
2332
|
+
raise
|
|
2333
|
+
end
|
|
2334
|
+
|
|
2335
|
+
def guard_against_tagged_response_skipping_handler!(tag)
|
|
2336
|
+
return unless (resp = @tagged_responses[tag])&.name&.upcase == "OK"
|
|
2337
|
+
raise(InvalidResponseError,
|
|
2338
|
+
"Server sent tagged 'OK' before command was finished: %p. " \
|
|
2339
|
+
"This could indicate a malicious server or client-side " \
|
|
2340
|
+
"command injection. Disconnecting." % [resp.raw_data.chomp])
|
|
2231
2341
|
end
|
|
2232
2342
|
|
|
2233
2343
|
def generate_tag
|
|
@@ -2372,6 +2482,7 @@ module Net
|
|
|
2372
2482
|
context.verify_callback = VerifyCallbackProc
|
|
2373
2483
|
end
|
|
2374
2484
|
@sock = SSLSocket.new(@sock, context)
|
|
2485
|
+
@reader = ResponseReader.new(self, @sock)
|
|
2375
2486
|
@sock.sync_close = true
|
|
2376
2487
|
@sock.hostname = @host if @sock.respond_to? :hostname=
|
|
2377
2488
|
ssl_socket_connect(@sock, @open_timeout)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: net-imap
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shugo Maeda
|
|
8
8
|
- nicholas a. evans
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: net-protocol
|
|
@@ -97,6 +97,7 @@ files:
|
|
|
97
97
|
- lib/net/imap/flags.rb
|
|
98
98
|
- lib/net/imap/response_data.rb
|
|
99
99
|
- lib/net/imap/response_parser.rb
|
|
100
|
+
- lib/net/imap/response_reader.rb
|
|
100
101
|
- lib/net/imap/sasl.rb
|
|
101
102
|
- lib/net/imap/sasl/saslprep.rb
|
|
102
103
|
- lib/net/imap/sasl/saslprep_tables.rb
|
|
@@ -128,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
128
129
|
- !ruby/object:Gem::Version
|
|
129
130
|
version: '0'
|
|
130
131
|
requirements: []
|
|
131
|
-
rubygems_version:
|
|
132
|
+
rubygems_version: 4.0.3
|
|
132
133
|
specification_version: 4
|
|
133
134
|
summary: Ruby client api for Internet Message Access Protocol
|
|
134
135
|
test_files: []
|