net-imap 0.4.19 → 0.4.24
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/command_data.rb +182 -21
- data/lib/net/imap/config/attr_type_coercion.rb +23 -22
- data/lib/net/imap/config.rb +101 -20
- data/lib/net/imap/errors.rb +33 -0
- data/lib/net/imap/response_data.rb +22 -2
- data/lib/net/imap/response_reader.rb +82 -0
- data/lib/net/imap/sasl/scram_authenticator.rb +74 -0
- data/lib/net/imap/sequence_set.rb +70 -63
- data/lib/net/imap.rb +215 -69
- 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: 9419630b908b12c7f89846682dae953e50376d89ebb3b7391b3426bb34480991
|
|
4
|
+
data.tar.gz: 4558a77d38a4def28af960c201ca9359fcc828d5a316f76f95cf3c0e575702b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f4baf570c8ed5732493ba801121ae092177fca3ab5f9162ef66f0db2a23e91c09fe740d8ddba52977e65737906a6735a8405bb94e923111970621e79b813141
|
|
7
|
+
data.tar.gz: 624e1b8a76630bffa007391b303eab5729b20665d9a5242f58749e0127340ddd1a842eb94ca46da085ccfa91cc093367348586cde705b51fe3eff25882f8f096
|
data/Gemfile
CHANGED
|
@@ -25,6 +25,7 @@ module Net
|
|
|
25
25
|
end
|
|
26
26
|
when Time, Date, DateTime
|
|
27
27
|
when Symbol
|
|
28
|
+
Flag.validate(data)
|
|
28
29
|
else
|
|
29
30
|
data.validate
|
|
30
31
|
end
|
|
@@ -45,7 +46,7 @@ module Net
|
|
|
45
46
|
when Date
|
|
46
47
|
send_date_data(data)
|
|
47
48
|
when Symbol
|
|
48
|
-
|
|
49
|
+
Flag[data].send_data(self, tag)
|
|
49
50
|
else
|
|
50
51
|
data.send_data(self, tag)
|
|
51
52
|
end
|
|
@@ -77,9 +78,23 @@ module Net
|
|
|
77
78
|
put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"')
|
|
78
79
|
end
|
|
79
80
|
|
|
80
|
-
def send_literal(
|
|
81
|
+
def send_binary_literal(*a, **kw) send_literal(*a, **kw, binary: true) end
|
|
82
|
+
|
|
83
|
+
# `non_sync` is an optional tri-state flag:
|
|
84
|
+
# * `true` -> Force non-synchronizing `LITERAL+`/`LITERAL-` behavior.
|
|
85
|
+
# TODO: raise or warn when capabilities don't allow non_sync.
|
|
86
|
+
# * `false` -> Force normal synchronizing literal behavior.
|
|
87
|
+
# * `nil` -> (default) Currently behaves like `false` (will be dynamic).
|
|
88
|
+
# TODO: Dynamic, based on capabilities and bytesize.
|
|
89
|
+
def send_literal(str, tag = nil, binary: false, non_sync: nil)
|
|
81
90
|
synchronize do
|
|
82
|
-
|
|
91
|
+
prefix = "~" if binary
|
|
92
|
+
plus = "+" if non_sync
|
|
93
|
+
put_string("#{prefix}{#{str.bytesize}#{plus}}\r\n")
|
|
94
|
+
if non_sync
|
|
95
|
+
put_string(str)
|
|
96
|
+
return
|
|
97
|
+
end
|
|
83
98
|
@continued_command_tag = tag
|
|
84
99
|
@continuation_request_exception = nil
|
|
85
100
|
begin
|
|
@@ -115,37 +130,148 @@ module Net
|
|
|
115
130
|
def send_date_data(date) put_string Net::IMAP.encode_date(date) end
|
|
116
131
|
def send_time_data(time) put_string Net::IMAP.encode_time(time) end
|
|
117
132
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
133
|
+
# simplistic emulation of CommandData = Data.define(:data)
|
|
134
|
+
class CommandData # :nodoc:
|
|
135
|
+
class << self
|
|
136
|
+
def new(arg = nil, data: arg) super(data: data) end
|
|
137
|
+
alias :[] :new
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def initialize(data:)
|
|
141
|
+
@data = data
|
|
142
|
+
freeze
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
attr_reader :data
|
|
146
|
+
|
|
147
|
+
def to_h(&block) block ? to_h.to_h(&block) : { data: data } end
|
|
148
|
+
def ==(other) self.class === other && to_h == other.to_h end
|
|
149
|
+
def eql?(other) self.class === other && to_h.eql?(other.to_h) end
|
|
150
|
+
|
|
151
|
+
# following class definition goes beyond the basic Data.define(:data)
|
|
152
|
+
##
|
|
153
|
+
|
|
154
|
+
def self.validate(...)
|
|
155
|
+
data = new(...)
|
|
156
|
+
data.validate
|
|
157
|
+
data
|
|
158
|
+
end
|
|
121
159
|
|
|
122
|
-
class RawData # :nodoc:
|
|
123
160
|
def send_data(imap, tag)
|
|
124
|
-
|
|
161
|
+
raise NoMethodError, "#{self.class} must implement #{__method__}"
|
|
125
162
|
end
|
|
126
163
|
|
|
127
164
|
def validate
|
|
128
165
|
end
|
|
166
|
+
end
|
|
129
167
|
|
|
130
|
-
|
|
168
|
+
# Represents IMAP +text+ data, which may contain any 7-bit ASCII character,
|
|
169
|
+
# except for +NULL+, +CR+, or +LF+. +text+ is extended to allow any
|
|
170
|
+
# multibyte +UTF-8+ character when either +UTF8=ACCEPT+ or +IMAP4rev2+ have
|
|
171
|
+
# been enabled, or when the server supports only +IMAP4rev2+ and not earlier
|
|
172
|
+
# IMAP revisions, or when the server advertises +UTF8=ONLY+.
|
|
173
|
+
#
|
|
174
|
+
# NOTE: The current implementation does not validate whether the connection
|
|
175
|
+
# currently supports UTF-8. Future versions may change.
|
|
176
|
+
#
|
|
177
|
+
# The string's bytes must be valid ASCII or valid UTF-8. The string's
|
|
178
|
+
# reported encoding is ignored, but the string is _not_ transcoded.
|
|
179
|
+
class RawText < CommandData # :nodoc:
|
|
180
|
+
def initialize(data:)
|
|
181
|
+
data = String(data.to_str)
|
|
182
|
+
data = if [Encoding::ASCII, Encoding::UTF_8].include?(data.encoding)
|
|
183
|
+
-data
|
|
184
|
+
elsif data.ascii_only?
|
|
185
|
+
-(data.dup.force_encoding("ASCII"))
|
|
186
|
+
else
|
|
187
|
+
-(data.dup.force_encoding("UTF-8"))
|
|
188
|
+
end
|
|
189
|
+
super
|
|
190
|
+
validate
|
|
191
|
+
end
|
|
131
192
|
|
|
132
|
-
def
|
|
133
|
-
|
|
193
|
+
def validate
|
|
194
|
+
if data.include?("\0")
|
|
195
|
+
raise DataFormatError, "NULL byte must be binary literal encoded"
|
|
196
|
+
elsif !data.valid_encoding?
|
|
197
|
+
raise DataFormatError, "invalid UTF-8 must be literal encoded"
|
|
198
|
+
elsif /[\r\n]/.match?(data)
|
|
199
|
+
raise DataFormatError, "CR and LF bytes must be literal encoded"
|
|
200
|
+
end
|
|
134
201
|
end
|
|
202
|
+
|
|
203
|
+
def ascii_only?; data.ascii_only? end
|
|
204
|
+
|
|
205
|
+
def send_data(imap, tag) imap.__send__(:put_string, data) end
|
|
135
206
|
end
|
|
136
207
|
|
|
137
|
-
class
|
|
138
|
-
def
|
|
139
|
-
|
|
208
|
+
class RawData < CommandData # :nodoc:
|
|
209
|
+
def initialize(data:)
|
|
210
|
+
data = split_parts(data)
|
|
211
|
+
super
|
|
212
|
+
validate
|
|
140
213
|
end
|
|
141
214
|
|
|
215
|
+
def send_data(imap, tag) data.each do _1.send_data(imap, tag) end end
|
|
216
|
+
|
|
142
217
|
def validate
|
|
218
|
+
return unless RawText === data.last
|
|
219
|
+
text = data.last.data
|
|
220
|
+
if text.rindex(/~?\{[1-9]\d*\+?\}\z/n)
|
|
221
|
+
raise DataFormatError, "RawData cannot end with literal continuation"
|
|
222
|
+
end
|
|
143
223
|
end
|
|
144
224
|
|
|
145
225
|
private
|
|
146
226
|
|
|
147
|
-
def
|
|
148
|
-
|
|
227
|
+
def split_parts(data)
|
|
228
|
+
data = data.b # dups and ensures BINARY encoding
|
|
229
|
+
parts = []
|
|
230
|
+
while data.match(/(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n)
|
|
231
|
+
text, binary, bytesize, non_sync, data = $`, !!$1, $2, !!$3, $'
|
|
232
|
+
bytesize = Integer bytesize, 10
|
|
233
|
+
parts << RawText[text] unless text.empty?
|
|
234
|
+
parts << extract_literal(data,
|
|
235
|
+
binary: binary,
|
|
236
|
+
bytesize: bytesize,
|
|
237
|
+
non_sync: non_sync)
|
|
238
|
+
data[0, bytesize] = ""
|
|
239
|
+
end
|
|
240
|
+
parts << RawText[data] unless data.empty?
|
|
241
|
+
parts
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def extract_literal(data, binary:, bytesize:, non_sync:)
|
|
245
|
+
if data.bytesize < bytesize
|
|
246
|
+
raise DataFormatError, "Too few bytes in string for literal, " \
|
|
247
|
+
"expected: %s, remaining: %s" % [bytesize, data.bytesize]
|
|
248
|
+
end
|
|
249
|
+
literal = data.byteslice(0, bytesize)
|
|
250
|
+
(binary ? Literal8 : Literal).new(data: literal, non_sync: non_sync)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
class Atom < CommandData # :nodoc:
|
|
255
|
+
def initialize(**)
|
|
256
|
+
super
|
|
257
|
+
validate
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def validate
|
|
261
|
+
data.to_s.ascii_only? \
|
|
262
|
+
or raise DataFormatError, "#{self.class} must be ASCII only"
|
|
263
|
+
data.match?(ResponseParser::Patterns::ATOM_SPECIALS) \
|
|
264
|
+
and raise DataFormatError, "#{self.class} must not contain atom-specials"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def send_data(imap, tag)
|
|
268
|
+
imap.__send__(:put_string, data.to_s)
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
class Flag < Atom # :nodoc:
|
|
273
|
+
def send_data(imap, tag)
|
|
274
|
+
imap.__send__(:put_string, "\\#{data}")
|
|
149
275
|
end
|
|
150
276
|
end
|
|
151
277
|
|
|
@@ -165,17 +291,52 @@ module Net
|
|
|
165
291
|
end
|
|
166
292
|
|
|
167
293
|
class Literal # :nodoc:
|
|
168
|
-
|
|
169
|
-
|
|
294
|
+
class << self
|
|
295
|
+
def new(_data = nil, _non_sync = nil, data: _data, non_sync: _non_sync)
|
|
296
|
+
super(data: data, non_sync: non_sync)
|
|
297
|
+
end
|
|
298
|
+
alias :[] :new
|
|
170
299
|
end
|
|
171
300
|
|
|
301
|
+
attr_reader :data, :non_sync
|
|
302
|
+
|
|
303
|
+
def to_h(&block) block ? to_h.to_h(&block) : { data: data, non_sync: non_sync } end
|
|
304
|
+
def ==(other) self.class === other && to_h == other.to_h end
|
|
305
|
+
def eql?(other) self.class === other && to_h.eql?(other.to_h) end
|
|
306
|
+
|
|
307
|
+
def initialize(data:, non_sync: nil)
|
|
308
|
+
data = -String(data.to_str).b or
|
|
309
|
+
raise DataFormatError, "#{self.class} expects string input"
|
|
310
|
+
@data, @non_sync = data, non_sync
|
|
311
|
+
validate
|
|
312
|
+
freeze
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def self.validate(...)
|
|
316
|
+
data = new(...)
|
|
317
|
+
data.validate
|
|
318
|
+
data
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def bytesize; data.bytesize end
|
|
322
|
+
|
|
172
323
|
def validate
|
|
324
|
+
if data.include?("\0")
|
|
325
|
+
raise DataFormatError, "NULL byte not allowed in #{self.class}. " \
|
|
326
|
+
"Use #{Literal8} or a null-safe encoding."
|
|
327
|
+
end
|
|
173
328
|
end
|
|
174
329
|
|
|
175
|
-
|
|
330
|
+
def send_data(imap, tag)
|
|
331
|
+
imap.__send__(:send_literal, data, tag, non_sync: non_sync)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
176
334
|
|
|
177
|
-
|
|
178
|
-
|
|
335
|
+
class Literal8 < Literal # :nodoc:
|
|
336
|
+
def validate; nil end # all bytes are okay
|
|
337
|
+
|
|
338
|
+
def send_data(imap, tag)
|
|
339
|
+
imap.__send__(:send_binary_literal, data, tag, non_sync: non_sync)
|
|
179
340
|
end
|
|
180
341
|
end
|
|
181
342
|
|
|
@@ -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,33 @@ module Net
|
|
|
26
28
|
end
|
|
27
29
|
private_class_method :included
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
# Used in v0.5.8+ for Ractor sharability.
|
|
32
|
+
def self.safe(...) nil.instance_eval(...).freeze end
|
|
33
|
+
private_class_method :safe
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
Types = Hash.new do |h, type|
|
|
36
|
+
type.nil? || Proc === type or raise TypeError, "type not nil or Proc"
|
|
37
|
+
safe{type}
|
|
41
38
|
end
|
|
39
|
+
Types[:boolean] = Boolean = safe{-> {!!_1}}
|
|
40
|
+
Types[Integer] = safe{->{Integer(_1)}}
|
|
42
41
|
|
|
43
|
-
def self.
|
|
44
|
-
|
|
42
|
+
def self.attr_accessor(attr, type: nil)
|
|
43
|
+
type = Types[type] or return
|
|
44
|
+
define_method :"#{attr}=" do |val| super type[val] end
|
|
45
|
+
define_method :"#{attr}?" do send attr end if type == Boolean
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
NilOrInteger = safe{->val { Integer val unless val.nil? }}
|
|
49
|
+
|
|
50
|
+
Enum = ->(*enum) {
|
|
51
|
+
enum = safe{enum}
|
|
49
52
|
expected = -"one of #{enum.map(&:inspect).join(", ")}"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
56
|
-
end
|
|
53
|
+
safe{->val {
|
|
54
|
+
return val if enum.include?(val)
|
|
55
|
+
raise ArgumentError, "expected %s, got %p" % [expected, val]
|
|
56
|
+
}}
|
|
57
|
+
}
|
|
57
58
|
|
|
58
59
|
end
|
|
59
60
|
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
|
|
|
@@ -295,7 +348,7 @@ module Net
|
|
|
295
348
|
#
|
|
296
349
|
# [+false+ <em>(planned default for +v0.6+)</em>]
|
|
297
350
|
# ResponseParser _only_ uses AppendUIDData and CopyUIDData.
|
|
298
|
-
attr_accessor :parser_use_deprecated_uidplus_data, type: [
|
|
351
|
+
attr_accessor :parser_use_deprecated_uidplus_data, type: Enum[
|
|
299
352
|
true, :up_to_max_size, false
|
|
300
353
|
]
|
|
301
354
|
|
|
@@ -401,6 +454,7 @@ module Net
|
|
|
401
454
|
open_timeout: 30,
|
|
402
455
|
idle_response_timeout: 5,
|
|
403
456
|
sasl_ir: true,
|
|
457
|
+
max_response_size: nil,
|
|
404
458
|
responses_without_block: :silence_deprecation_warning,
|
|
405
459
|
parser_use_deprecated_uidplus_data: true,
|
|
406
460
|
parser_max_deprecated_uidplus_data_size: 1000,
|
|
@@ -408,36 +462,63 @@ module Net
|
|
|
408
462
|
|
|
409
463
|
@global = default.new
|
|
410
464
|
|
|
411
|
-
version_defaults[
|
|
465
|
+
version_defaults[:default] = Config[default.send(:defaults_hash)]
|
|
412
466
|
|
|
413
|
-
version_defaults[
|
|
467
|
+
version_defaults[0r] = Config[:default].dup.update(
|
|
414
468
|
sasl_ir: false,
|
|
469
|
+
max_response_size: nil,
|
|
415
470
|
parser_use_deprecated_uidplus_data: true,
|
|
416
471
|
parser_max_deprecated_uidplus_data_size: 10_000,
|
|
417
472
|
).freeze
|
|
418
|
-
version_defaults[0.
|
|
419
|
-
version_defaults[0.
|
|
420
|
-
version_defaults[0.
|
|
421
|
-
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]
|
|
422
477
|
|
|
423
|
-
version_defaults[0.
|
|
478
|
+
version_defaults[0.4r] = Config[0.3r].dup.update(
|
|
479
|
+
sasl_ir: true,
|
|
480
|
+
parser_max_deprecated_uidplus_data_size: 1000,
|
|
481
|
+
).freeze
|
|
482
|
+
|
|
483
|
+
version_defaults[0.5r] = Config[0.4r].dup.update(
|
|
484
|
+
max_response_size: 512 << 20, # 512 MiB
|
|
424
485
|
responses_without_block: :warn,
|
|
425
486
|
parser_use_deprecated_uidplus_data: :up_to_max_size,
|
|
426
487
|
parser_max_deprecated_uidplus_data_size: 100,
|
|
427
488
|
).freeze
|
|
428
489
|
|
|
429
|
-
version_defaults[
|
|
430
|
-
version_defaults[:current] = Config[0.4]
|
|
431
|
-
version_defaults[:next] = Config[0.5]
|
|
432
|
-
|
|
433
|
-
version_defaults[0.6] = Config[0.5].dup.update(
|
|
490
|
+
version_defaults[0.6r] = Config[0.5r].dup.update(
|
|
434
491
|
responses_without_block: :frozen_dup,
|
|
435
492
|
parser_use_deprecated_uidplus_data: false,
|
|
436
493
|
parser_max_deprecated_uidplus_data_size: 0,
|
|
437
494
|
).freeze
|
|
438
|
-
|
|
495
|
+
|
|
496
|
+
version_defaults[0.7r] = Config[0.6r].dup.update(
|
|
497
|
+
).freeze
|
|
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]
|
|
439
513
|
|
|
440
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
|
|
441
522
|
end
|
|
442
523
|
end
|
|
443
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
|
|
@@ -295,6 +295,14 @@ module Net
|
|
|
295
295
|
# because the server doesn't allow deletion of mailboxes with children.
|
|
296
296
|
# #data is +nil+.
|
|
297
297
|
#
|
|
298
|
+
# ==== <tt>QUOTA=RES-*</tt> response codes
|
|
299
|
+
# See {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208.html#section-4.3].
|
|
300
|
+
# * +OVERQUOTA+ (also in RFC5530[https://www.rfc-editor.org/rfc/rfc5530]),
|
|
301
|
+
# with a tagged +NO+ response to an +APPEND+/+COPY+/+MOVE+ command when
|
|
302
|
+
# the command would put the target mailbox over any quota, and with an
|
|
303
|
+
# untagged +NO+ when a mailbox exceeds a soft quota (which may be caused
|
|
304
|
+
# be external events). #data is +nil+.
|
|
305
|
+
#
|
|
298
306
|
# ==== +CONDSTORE+ extension
|
|
299
307
|
# See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
|
|
300
308
|
# * +NOMODSEQ+, when selecting a mailbox that does not support
|
|
@@ -369,12 +377,24 @@ module Net
|
|
|
369
377
|
# Net::IMAP#getquotaroot returns an array containing both MailboxQuotaRoot
|
|
370
378
|
# and MailboxQuota objects.
|
|
371
379
|
#
|
|
380
|
+
# ==== Required capability
|
|
381
|
+
#
|
|
382
|
+
# Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
|
|
383
|
+
# or <tt>QUOTA=RES-STORAGE</tt>
|
|
384
|
+
# [RFC9208[https://www.rfc-editor.org/rfc/rfc9208]] capability.
|
|
372
385
|
class MailboxQuota < Struct.new(:mailbox, :usage, :quota)
|
|
373
386
|
##
|
|
374
387
|
# method: mailbox
|
|
375
388
|
# :call-seq: mailbox -> string
|
|
376
389
|
#
|
|
377
|
-
# The
|
|
390
|
+
# The quota root with the associated quota.
|
|
391
|
+
#
|
|
392
|
+
# NOTE: this was mistakenly named "mailbox". But the quota root's name may
|
|
393
|
+
# differ from the mailbox. A single quota root may cover multiple
|
|
394
|
+
# mailboxes, and a single mailbox may be governed by multiple quota roots.
|
|
395
|
+
|
|
396
|
+
# The quota root with the associated quota.
|
|
397
|
+
alias quota_root mailbox
|
|
378
398
|
|
|
379
399
|
##
|
|
380
400
|
# method: usage
|
|
@@ -386,7 +406,7 @@ module Net
|
|
|
386
406
|
# method: quota
|
|
387
407
|
# :call-seq: quota -> Integer
|
|
388
408
|
#
|
|
389
|
-
#
|
|
409
|
+
# Storage limit imposed on the mailbox.
|
|
390
410
|
#
|
|
391
411
|
end
|
|
392
412
|
|