net-imap 0.2.3 → 0.2.5
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/.github/workflows/test.yml +1 -1
- data/lib/net/imap/errors.rb +34 -0
- data/lib/net/imap/response_reader.rb +75 -0
- data/lib/net/imap.rb +94 -17
- metadata +4 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c96e58708687b3fb8ded9def89a66e3ca9d1d83717c76c1579928ed60fb2b52
|
4
|
+
data.tar.gz: 56c64b20eab875d49b03e7a18f46988850e38865fbd756b0e6db3d7ecaec5975
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6dc72eab65ac59b09328cad6d1ad9bcc86e4565f05416b6b7de952d2d9d4634f8f272b6e2bf36a0f386ae0acb46de506d28c3856e1a53058fa83f30edfd7275c
|
7
|
+
data.tar.gz: 6deb8133dd67519bd082ba287db7eda44500048ef30f6e25f43e6955ed4b82f96c690633375dad56af8590b578ea6df846422a8ae54d40e359bfd2795f2d4125
|
data/.github/workflows/test.yml
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
|
@@ -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
@@ -106,6 +106,41 @@ module Net
|
|
106
106
|
#
|
107
107
|
# This script invokes the FETCH command and the SEARCH command concurrently.
|
108
108
|
#
|
109
|
+
# When running multiple commands, care must be taken to avoid ambiguity. For
|
110
|
+
# example, SEARCH responses are ambiguous about which command they are
|
111
|
+
# responding to, so search commands should not run simultaneously, unless the
|
112
|
+
# server supports +ESEARCH+ {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731] or
|
113
|
+
# IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]. See {RFC9051
|
114
|
+
# §5.5}[https://www.rfc-editor.org/rfc/rfc9051.html#section-5.5] for
|
115
|
+
# other examples of command sequences which should not be pipelined.
|
116
|
+
#
|
117
|
+
# == Unbounded memory use
|
118
|
+
#
|
119
|
+
# Net::IMAP reads server responses in a separate receiver thread per client.
|
120
|
+
# Unhandled response data is saved to #responses, and response_handlers run
|
121
|
+
# inside the receiver thread. See the list of methods for {handling server
|
122
|
+
# responses}[rdoc-ref:Net::IMAP@Handling+server+responses], below.
|
123
|
+
#
|
124
|
+
# Because the receiver thread continuously reads and saves new responses, some
|
125
|
+
# scenarios must be careful to avoid unbounded memory use:
|
126
|
+
#
|
127
|
+
# * Commands such as #list or #fetch can have an enormous number of responses.
|
128
|
+
# * Commands such as #fetch can result in an enormous size per response.
|
129
|
+
# * Long-lived connections will gradually accumulate unsolicited server
|
130
|
+
# responses, especially +EXISTS+, +FETCH+, and +EXPUNGE+ responses.
|
131
|
+
# * A buggy or untrusted server could send inappropriate responses, which
|
132
|
+
# could be very numerous, very large, and very rapid.
|
133
|
+
#
|
134
|
+
# Use paginated or limited versions of commands whenever possible.
|
135
|
+
#
|
136
|
+
# Use #max_response_size to impose a limit on incoming server responses
|
137
|
+
# as they are being read. <em>This is especially important for untrusted
|
138
|
+
# servers.</em>
|
139
|
+
#
|
140
|
+
# Use #add_response_handler to handle responses after each one is received.
|
141
|
+
# Use the +response_handlers+ argument to ::new to assign response handlers
|
142
|
+
# before the receiver thread is started.
|
143
|
+
#
|
109
144
|
# == Errors
|
110
145
|
#
|
111
146
|
# An IMAP server can send three different types of responses to indicate
|
@@ -220,7 +255,9 @@ module Net
|
|
220
255
|
# Unicode", RFC-2152[https://tools.ietf.org/html/rfc2152], May 1997.
|
221
256
|
#
|
222
257
|
class IMAP < Protocol
|
223
|
-
VERSION = "0.2.
|
258
|
+
VERSION = "0.2.5"
|
259
|
+
|
260
|
+
autoload :ResponseReader, File.expand_path("imap/response_reader", __dir__)
|
224
261
|
|
225
262
|
include MonitorMixin
|
226
263
|
if defined?(OpenSSL::SSL)
|
@@ -251,6 +288,40 @@ module Net
|
|
251
288
|
# Seconds to wait until an IDLE response is received.
|
252
289
|
attr_reader :idle_response_timeout
|
253
290
|
|
291
|
+
# The maximum allowed server response size. When +nil+, there is no limit
|
292
|
+
# on response size.
|
293
|
+
#
|
294
|
+
# The default value is _unlimited_ (after +v0.5.8+, the default is 512 MiB).
|
295
|
+
# A _much_ lower value should be used with untrusted servers (for example,
|
296
|
+
# when connecting to a user-provided hostname). When using a lower limit,
|
297
|
+
# message bodies should be fetched in chunks rather than all at once.
|
298
|
+
#
|
299
|
+
# <em>Please Note:</em> this only limits the size per response. It does
|
300
|
+
# not prevent a flood of individual responses and it does not limit how
|
301
|
+
# many unhandled responses may be stored on the responses hash. See
|
302
|
+
# Net::IMAP@Unbounded+memory+use.
|
303
|
+
#
|
304
|
+
# Socket reads are limited to the maximum remaining bytes for the current
|
305
|
+
# response: max_response_size minus the bytes that have already been read.
|
306
|
+
# When the limit is reached, or reading a +literal+ _would_ go over the
|
307
|
+
# limit, ResponseTooLargeError is raised and the connection is closed.
|
308
|
+
# See also #socket_read_limit.
|
309
|
+
#
|
310
|
+
# Note that changes will not take effect immediately, because the receiver
|
311
|
+
# thread may already be waiting for the next response using the previous
|
312
|
+
# value. Net::IMAP#noop can force a response and enforce the new setting
|
313
|
+
# immediately.
|
314
|
+
#
|
315
|
+
# ==== Versioned Defaults
|
316
|
+
#
|
317
|
+
# Net::IMAP#max_response_size <em>was added in +v0.2.5+ and +v0.3.9+ as an
|
318
|
+
# attr_accessor, and in +v0.4.20+ and +v0.5.7+ as a delegator to a config
|
319
|
+
# attribute.</em>
|
320
|
+
#
|
321
|
+
# * original: +nil+ <em>(no limit)</em>
|
322
|
+
# * +0.5+: 512 MiB
|
323
|
+
attr_accessor :max_response_size
|
324
|
+
|
254
325
|
# The thread to receive exceptions.
|
255
326
|
attr_accessor :client_thread
|
256
327
|
|
@@ -955,6 +1026,11 @@ module Net
|
|
955
1026
|
# end
|
956
1027
|
# }
|
957
1028
|
#
|
1029
|
+
# Response handlers can also be added when the client is created before the
|
1030
|
+
# receiver thread is started, by the +response_handlers+ argument to ::new.
|
1031
|
+
# This ensures every server response is handled, including the #greeting.
|
1032
|
+
#
|
1033
|
+
# Related: #remove_response_handler, #response_handlers
|
958
1034
|
def add_response_handler(handler = nil, &block)
|
959
1035
|
raise ArgumentError, "two Procs are passed" if handler && block
|
960
1036
|
@response_handlers.push(block || handler)
|
@@ -1070,6 +1146,13 @@ module Net
|
|
1070
1146
|
# OpenSSL::SSL::SSLContext#set_params as parameters.
|
1071
1147
|
# open_timeout:: Seconds to wait until a connection is opened
|
1072
1148
|
# idle_response_timeout:: Seconds to wait until an IDLE response is received
|
1149
|
+
# response_handlers:: A list of response handlers to be added before the
|
1150
|
+
# receiver thread is started. This ensures every server
|
1151
|
+
# response is handled, including the #greeting. Note
|
1152
|
+
# that the greeting is handled in the current thread,
|
1153
|
+
# but all other responses are handled in the receiver
|
1154
|
+
# thread.
|
1155
|
+
# max_response_size:: See #max_response_size.
|
1073
1156
|
#
|
1074
1157
|
# The most common errors are:
|
1075
1158
|
#
|
@@ -1100,8 +1183,10 @@ module Net
|
|
1100
1183
|
@tagno = 0
|
1101
1184
|
@open_timeout = options[:open_timeout] || 30
|
1102
1185
|
@idle_response_timeout = options[:idle_response_timeout] || 5
|
1186
|
+
@max_response_size = options[:max_response_size]
|
1103
1187
|
@parser = ResponseParser.new
|
1104
1188
|
@sock = tcp_socket(@host, @port)
|
1189
|
+
@reader = ResponseReader.new(self, @sock)
|
1105
1190
|
begin
|
1106
1191
|
if options[:ssl]
|
1107
1192
|
start_tls_session(options[:ssl])
|
@@ -1112,6 +1197,7 @@ module Net
|
|
1112
1197
|
@responses = Hash.new([].freeze)
|
1113
1198
|
@tagged_responses = {}
|
1114
1199
|
@response_handlers = []
|
1200
|
+
options[:response_handlers]&.each do |h| add_response_handler(h) end
|
1115
1201
|
@tagged_response_arrival = new_cond
|
1116
1202
|
@continued_command_tag = nil
|
1117
1203
|
@continuation_request_arrival = new_cond
|
@@ -1128,6 +1214,7 @@ module Net
|
|
1128
1214
|
if @greeting.name == "BYE"
|
1129
1215
|
raise ByeResponseError, @greeting
|
1130
1216
|
end
|
1217
|
+
@response_handlers.each do |handler| handler.call(@greeting) end
|
1131
1218
|
|
1132
1219
|
@client_thread = Thread.current
|
1133
1220
|
@receiver_thread = Thread.start {
|
@@ -1251,25 +1338,14 @@ module Net
|
|
1251
1338
|
end
|
1252
1339
|
|
1253
1340
|
def get_response
|
1254
|
-
buff =
|
1255
|
-
while true
|
1256
|
-
s = @sock.gets(CRLF)
|
1257
|
-
break unless s
|
1258
|
-
buff.concat(s)
|
1259
|
-
if /\{(\d+)\}\r\n/n =~ s
|
1260
|
-
s = @sock.read($1.to_i)
|
1261
|
-
buff.concat(s)
|
1262
|
-
else
|
1263
|
-
break
|
1264
|
-
end
|
1265
|
-
end
|
1341
|
+
buff = @reader.read_response_buffer
|
1266
1342
|
return nil if buff.length == 0
|
1267
|
-
if @@debug
|
1268
|
-
|
1269
|
-
end
|
1270
|
-
return @parser.parse(buff)
|
1343
|
+
$stderr.print(buff.gsub(/^/n, "S: ")) if @@debug
|
1344
|
+
@parser.parse(buff)
|
1271
1345
|
end
|
1272
1346
|
|
1347
|
+
#############################
|
1348
|
+
|
1273
1349
|
def record_response(name, data)
|
1274
1350
|
unless @responses.has_key?(name)
|
1275
1351
|
@responses[name] = []
|
@@ -1447,6 +1523,7 @@ module Net
|
|
1447
1523
|
context.verify_callback = VerifyCallbackProc
|
1448
1524
|
end
|
1449
1525
|
@sock = SSLSocket.new(@sock, context)
|
1526
|
+
@reader = ResponseReader.new(self, @sock)
|
1450
1527
|
@sock.sync_close = true
|
1451
1528
|
@sock.hostname = @host if @sock.respond_to? :hostname=
|
1452
1529
|
ssl_socket_connect(@sock, @open_timeout)
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: net-imap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shugo Maeda
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: net-protocol
|
@@ -77,6 +76,7 @@ files:
|
|
77
76
|
- lib/net/imap/flags.rb
|
78
77
|
- lib/net/imap/response_data.rb
|
79
78
|
- lib/net/imap/response_parser.rb
|
79
|
+
- lib/net/imap/response_reader.rb
|
80
80
|
- net-imap.gemspec
|
81
81
|
homepage: https://github.com/ruby/net-imap
|
82
82
|
licenses:
|
@@ -85,7 +85,6 @@ licenses:
|
|
85
85
|
metadata:
|
86
86
|
homepage_uri: https://github.com/ruby/net-imap
|
87
87
|
source_code_uri: https://github.com/ruby/net-imap
|
88
|
-
post_install_message:
|
89
88
|
rdoc_options: []
|
90
89
|
require_paths:
|
91
90
|
- lib
|
@@ -100,8 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
99
|
- !ruby/object:Gem::Version
|
101
100
|
version: '0'
|
102
101
|
requirements: []
|
103
|
-
rubygems_version: 3.
|
104
|
-
signing_key:
|
102
|
+
rubygems_version: 3.6.8
|
105
103
|
specification_version: 4
|
106
104
|
summary: Ruby client api for Internet Message Access Protocol
|
107
105
|
test_files: []
|