net-imap 0.4.18 → 0.4.20
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/lib/net/imap/config/attr_type_coercion.rb +25 -21
- data/lib/net/imap/config.rb +169 -19
- data/lib/net/imap/errors.rb +33 -0
- data/lib/net/imap/response_data.rb +3 -54
- data/lib/net/imap/response_parser.rb +28 -13
- data/lib/net/imap/response_reader.rb +75 -0
- data/lib/net/imap/sequence_set.rb +254 -117
- data/lib/net/imap/uidplus_data.rb +326 -0
- data/lib/net/imap.rb +114 -41
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b965efa83b72d27d68fe55ce65f08ffeb696e4a4fb44219e6206a4b7dea4de91
|
4
|
+
data.tar.gz: 3554d7a749873b2eccec2470142315afddb4a6dd359a1d2cd6cfeacffa27d3f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b97c154cfe1b5ce9020029908ec017f316ef36ec36a4948d637d54990ad422580c99bdb2090c4fac4533a503a9678474753d664a101896164ee729d3abaacf37
|
7
|
+
data.tar.gz: 518dc7c466a5200b5945f1b21b8e8bcb4523649a3ef5678811385613834f910f5be9a193fd80dee1a8a029882b62da61c265fd3c942a874bd5cdb43c59d5ccef
|
@@ -18,6 +18,8 @@ module Net
|
|
18
18
|
super(attr)
|
19
19
|
AttrTypeCoercion.attr_accessor(attr, type: type)
|
20
20
|
end
|
21
|
+
|
22
|
+
module_function def Integer?; NilOrInteger end
|
21
23
|
end
|
22
24
|
private_constant :Macros
|
23
25
|
|
@@ -26,34 +28,36 @@ module Net
|
|
26
28
|
end
|
27
29
|
private_class_method :included
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
elsif Array === type then enum attr, type
|
34
|
-
else raise ArgumentError, "unknown type coercion %p" % [type]
|
35
|
-
end
|
31
|
+
if defined?(Ractor.make_shareable)
|
32
|
+
def self.safe(...) Ractor.make_shareable nil.instance_eval(...).freeze end
|
33
|
+
else
|
34
|
+
def self.safe(...) nil.instance_eval(...).freeze end
|
36
35
|
end
|
36
|
+
private_class_method :safe
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
Types = Hash.new do |h, type|
|
39
|
+
type.nil? || Proc === type or raise TypeError, "type not nil or Proc"
|
40
|
+
safe{type}
|
41
41
|
end
|
42
|
+
Types[:boolean] = Boolean = safe{-> {!!_1}}
|
43
|
+
Types[Integer] = safe{->{Integer(_1)}}
|
42
44
|
|
43
|
-
def self.
|
44
|
-
|
45
|
+
def self.attr_accessor(attr, type: nil)
|
46
|
+
type = Types[type] or return
|
47
|
+
define_method :"#{attr}=" do |val| super type[val] end
|
48
|
+
define_method :"#{attr}?" do send attr end if type == Boolean
|
45
49
|
end
|
46
50
|
|
47
|
-
|
48
|
-
|
51
|
+
NilOrInteger = safe{->val { Integer val unless val.nil? }}
|
52
|
+
|
53
|
+
Enum = ->(*enum) {
|
54
|
+
enum = safe{enum}
|
49
55
|
expected = -"one of #{enum.map(&:inspect).join(", ")}"
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
56
|
-
end
|
56
|
+
safe{->val {
|
57
|
+
return val if enum.include?(val)
|
58
|
+
raise ArgumentError, "expected %s, got %p" % [expected, val]
|
59
|
+
}}
|
60
|
+
}
|
57
61
|
|
58
62
|
end
|
59
63
|
end
|
data/lib/net/imap/config.rb
CHANGED
@@ -129,8 +129,25 @@ module Net
|
|
129
129
|
def self.global; @global if defined?(@global) end
|
130
130
|
|
131
131
|
# A hash of hard-coded configurations, indexed by version number or name.
|
132
|
+
# Values can be accessed with any object that responds to +to_sym+ or
|
133
|
+
# +to_r+/+to_f+ with a non-zero number.
|
134
|
+
#
|
135
|
+
# Config::[] gets named or numbered versions from this hash.
|
136
|
+
#
|
137
|
+
# For example:
|
138
|
+
# Net::IMAP::Config.version_defaults[0.5] == Net::IMAP::Config[0.5]
|
139
|
+
# Net::IMAP::Config[0.5] == Net::IMAP::Config[0.5r] # => true
|
140
|
+
# Net::IMAP::Config["current"] == Net::IMAP::Config[:current] # => true
|
141
|
+
# Net::IMAP::Config["0.5.6"] == Net::IMAP::Config[0.5r] # => true
|
132
142
|
def self.version_defaults; @version_defaults end
|
133
|
-
@version_defaults = {
|
143
|
+
@version_defaults = Hash.new {|h, k|
|
144
|
+
# NOTE: String responds to both so the order is significant.
|
145
|
+
# And ignore non-numeric conversion to zero, because: "wat!?".to_r == 0
|
146
|
+
(h.fetch(k.to_r, nil) || h.fetch(k.to_f, nil) if k.is_a?(Numeric)) ||
|
147
|
+
(h.fetch(k.to_sym, nil) if k.respond_to?(:to_sym)) ||
|
148
|
+
(h.fetch(k.to_r, nil) if k.respond_to?(:to_r) && k.to_r != 0r) ||
|
149
|
+
(h.fetch(k.to_f, nil) if k.respond_to?(:to_f) && k.to_f != 0.0)
|
150
|
+
}
|
134
151
|
|
135
152
|
# :call-seq:
|
136
153
|
# Net::IMAP::Config[number] -> versioned config
|
@@ -153,18 +170,17 @@ module Net
|
|
153
170
|
elsif config.nil? && global.nil? then nil
|
154
171
|
elsif config.respond_to?(:to_hash) then new(global, **config).freeze
|
155
172
|
else
|
156
|
-
version_defaults
|
173
|
+
version_defaults[config] or
|
157
174
|
case config
|
158
175
|
when Numeric
|
159
176
|
raise RangeError, "unknown config version: %p" % [config]
|
160
|
-
when Symbol
|
177
|
+
when String, Symbol
|
161
178
|
raise KeyError, "unknown config name: %p" % [config]
|
162
179
|
else
|
163
180
|
raise TypeError, "no implicit conversion of %s to %s" % [
|
164
181
|
config.class, Config
|
165
182
|
]
|
166
183
|
end
|
167
|
-
end
|
168
184
|
end
|
169
185
|
end
|
170
186
|
|
@@ -191,10 +207,13 @@ module Net
|
|
191
207
|
|
192
208
|
# Seconds to wait until a connection is opened.
|
193
209
|
#
|
210
|
+
# Applied separately for establishing TCP connection and starting a TLS
|
211
|
+
# connection.
|
212
|
+
#
|
194
213
|
# If the IMAP object cannot open a connection within this time,
|
195
214
|
# it raises a Net::OpenTimeout exception.
|
196
215
|
#
|
197
|
-
# See Net::IMAP.new.
|
216
|
+
# See Net::IMAP.new and Net::IMAP#starttls.
|
198
217
|
#
|
199
218
|
# The default value is +30+ seconds.
|
200
219
|
attr_accessor :open_timeout, type: Integer
|
@@ -223,6 +242,40 @@ module Net
|
|
223
242
|
# Use +SASL-IR+ when it is supported by the server and the mechanism.
|
224
243
|
attr_accessor :sasl_ir, type: :boolean
|
225
244
|
|
245
|
+
# The maximum allowed server response size. When +nil+, there is no limit
|
246
|
+
# on response size.
|
247
|
+
#
|
248
|
+
# The default value (512 MiB, since +v0.5.7+) is <em>very high</em> and
|
249
|
+
# unlikely to be reached. To use a lower limit, fetch message bodies in
|
250
|
+
# chunks rather than all at once. A _much_ lower value should be used
|
251
|
+
# with untrusted servers (for example, when connecting to a user-provided
|
252
|
+
# hostname).
|
253
|
+
#
|
254
|
+
# <em>Please Note:</em> this only limits the size per response. It does
|
255
|
+
# not prevent a flood of individual responses and it does not limit how
|
256
|
+
# many unhandled responses may be stored on the responses hash. See
|
257
|
+
# Net::IMAP@Unbounded+memory+use.
|
258
|
+
#
|
259
|
+
# Socket reads are limited to the maximum remaining bytes for the current
|
260
|
+
# response: max_response_size minus the bytes that have already been read.
|
261
|
+
# When the limit is reached, or reading a +literal+ _would_ go over the
|
262
|
+
# limit, ResponseTooLargeError is raised and the connection is closed.
|
263
|
+
# See also #socket_read_limit.
|
264
|
+
#
|
265
|
+
# Note that changes will not take effect immediately, because the receiver
|
266
|
+
# thread may already be waiting for the next response using the previous
|
267
|
+
# value. Net::IMAP#noop can force a response and enforce the new setting
|
268
|
+
# immediately.
|
269
|
+
#
|
270
|
+
# ==== Versioned Defaults
|
271
|
+
#
|
272
|
+
# Net::IMAP#max_response_size <em>was added in +v0.2.5+ and +v0.3.9+ as an
|
273
|
+
# attr_accessor, and in +v0.4.20+ and +v0.5.7+ as a delegator to this
|
274
|
+
# config attribute.</em>
|
275
|
+
#
|
276
|
+
# * original: +nil+ <em>(no limit)</em>
|
277
|
+
# * +0.5+: 512 MiB
|
278
|
+
attr_accessor :max_response_size, type: Integer?
|
226
279
|
|
227
280
|
# Controls the behavior of Net::IMAP#responses when called without any
|
228
281
|
# arguments (+type+ or +block+).
|
@@ -250,7 +303,7 @@ module Net
|
|
250
303
|
# Raise an ArgumentError with the deprecation warning.
|
251
304
|
#
|
252
305
|
# Note: #responses_without_args is an alias for #responses_without_block.
|
253
|
-
attr_accessor :responses_without_block, type: [
|
306
|
+
attr_accessor :responses_without_block, type: Enum[
|
254
307
|
:silence_deprecation_warning, :warn, :frozen_dup, :raise,
|
255
308
|
]
|
256
309
|
|
@@ -262,6 +315,67 @@ module Net
|
|
262
315
|
#
|
263
316
|
# Alias for responses_without_block
|
264
317
|
|
318
|
+
# Whether ResponseParser should use the deprecated UIDPlusData or
|
319
|
+
# CopyUIDData for +COPYUID+ response codes, and UIDPlusData or
|
320
|
+
# AppendUIDData for +APPENDUID+ response codes.
|
321
|
+
#
|
322
|
+
# UIDPlusData stores its data in arrays of numbers, which is vulnerable to
|
323
|
+
# a memory exhaustion denial of service attack from an untrusted or
|
324
|
+
# compromised server. Set this option to +false+ to completely block this
|
325
|
+
# vulnerability. Otherwise, parser_max_deprecated_uidplus_data_size
|
326
|
+
# mitigates this vulnerability.
|
327
|
+
#
|
328
|
+
# AppendUIDData and CopyUIDData are _mostly_ backward-compatible with
|
329
|
+
# UIDPlusData. Most applications should be able to upgrade with little
|
330
|
+
# or no changes.
|
331
|
+
#
|
332
|
+
# <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
|
333
|
+
#
|
334
|
+
# <em>(Config option added in +v0.4.19+ and +v0.5.6+.)</em>
|
335
|
+
#
|
336
|
+
# <em>UIDPlusData will be removed in +v0.6+ and this config setting will
|
337
|
+
# be ignored.</em>
|
338
|
+
#
|
339
|
+
# ==== Valid options
|
340
|
+
#
|
341
|
+
# [+true+ <em>(original default)</em>]
|
342
|
+
# ResponseParser only uses UIDPlusData.
|
343
|
+
#
|
344
|
+
# [+:up_to_max_size+ <em>(default since +v0.5.6+)</em>]
|
345
|
+
# ResponseParser uses UIDPlusData when the +uid-set+ size is below
|
346
|
+
# parser_max_deprecated_uidplus_data_size. Above that size,
|
347
|
+
# ResponseParser uses AppendUIDData or CopyUIDData.
|
348
|
+
#
|
349
|
+
# [+false+ <em>(planned default for +v0.6+)</em>]
|
350
|
+
# ResponseParser _only_ uses AppendUIDData and CopyUIDData.
|
351
|
+
attr_accessor :parser_use_deprecated_uidplus_data, type: Enum[
|
352
|
+
true, :up_to_max_size, false
|
353
|
+
]
|
354
|
+
|
355
|
+
# The maximum +uid-set+ size that ResponseParser will parse into
|
356
|
+
# deprecated UIDPlusData. This limit only applies when
|
357
|
+
# parser_use_deprecated_uidplus_data is not +false+.
|
358
|
+
#
|
359
|
+
# <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
|
360
|
+
#
|
361
|
+
# <em>Support for limiting UIDPlusData to a maximum size was added in
|
362
|
+
# +v0.3.8+, +v0.4.19+, and +v0.5.6+.</em>
|
363
|
+
#
|
364
|
+
# <em>UIDPlusData will be removed in +v0.6+.</em>
|
365
|
+
#
|
366
|
+
# ==== Versioned Defaults
|
367
|
+
#
|
368
|
+
# Because this limit guards against a remote server causing catastrophic
|
369
|
+
# memory exhaustion, the versioned default (used by #load_defaults) also
|
370
|
+
# applies to versions without the feature.
|
371
|
+
#
|
372
|
+
# * +0.3+ and prior: <tt>10,000</tt>
|
373
|
+
# * +0.4+: <tt>1,000</tt>
|
374
|
+
# * +0.5+: <tt>100</tt>
|
375
|
+
# * +0.6+: <tt>0</tt>
|
376
|
+
#
|
377
|
+
attr_accessor :parser_max_deprecated_uidplus_data_size, type: Integer
|
378
|
+
|
265
379
|
# Creates a new config object and initialize its attribute with +attrs+.
|
266
380
|
#
|
267
381
|
# If +parent+ is not given, the global config is used by default.
|
@@ -340,35 +454,71 @@ module Net
|
|
340
454
|
open_timeout: 30,
|
341
455
|
idle_response_timeout: 5,
|
342
456
|
sasl_ir: true,
|
457
|
+
max_response_size: nil,
|
343
458
|
responses_without_block: :silence_deprecation_warning,
|
459
|
+
parser_use_deprecated_uidplus_data: true,
|
460
|
+
parser_max_deprecated_uidplus_data_size: 1000,
|
344
461
|
).freeze
|
345
462
|
|
346
463
|
@global = default.new
|
347
464
|
|
348
|
-
version_defaults[
|
465
|
+
version_defaults[:default] = Config[default.send(:defaults_hash)]
|
349
466
|
|
350
|
-
version_defaults[
|
467
|
+
version_defaults[0r] = Config[:default].dup.update(
|
351
468
|
sasl_ir: false,
|
469
|
+
max_response_size: nil,
|
470
|
+
parser_use_deprecated_uidplus_data: true,
|
471
|
+
parser_max_deprecated_uidplus_data_size: 10_000,
|
352
472
|
).freeze
|
353
|
-
version_defaults[0.
|
354
|
-
version_defaults[0.
|
355
|
-
version_defaults[0.
|
356
|
-
version_defaults[0.
|
473
|
+
version_defaults[0.0r] = Config[0r]
|
474
|
+
version_defaults[0.1r] = Config[0r]
|
475
|
+
version_defaults[0.2r] = Config[0r]
|
476
|
+
version_defaults[0.3r] = Config[0r]
|
357
477
|
|
358
|
-
version_defaults[0.
|
359
|
-
|
478
|
+
version_defaults[0.4r] = Config[0.3r].dup.update(
|
479
|
+
sasl_ir: true,
|
480
|
+
parser_max_deprecated_uidplus_data_size: 1000,
|
360
481
|
).freeze
|
361
482
|
|
362
|
-
version_defaults[
|
363
|
-
|
364
|
-
|
483
|
+
version_defaults[0.5r] = Config[0.4r].dup.update(
|
484
|
+
max_response_size: 512 << 20, # 512 MiB
|
485
|
+
responses_without_block: :warn,
|
486
|
+
parser_use_deprecated_uidplus_data: :up_to_max_size,
|
487
|
+
parser_max_deprecated_uidplus_data_size: 100,
|
488
|
+
).freeze
|
365
489
|
|
366
|
-
version_defaults[0.
|
490
|
+
version_defaults[0.6r] = Config[0.5r].dup.update(
|
367
491
|
responses_without_block: :frozen_dup,
|
492
|
+
parser_use_deprecated_uidplus_data: false,
|
493
|
+
parser_max_deprecated_uidplus_data_size: 0,
|
494
|
+
).freeze
|
495
|
+
|
496
|
+
version_defaults[0.7r] = Config[0.6r].dup.update(
|
368
497
|
).freeze
|
369
|
-
|
498
|
+
|
499
|
+
# Safe conversions one way only:
|
500
|
+
# 0.6r.to_f == 0.6 # => true
|
501
|
+
# 0.6 .to_r == 0.6r # => false
|
502
|
+
version_defaults.to_a.each do |k, v|
|
503
|
+
next unless k.is_a? Rational
|
504
|
+
version_defaults[k.to_f] = v
|
505
|
+
end
|
506
|
+
|
507
|
+
current = VERSION.to_r
|
508
|
+
version_defaults[:original] = Config[0]
|
509
|
+
version_defaults[:current] = Config[current]
|
510
|
+
version_defaults[:next] = Config[current + 0.1r]
|
511
|
+
|
512
|
+
version_defaults[:future] = Config[0.7r]
|
370
513
|
|
371
514
|
version_defaults.freeze
|
515
|
+
|
516
|
+
if ($VERBOSE || $DEBUG) && self[:current].to_h != self[:default].to_h
|
517
|
+
warn "Misconfigured Net::IMAP::Config[:current] => %p,\n" \
|
518
|
+
" not equal to Net::IMAP::Config[:default] => %p" % [
|
519
|
+
self[:current].to_h, self[:default].to_h
|
520
|
+
]
|
521
|
+
end
|
372
522
|
end
|
373
523
|
end
|
374
524
|
end
|
data/lib/net/imap/errors.rb
CHANGED
@@ -11,6 +11,39 @@ module Net
|
|
11
11
|
class DataFormatError < Error
|
12
12
|
end
|
13
13
|
|
14
|
+
# Error raised when the socket cannot be read, due to a Config 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
|
+
super(msg, *args, **kwargs)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def response_size_msg
|
41
|
+
if bytes_read && literal_size
|
42
|
+
"(#{bytes_read}B read + #{literal_size}B literal)"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
14
47
|
# Error raised when a response from the server is non-parsable.
|
15
48
|
class ResponseParseError < Error
|
16
49
|
end
|
@@ -5,6 +5,9 @@ module Net
|
|
5
5
|
autoload :FetchData, "#{__dir__}/fetch_data"
|
6
6
|
autoload :SearchResult, "#{__dir__}/search_result"
|
7
7
|
autoload :SequenceSet, "#{__dir__}/sequence_set"
|
8
|
+
autoload :UIDPlusData, "#{__dir__}/uidplus_data"
|
9
|
+
autoload :AppendUIDData, "#{__dir__}/uidplus_data"
|
10
|
+
autoload :CopyUIDData, "#{__dir__}/uidplus_data"
|
8
11
|
|
9
12
|
# Net::IMAP::ContinuationRequest represents command continuation requests.
|
10
13
|
#
|
@@ -324,60 +327,6 @@ module Net
|
|
324
327
|
# code data can take.
|
325
328
|
end
|
326
329
|
|
327
|
-
# Net::IMAP::UIDPlusData represents the ResponseCode#data that accompanies
|
328
|
-
# the +APPENDUID+ and +COPYUID+ response codes.
|
329
|
-
#
|
330
|
-
# See [[UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]].
|
331
|
-
#
|
332
|
-
# ==== Capability requirement
|
333
|
-
#
|
334
|
-
# The +UIDPLUS+ capability[rdoc-ref:Net::IMAP#capability] must be supported.
|
335
|
-
# A server that supports +UIDPLUS+ should send a UIDPlusData object inside
|
336
|
-
# every TaggedResponse returned by the append[rdoc-ref:Net::IMAP#append],
|
337
|
-
# copy[rdoc-ref:Net::IMAP#copy], move[rdoc-ref:Net::IMAP#move], {uid
|
338
|
-
# copy}[rdoc-ref:Net::IMAP#uid_copy], and {uid
|
339
|
-
# move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the destination
|
340
|
-
# mailbox reports +UIDNOTSTICKY+.
|
341
|
-
#
|
342
|
-
#--
|
343
|
-
# TODO: support MULTIAPPEND
|
344
|
-
#++
|
345
|
-
#
|
346
|
-
class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
|
347
|
-
##
|
348
|
-
# method: uidvalidity
|
349
|
-
# :call-seq: uidvalidity -> nonzero uint32
|
350
|
-
#
|
351
|
-
# The UIDVALIDITY of the destination mailbox.
|
352
|
-
|
353
|
-
##
|
354
|
-
# method: source_uids
|
355
|
-
# :call-seq: source_uids -> nil or an array of nonzero uint32
|
356
|
-
#
|
357
|
-
# The UIDs of the copied or moved messages.
|
358
|
-
#
|
359
|
-
# Note:: Returns +nil+ for Net::IMAP#append.
|
360
|
-
|
361
|
-
##
|
362
|
-
# method: assigned_uids
|
363
|
-
# :call-seq: assigned_uids -> an array of nonzero uint32
|
364
|
-
#
|
365
|
-
# The newly assigned UIDs of the copied, moved, or appended messages.
|
366
|
-
#
|
367
|
-
# Note:: This always returns an array, even when it contains only one UID.
|
368
|
-
|
369
|
-
##
|
370
|
-
# :call-seq: uid_mapping -> nil or a hash
|
371
|
-
#
|
372
|
-
# Returns a hash mapping each source UID to the newly assigned destination
|
373
|
-
# UID.
|
374
|
-
#
|
375
|
-
# Note:: Returns +nil+ for Net::IMAP#append.
|
376
|
-
def uid_mapping
|
377
|
-
source_uids&.zip(assigned_uids)&.to_h
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
330
|
# Net::IMAP::MailboxList represents contents of the LIST response,
|
382
331
|
# representing a single mailbox path.
|
383
332
|
#
|
@@ -13,13 +13,17 @@ module Net
|
|
13
13
|
|
14
14
|
attr_reader :config
|
15
15
|
|
16
|
-
#
|
16
|
+
# Creates a new ResponseParser.
|
17
|
+
#
|
18
|
+
# When +config+ is frozen or global, the parser #config inherits from it.
|
19
|
+
# Otherwise, +config+ will be used directly.
|
17
20
|
def initialize(config: Config.global)
|
18
21
|
@str = nil
|
19
22
|
@pos = nil
|
20
23
|
@lex_state = nil
|
21
24
|
@token = nil
|
22
25
|
@config = Config[config]
|
26
|
+
@config = @config.new if @config == Config.global || @config.frozen?
|
23
27
|
end
|
24
28
|
|
25
29
|
# :call-seq:
|
@@ -1863,11 +1867,10 @@ module Net
|
|
1863
1867
|
#
|
1864
1868
|
# n.b, uniqueid ⊂ uid-set. To avoid inconsistent return types, we always
|
1865
1869
|
# match uid_set even if that returns a single-member array.
|
1866
|
-
#
|
1867
1870
|
def resp_code_apnd__data
|
1868
1871
|
validity = number; SP!
|
1869
1872
|
dst_uids = uid_set # uniqueid ⊂ uid-set
|
1870
|
-
|
1873
|
+
AppendUID(validity, dst_uids)
|
1871
1874
|
end
|
1872
1875
|
|
1873
1876
|
# already matched: "COPYUID"
|
@@ -1877,7 +1880,25 @@ module Net
|
|
1877
1880
|
validity = number; SP!
|
1878
1881
|
src_uids = uid_set; SP!
|
1879
1882
|
dst_uids = uid_set
|
1880
|
-
|
1883
|
+
CopyUID(validity, src_uids, dst_uids)
|
1884
|
+
end
|
1885
|
+
|
1886
|
+
def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
|
1887
|
+
def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end
|
1888
|
+
|
1889
|
+
# TODO: remove this code in the v0.6.0 release
|
1890
|
+
def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids)
|
1891
|
+
return unless config.parser_use_deprecated_uidplus_data
|
1892
|
+
compact_uid_sets = [src_uids, dst_uids].compact
|
1893
|
+
count = compact_uid_sets.map { _1.count_with_duplicates }.max
|
1894
|
+
max = config.parser_max_deprecated_uidplus_data_size
|
1895
|
+
if count <= max
|
1896
|
+
src_uids &&= src_uids.each_ordered_number.to_a
|
1897
|
+
dst_uids = dst_uids.each_ordered_number.to_a
|
1898
|
+
UIDPlusData.new(validity, src_uids, dst_uids)
|
1899
|
+
elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size
|
1900
|
+
parse_error("uid-set is too large: %d > %d", count, max)
|
1901
|
+
end
|
1881
1902
|
end
|
1882
1903
|
|
1883
1904
|
ADDRESS_REGEXP = /\G
|
@@ -2003,15 +2024,9 @@ module Net
|
|
2003
2024
|
# uniqueid = nz-number
|
2004
2025
|
# ; Strictly ascending
|
2005
2026
|
def uid_set
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
when T_ATOM
|
2010
|
-
token.value.split(",").flat_map {|range|
|
2011
|
-
range = range.split(":").map {|uniqueid| Integer(uniqueid) }
|
2012
|
-
range.size == 1 ? range : Range.new(range.min, range.max).to_a
|
2013
|
-
}
|
2014
|
-
end
|
2027
|
+
set = sequence_set
|
2028
|
+
parse_error("uid-set cannot contain '*'") if set.include_star?
|
2029
|
+
set
|
2015
2030
|
end
|
2016
2031
|
|
2017
2032
|
def nil_atom
|
@@ -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
|