net-imap 0.5.5 → 0.5.7
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 -0
- data/lib/net/imap/config/attr_type_coercion.rb +20 -23
- data/lib/net/imap/config.rb +168 -18
- data/lib/net/imap/connection_state.rb +48 -0
- data/lib/net/imap/errors.rb +33 -0
- data/lib/net/imap/response_data.rb +3 -49
- data/lib/net/imap/response_parser.rb +28 -13
- data/lib/net/imap/response_reader.rb +73 -0
- data/lib/net/imap/sequence_set.rb +267 -118
- data/lib/net/imap/uidplus_data.rb +244 -0
- data/lib/net/imap.rb +227 -48
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee48ac78f129043fd8ccb4f6e0b42535cc0ef3a6acd8ea1067cbd9b512815e49
|
4
|
+
data.tar.gz: 5bfae3bf0ee2e63c5b61c19d30187fe722adfe8018746e64d583124531de87a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f90a4500f3c218dd3a51cca84a76c620fa8f8488f2ca73486b538a43e67e21e0b897d23d957f490afe1590fd755c692f2e0dede562460ed2cfcd7f4d9bec3262
|
7
|
+
data.tar.gz: 22abb3cf5699bb715c33c69b596ead46ff400109925f58d633ecdc82797be9b80fbbd040c02aa7cd9286f3aa9c697e9fc426f1115b9019455b2ac4e6667cc236
|
data/Gemfile
CHANGED
@@ -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
|
21
23
|
end
|
22
24
|
private_constant :Macros
|
23
25
|
|
@@ -26,34 +28,29 @@ module Net
|
|
26
28
|
end
|
27
29
|
private_class_method :included
|
28
30
|
|
29
|
-
def self.
|
30
|
-
|
31
|
-
if :boolean == type then boolean attr
|
32
|
-
elsif Integer == type then integer attr
|
33
|
-
elsif Array === type then enum attr, type
|
34
|
-
else raise ArgumentError, "unknown type coercion %p" % [type]
|
35
|
-
end
|
36
|
-
end
|
31
|
+
def self.safe(...) = Ractor.make_shareable nil.instance_eval(...).freeze
|
32
|
+
private_class_method :safe
|
37
33
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
34
|
+
Types = Hash.new do |h, type| type => Proc | nil; safe{type} end
|
35
|
+
Types[:boolean] = Boolean = safe{-> {!!_1}}
|
36
|
+
Types[Integer] = safe{->{Integer(_1)}}
|
42
37
|
|
43
|
-
def self.
|
44
|
-
|
38
|
+
def self.attr_accessor(attr, type: nil)
|
39
|
+
type = Types[type] or return
|
40
|
+
define_method :"#{attr}=" do |val| super type[val] end
|
41
|
+
define_method :"#{attr}?" do send attr end if type == Boolean
|
45
42
|
end
|
46
43
|
|
47
|
-
|
48
|
-
|
44
|
+
NilOrInteger = safe{->val { Integer val unless val.nil? }}
|
45
|
+
|
46
|
+
Enum = ->(*enum) {
|
47
|
+
enum = safe{enum}
|
49
48
|
expected = -"one of #{enum.map(&:inspect).join(", ")}"
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
56
|
-
end
|
49
|
+
safe{->val {
|
50
|
+
return val if enum.include?(val)
|
51
|
+
raise ArgumentError, "expected %s, got %p" % [expected, val]
|
52
|
+
}}
|
53
|
+
}
|
57
54
|
|
58
55
|
end
|
59
56
|
end
|
data/lib/net/imap/config.rb
CHANGED
@@ -131,8 +131,25 @@ module Net
|
|
131
131
|
def self.global; @global if defined?(@global) end
|
132
132
|
|
133
133
|
# A hash of hard-coded configurations, indexed by version number or name.
|
134
|
+
# Values can be accessed with any object that responds to +to_sym+ or
|
135
|
+
# +to_r+/+to_f+ with a non-zero number.
|
136
|
+
#
|
137
|
+
# Config::[] gets named or numbered versions from this hash.
|
138
|
+
#
|
139
|
+
# For example:
|
140
|
+
# Net::IMAP::Config.version_defaults[0.5] == Net::IMAP::Config[0.5]
|
141
|
+
# Net::IMAP::Config[0.5] == Net::IMAP::Config[0.5r] # => true
|
142
|
+
# Net::IMAP::Config["current"] == Net::IMAP::Config[:current] # => true
|
143
|
+
# Net::IMAP::Config["0.5.6"] == Net::IMAP::Config[0.5r] # => true
|
134
144
|
def self.version_defaults; @version_defaults end
|
135
|
-
@version_defaults = {
|
145
|
+
@version_defaults = Hash.new {|h, k|
|
146
|
+
# NOTE: String responds to both so the order is significant.
|
147
|
+
# And ignore non-numeric conversion to zero, because: "wat!?".to_r == 0
|
148
|
+
(h.fetch(k.to_r, nil) || h.fetch(k.to_f, nil) if k.is_a?(Numeric)) ||
|
149
|
+
(h.fetch(k.to_sym, nil) if k.respond_to?(:to_sym)) ||
|
150
|
+
(h.fetch(k.to_r, nil) if k.respond_to?(:to_r) && k.to_r != 0r) ||
|
151
|
+
(h.fetch(k.to_f, nil) if k.respond_to?(:to_f) && k.to_f != 0.0)
|
152
|
+
}
|
136
153
|
|
137
154
|
# :call-seq:
|
138
155
|
# Net::IMAP::Config[number] -> versioned config
|
@@ -155,18 +172,17 @@ module Net
|
|
155
172
|
elsif config.nil? && global.nil? then nil
|
156
173
|
elsif config.respond_to?(:to_hash) then new(global, **config).freeze
|
157
174
|
else
|
158
|
-
version_defaults
|
175
|
+
version_defaults[config] or
|
159
176
|
case config
|
160
177
|
when Numeric
|
161
178
|
raise RangeError, "unknown config version: %p" % [config]
|
162
|
-
when Symbol
|
179
|
+
when String, Symbol
|
163
180
|
raise KeyError, "unknown config name: %p" % [config]
|
164
181
|
else
|
165
182
|
raise TypeError, "no implicit conversion of %s to %s" % [
|
166
183
|
config.class, Config
|
167
184
|
]
|
168
185
|
end
|
169
|
-
end
|
170
186
|
end
|
171
187
|
end
|
172
188
|
|
@@ -193,10 +209,13 @@ module Net
|
|
193
209
|
|
194
210
|
# Seconds to wait until a connection is opened.
|
195
211
|
#
|
212
|
+
# Applied separately for establishing TCP connection and starting a TLS
|
213
|
+
# connection.
|
214
|
+
#
|
196
215
|
# If the IMAP object cannot open a connection within this time,
|
197
216
|
# it raises a Net::OpenTimeout exception.
|
198
217
|
#
|
199
|
-
# See Net::IMAP.new.
|
218
|
+
# See Net::IMAP.new and Net::IMAP#starttls.
|
200
219
|
#
|
201
220
|
# The default value is +30+ seconds.
|
202
221
|
attr_accessor :open_timeout, type: Integer
|
@@ -245,10 +264,44 @@ module Net
|
|
245
264
|
# present. When capabilities are unknown, Net::IMAP will automatically
|
246
265
|
# send a +CAPABILITY+ command first before sending +LOGIN+.
|
247
266
|
#
|
248
|
-
attr_accessor :enforce_logindisabled, type: [
|
267
|
+
attr_accessor :enforce_logindisabled, type: Enum[
|
249
268
|
false, :when_capabilities_cached, true
|
250
269
|
]
|
251
270
|
|
271
|
+
# The maximum allowed server response size. When +nil+, there is no limit
|
272
|
+
# on response size.
|
273
|
+
#
|
274
|
+
# The default value (512 MiB, since +v0.5.7+) is <em>very high</em> and
|
275
|
+
# unlikely to be reached. A _much_ lower value should be used with
|
276
|
+
# untrusted servers (for example, when connecting to a user-provided
|
277
|
+
# hostname). When using a lower limit, message bodies should be fetched
|
278
|
+
# in chunks rather than all at once.
|
279
|
+
#
|
280
|
+
# <em>Please Note:</em> this only limits the size per response. It does
|
281
|
+
# not prevent a flood of individual responses and it does not limit how
|
282
|
+
# many unhandled responses may be stored on the responses hash. See
|
283
|
+
# Net::IMAP@Unbounded+memory+use.
|
284
|
+
#
|
285
|
+
# Socket reads are limited to the maximum remaining bytes for the current
|
286
|
+
# response: max_response_size minus the bytes that have already been read.
|
287
|
+
# When the limit is reached, or reading a +literal+ _would_ go over the
|
288
|
+
# limit, ResponseTooLargeError is raised and the connection is closed.
|
289
|
+
#
|
290
|
+
# Note that changes will not take effect immediately, because the receiver
|
291
|
+
# thread may already be waiting for the next response using the previous
|
292
|
+
# value. Net::IMAP#noop can force a response and enforce the new setting
|
293
|
+
# immediately.
|
294
|
+
#
|
295
|
+
# ==== Versioned Defaults
|
296
|
+
#
|
297
|
+
# Net::IMAP#max_response_size <em>was added in +v0.2.5+ and +v0.3.9+ as an
|
298
|
+
# attr_accessor, and in +v0.4.20+ and +v0.5.7+ as a delegator to this
|
299
|
+
# config attribute.</em>
|
300
|
+
#
|
301
|
+
# * original: +nil+ <em>(no limit)</em>
|
302
|
+
# * +0.5+: 512 MiB
|
303
|
+
attr_accessor :max_response_size, type: Integer?
|
304
|
+
|
252
305
|
# Controls the behavior of Net::IMAP#responses when called without any
|
253
306
|
# arguments (+type+ or +block+).
|
254
307
|
#
|
@@ -275,7 +328,7 @@ module Net
|
|
275
328
|
# Raise an ArgumentError with the deprecation warning.
|
276
329
|
#
|
277
330
|
# Note: #responses_without_args is an alias for #responses_without_block.
|
278
|
-
attr_accessor :responses_without_block, type: [
|
331
|
+
attr_accessor :responses_without_block, type: Enum[
|
279
332
|
:silence_deprecation_warning, :warn, :frozen_dup, :raise,
|
280
333
|
]
|
281
334
|
|
@@ -287,6 +340,67 @@ module Net
|
|
287
340
|
#
|
288
341
|
# Alias for responses_without_block
|
289
342
|
|
343
|
+
# Whether ResponseParser should use the deprecated UIDPlusData or
|
344
|
+
# CopyUIDData for +COPYUID+ response codes, and UIDPlusData or
|
345
|
+
# AppendUIDData for +APPENDUID+ response codes.
|
346
|
+
#
|
347
|
+
# UIDPlusData stores its data in arrays of numbers, which is vulnerable to
|
348
|
+
# a memory exhaustion denial of service attack from an untrusted or
|
349
|
+
# compromised server. Set this option to +false+ to completely block this
|
350
|
+
# vulnerability. Otherwise, parser_max_deprecated_uidplus_data_size
|
351
|
+
# mitigates this vulnerability.
|
352
|
+
#
|
353
|
+
# AppendUIDData and CopyUIDData are _mostly_ backward-compatible with
|
354
|
+
# UIDPlusData. Most applications should be able to upgrade with little
|
355
|
+
# or no changes.
|
356
|
+
#
|
357
|
+
# <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
|
358
|
+
#
|
359
|
+
# <em>(Config option added in +v0.4.19+ and +v0.5.6+.)</em>
|
360
|
+
#
|
361
|
+
# <em>UIDPlusData will be removed in +v0.6+ and this config setting will
|
362
|
+
# be ignored.</em>
|
363
|
+
#
|
364
|
+
# ==== Valid options
|
365
|
+
#
|
366
|
+
# [+true+ <em>(original default)</em>]
|
367
|
+
# ResponseParser only uses UIDPlusData.
|
368
|
+
#
|
369
|
+
# [+:up_to_max_size+ <em>(default since +v0.5.6+)</em>]
|
370
|
+
# ResponseParser uses UIDPlusData when the +uid-set+ size is below
|
371
|
+
# parser_max_deprecated_uidplus_data_size. Above that size,
|
372
|
+
# ResponseParser uses AppendUIDData or CopyUIDData.
|
373
|
+
#
|
374
|
+
# [+false+ <em>(planned default for +v0.6+)</em>]
|
375
|
+
# ResponseParser _only_ uses AppendUIDData and CopyUIDData.
|
376
|
+
attr_accessor :parser_use_deprecated_uidplus_data, type: Enum[
|
377
|
+
true, :up_to_max_size, false
|
378
|
+
]
|
379
|
+
|
380
|
+
# The maximum +uid-set+ size that ResponseParser will parse into
|
381
|
+
# deprecated UIDPlusData. This limit only applies when
|
382
|
+
# parser_use_deprecated_uidplus_data is not +false+.
|
383
|
+
#
|
384
|
+
# <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
|
385
|
+
#
|
386
|
+
# <em>Support for limiting UIDPlusData to a maximum size was added in
|
387
|
+
# +v0.3.8+, +v0.4.19+, and +v0.5.6+.</em>
|
388
|
+
#
|
389
|
+
# <em>UIDPlusData will be removed in +v0.6+.</em>
|
390
|
+
#
|
391
|
+
# ==== Versioned Defaults
|
392
|
+
#
|
393
|
+
# Because this limit guards against a remote server causing catastrophic
|
394
|
+
# memory exhaustion, the versioned default (used by #load_defaults) also
|
395
|
+
# applies to versions without the feature.
|
396
|
+
#
|
397
|
+
# * +0.3+ and prior: <tt>10,000</tt>
|
398
|
+
# * +0.4+: <tt>1,000</tt>
|
399
|
+
# * +0.5+: <tt>100</tt>
|
400
|
+
# * +0.6+: <tt>0</tt>
|
401
|
+
#
|
402
|
+
attr_accessor :parser_max_deprecated_uidplus_data_size, type: Integer
|
403
|
+
|
290
404
|
# Creates a new config object and initialize its attribute with +attrs+.
|
291
405
|
#
|
292
406
|
# If +parent+ is not given, the global config is used by default.
|
@@ -366,37 +480,73 @@ module Net
|
|
366
480
|
idle_response_timeout: 5,
|
367
481
|
sasl_ir: true,
|
368
482
|
enforce_logindisabled: true,
|
483
|
+
max_response_size: 512 << 20, # 512 MiB
|
369
484
|
responses_without_block: :warn,
|
485
|
+
parser_use_deprecated_uidplus_data: :up_to_max_size,
|
486
|
+
parser_max_deprecated_uidplus_data_size: 100,
|
370
487
|
).freeze
|
371
488
|
|
372
489
|
@global = default.new
|
373
490
|
|
374
491
|
version_defaults[:default] = Config[default.send(:defaults_hash)]
|
375
|
-
version_defaults[:current] = Config[:default]
|
376
492
|
|
377
|
-
version_defaults[
|
493
|
+
version_defaults[0r] = Config[:default].dup.update(
|
378
494
|
sasl_ir: false,
|
379
495
|
responses_without_block: :silence_deprecation_warning,
|
380
496
|
enforce_logindisabled: false,
|
497
|
+
max_response_size: nil,
|
498
|
+
parser_use_deprecated_uidplus_data: true,
|
499
|
+
parser_max_deprecated_uidplus_data_size: 10_000,
|
381
500
|
).freeze
|
382
|
-
version_defaults[0.
|
383
|
-
version_defaults[0.
|
384
|
-
version_defaults[0.
|
385
|
-
version_defaults[0.
|
501
|
+
version_defaults[0.0r] = Config[0r]
|
502
|
+
version_defaults[0.1r] = Config[0r]
|
503
|
+
version_defaults[0.2r] = Config[0r]
|
504
|
+
version_defaults[0.3r] = Config[0r]
|
386
505
|
|
387
|
-
version_defaults[0.
|
506
|
+
version_defaults[0.4r] = Config[0.3r].dup.update(
|
388
507
|
sasl_ir: true,
|
508
|
+
parser_max_deprecated_uidplus_data_size: 1000,
|
389
509
|
).freeze
|
390
510
|
|
391
|
-
version_defaults[0.
|
511
|
+
version_defaults[0.5r] = Config[0.4r].dup.update(
|
512
|
+
enforce_logindisabled: true,
|
513
|
+
max_response_size: 512 << 20, # 512 MiB
|
514
|
+
responses_without_block: :warn,
|
515
|
+
parser_use_deprecated_uidplus_data: :up_to_max_size,
|
516
|
+
parser_max_deprecated_uidplus_data_size: 100,
|
517
|
+
).freeze
|
392
518
|
|
393
|
-
version_defaults[0.
|
519
|
+
version_defaults[0.6r] = Config[0.5r].dup.update(
|
394
520
|
responses_without_block: :frozen_dup,
|
521
|
+
parser_use_deprecated_uidplus_data: false,
|
522
|
+
parser_max_deprecated_uidplus_data_size: 0,
|
395
523
|
).freeze
|
396
|
-
|
397
|
-
version_defaults[
|
524
|
+
|
525
|
+
version_defaults[0.7r] = Config[0.6r].dup.update(
|
526
|
+
).freeze
|
527
|
+
|
528
|
+
# Safe conversions one way only:
|
529
|
+
# 0.6r.to_f == 0.6 # => true
|
530
|
+
# 0.6 .to_r == 0.6r # => false
|
531
|
+
version_defaults.to_a.each do |k, v|
|
532
|
+
next unless k in Rational
|
533
|
+
version_defaults[k.to_f] = v
|
534
|
+
end
|
535
|
+
|
536
|
+
current = VERSION.to_r
|
537
|
+
version_defaults[:original] = Config[0]
|
538
|
+
version_defaults[:current] = Config[current]
|
539
|
+
version_defaults[:next] = Config[current + 0.1r]
|
540
|
+
version_defaults[:future] = Config[current + 0.2r]
|
398
541
|
|
399
542
|
version_defaults.freeze
|
543
|
+
|
544
|
+
if ($VERBOSE || $DEBUG) && self[:current].to_h != self[:default].to_h
|
545
|
+
warn "Misconfigured Net::IMAP::Config[:current] => %p,\n" \
|
546
|
+
" not equal to Net::IMAP::Config[:default] => %p" % [
|
547
|
+
self[:current].to_h, self[:default].to_h
|
548
|
+
]
|
549
|
+
end
|
400
550
|
end
|
401
551
|
end
|
402
552
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
class ConnectionState < Net::IMAP::Data # :nodoc:
|
6
|
+
def self.define(symbol, *attrs)
|
7
|
+
symbol => Symbol
|
8
|
+
state = super(*attrs)
|
9
|
+
state.const_set :NAME, symbol
|
10
|
+
state
|
11
|
+
end
|
12
|
+
|
13
|
+
def symbol; self.class::NAME end
|
14
|
+
def name; self.class::NAME.name end
|
15
|
+
alias to_sym symbol
|
16
|
+
|
17
|
+
def deconstruct; [symbol, *super] end
|
18
|
+
|
19
|
+
def deconstruct_keys(names)
|
20
|
+
hash = super
|
21
|
+
hash[:symbol] = symbol if names.nil? || names.include?(:symbol)
|
22
|
+
hash[:name] = name if names.nil? || names.include?(:name)
|
23
|
+
hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_h(&block)
|
27
|
+
hash = deconstruct_keys(nil)
|
28
|
+
block ? hash.to_h(&block) : hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def not_authenticated?; to_sym == :not_authenticated end
|
32
|
+
def authenticated?; to_sym == :authenticated end
|
33
|
+
def selected?; to_sym == :selected end
|
34
|
+
def logout?; to_sym == :logout end
|
35
|
+
|
36
|
+
NotAuthenticated = define(:not_authenticated)
|
37
|
+
Authenticated = define(:authenticated)
|
38
|
+
Selected = define(:selected)
|
39
|
+
Logout = define(:logout)
|
40
|
+
|
41
|
+
class << self
|
42
|
+
undef :define
|
43
|
+
end
|
44
|
+
freeze
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
data/lib/net/imap/errors.rb
CHANGED
@@ -17,6 +17,39 @@ module Net
|
|
17
17
|
class DataFormatError < Error
|
18
18
|
end
|
19
19
|
|
20
|
+
# Error raised when the socket cannot be read, due to a Config limit.
|
21
|
+
class ResponseReadError < Error
|
22
|
+
end
|
23
|
+
|
24
|
+
# Error raised when a response is larger than IMAP#max_response_size.
|
25
|
+
class ResponseTooLargeError < ResponseReadError
|
26
|
+
attr_reader :bytes_read, :literal_size
|
27
|
+
attr_reader :max_response_size
|
28
|
+
|
29
|
+
def initialize(msg = nil, *args,
|
30
|
+
bytes_read: nil,
|
31
|
+
literal_size: nil,
|
32
|
+
max_response_size: nil,
|
33
|
+
**kwargs)
|
34
|
+
@bytes_read = bytes_read
|
35
|
+
@literal_size = literal_size
|
36
|
+
@max_response_size = max_response_size
|
37
|
+
msg ||= [
|
38
|
+
"Response size", response_size_msg, "exceeds max_response_size",
|
39
|
+
max_response_size && "(#{max_response_size}B)",
|
40
|
+
].compact.join(" ")
|
41
|
+
super(msg, *args, **kwargs)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def response_size_msg
|
47
|
+
if bytes_read && literal_size
|
48
|
+
"(#{bytes_read}B read + #{literal_size}B literal)"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
20
53
|
# Error raised when a response from the server is non-parsable.
|
21
54
|
class ResponseParseError < Error
|
22
55
|
end
|
@@ -7,6 +7,9 @@ module Net
|
|
7
7
|
autoload :UIDFetchData, "#{__dir__}/fetch_data"
|
8
8
|
autoload :SearchResult, "#{__dir__}/search_result"
|
9
9
|
autoload :SequenceSet, "#{__dir__}/sequence_set"
|
10
|
+
autoload :UIDPlusData, "#{__dir__}/uidplus_data"
|
11
|
+
autoload :AppendUIDData, "#{__dir__}/uidplus_data"
|
12
|
+
autoload :CopyUIDData, "#{__dir__}/uidplus_data"
|
10
13
|
autoload :VanishedData, "#{__dir__}/vanished_data"
|
11
14
|
|
12
15
|
# Net::IMAP::ContinuationRequest represents command continuation requests.
|
@@ -344,55 +347,6 @@ module Net
|
|
344
347
|
# code data can take.
|
345
348
|
end
|
346
349
|
|
347
|
-
# UIDPlusData represents the ResponseCode#data that accompanies the
|
348
|
-
# +APPENDUID+ and +COPYUID+ {response codes}[rdoc-ref:ResponseCode].
|
349
|
-
#
|
350
|
-
# A server that supports +UIDPLUS+ should send a UIDPlusData object inside
|
351
|
-
# every TaggedResponse returned by the append[rdoc-ref:Net::IMAP#append],
|
352
|
-
# copy[rdoc-ref:Net::IMAP#copy], move[rdoc-ref:Net::IMAP#move], {uid
|
353
|
-
# copy}[rdoc-ref:Net::IMAP#uid_copy], and {uid
|
354
|
-
# move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the destination
|
355
|
-
# mailbox reports +UIDNOTSTICKY+.
|
356
|
-
#
|
357
|
-
# == Required capability
|
358
|
-
# Requires either +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315]]
|
359
|
-
# or +IMAP4rev2+ capability.
|
360
|
-
#
|
361
|
-
class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
|
362
|
-
##
|
363
|
-
# method: uidvalidity
|
364
|
-
# :call-seq: uidvalidity -> nonzero uint32
|
365
|
-
#
|
366
|
-
# The UIDVALIDITY of the destination mailbox.
|
367
|
-
|
368
|
-
##
|
369
|
-
# method: source_uids
|
370
|
-
# :call-seq: source_uids -> nil or an array of nonzero uint32
|
371
|
-
#
|
372
|
-
# The UIDs of the copied or moved messages.
|
373
|
-
#
|
374
|
-
# Note:: Returns +nil+ for Net::IMAP#append.
|
375
|
-
|
376
|
-
##
|
377
|
-
# method: assigned_uids
|
378
|
-
# :call-seq: assigned_uids -> an array of nonzero uint32
|
379
|
-
#
|
380
|
-
# The newly assigned UIDs of the copied, moved, or appended messages.
|
381
|
-
#
|
382
|
-
# Note:: This always returns an array, even when it contains only one UID.
|
383
|
-
|
384
|
-
##
|
385
|
-
# :call-seq: uid_mapping -> nil or a hash
|
386
|
-
#
|
387
|
-
# Returns a hash mapping each source UID to the newly assigned destination
|
388
|
-
# UID.
|
389
|
-
#
|
390
|
-
# Note:: Returns +nil+ for Net::IMAP#append.
|
391
|
-
def uid_mapping
|
392
|
-
source_uids&.zip(assigned_uids)&.to_h
|
393
|
-
end
|
394
|
-
end
|
395
|
-
|
396
350
|
# MailboxList represents the data of an untagged +LIST+ response, for a
|
397
351
|
# _single_ mailbox path. IMAP#list returns an array of MailboxList objects.
|
398
352
|
#
|
@@ -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:
|
@@ -1997,11 +2001,10 @@ module Net
|
|
1997
2001
|
#
|
1998
2002
|
# n.b, uniqueid ⊂ uid-set. To avoid inconsistent return types, we always
|
1999
2003
|
# match uid_set even if that returns a single-member array.
|
2000
|
-
#
|
2001
2004
|
def resp_code_apnd__data
|
2002
2005
|
validity = number; SP!
|
2003
2006
|
dst_uids = uid_set # uniqueid ⊂ uid-set
|
2004
|
-
|
2007
|
+
AppendUID(validity, dst_uids)
|
2005
2008
|
end
|
2006
2009
|
|
2007
2010
|
# already matched: "COPYUID"
|
@@ -2011,7 +2014,25 @@ module Net
|
|
2011
2014
|
validity = number; SP!
|
2012
2015
|
src_uids = uid_set; SP!
|
2013
2016
|
dst_uids = uid_set
|
2014
|
-
|
2017
|
+
CopyUID(validity, src_uids, dst_uids)
|
2018
|
+
end
|
2019
|
+
|
2020
|
+
def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
|
2021
|
+
def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end
|
2022
|
+
|
2023
|
+
# TODO: remove this code in the v0.6.0 release
|
2024
|
+
def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids)
|
2025
|
+
return unless config.parser_use_deprecated_uidplus_data
|
2026
|
+
compact_uid_sets = [src_uids, dst_uids].compact
|
2027
|
+
count = compact_uid_sets.map { _1.count_with_duplicates }.max
|
2028
|
+
max = config.parser_max_deprecated_uidplus_data_size
|
2029
|
+
if count <= max
|
2030
|
+
src_uids &&= src_uids.each_ordered_number.to_a
|
2031
|
+
dst_uids = dst_uids.each_ordered_number.to_a
|
2032
|
+
UIDPlusData.new(validity, src_uids, dst_uids)
|
2033
|
+
elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size
|
2034
|
+
parse_error("uid-set is too large: %d > %d", count, max)
|
2035
|
+
end
|
2015
2036
|
end
|
2016
2037
|
|
2017
2038
|
ADDRESS_REGEXP = /\G
|
@@ -2137,15 +2158,9 @@ module Net
|
|
2137
2158
|
# uniqueid = nz-number
|
2138
2159
|
# ; Strictly ascending
|
2139
2160
|
def uid_set
|
2140
|
-
|
2141
|
-
|
2142
|
-
|
2143
|
-
when T_ATOM
|
2144
|
-
token.value.split(",").flat_map {|range|
|
2145
|
-
range = range.split(":").map {|uniqueid| Integer(uniqueid) }
|
2146
|
-
range.size == 1 ? range : Range.new(range.min, range.max).to_a
|
2147
|
-
}
|
2148
|
-
end
|
2161
|
+
set = sequence_set
|
2162
|
+
parse_error("uid-set cannot contain '*'") if set.include_star?
|
2163
|
+
set
|
2149
2164
|
end
|
2150
2165
|
|
2151
2166
|
def nil_atom
|
@@ -0,0 +1,73 @@
|
|
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
|
32
|
+
def empty? = buff.empty?
|
33
|
+
def done? = line_done? && !get_literal_size
|
34
|
+
def line_done? = buff.end_with?(CRLF)
|
35
|
+
def get_literal_size = /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i
|
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
|
56
|
+
def max_response_remaining = max_response_size &.- bytes_read
|
57
|
+
def response_too_large? = max_response_size &.< min_response_size
|
58
|
+
def min_response_size = bytes_read + min_response_remaining
|
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:, bytes_read:, literal_size:,
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|