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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d13d2ec5aeab6f5fce9d77841e77c690a2849d2d6393d4bac0847fcec6dcf2b
4
- data.tar.gz: 2821ba41ca70465fdfc38005908ae2fa2828ecd5c3425bf407df27f2e949ea2b
3
+ metadata.gz: 9419630b908b12c7f89846682dae953e50376d89ebb3b7391b3426bb34480991
4
+ data.tar.gz: 4558a77d38a4def28af960c201ca9359fcc828d5a316f76f95cf3c0e575702b6
5
5
  SHA512:
6
- metadata.gz: 45bb4a741d80d2097e487b3d1ca31196f97322d6104967c6cad6410d65321e372667a6ff5bbb5bf9aafbdddc7906c7fc05b88c2fff06984dc9efd8a309802ecc
7
- data.tar.gz: '08acdba3fdc323f01bc593ac7fde03ed5e06f1265be821263213fd53714b647e81305713d6829b67adba55dfb541a231e8c0a2303ad83e3efada4088e10c8419'
6
+ metadata.gz: 5f4baf570c8ed5732493ba801121ae092177fca3ab5f9162ef66f0db2a23e91c09fe740d8ddba52977e65737906a6735a8405bb94e923111970621e79b813141
7
+ data.tar.gz: 624e1b8a76630bffa007391b303eab5729b20665d9a5242f58749e0127340ddd1a842eb94ca46da085ccfa91cc093367348586cde705b51fe3eff25882f8f096
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
- gem "digest"
7
+ # gem "digest" # not included as a workaround for #576
8
8
  gem "strscan"
9
9
  gem "base64"
10
10
 
@@ -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
- send_symbol_data(data)
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(str, tag = nil)
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
- put_string("{" + str.bytesize.to_s + "}" + CRLF)
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
- def send_symbol_data(symbol)
119
- put_string("\\" + symbol.to_s)
120
- end
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
- imap.__send__(:put_string, @data)
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
- private
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 initialize(data)
133
- @data = data
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 Atom # :nodoc:
138
- def send_data(imap, tag)
139
- imap.__send__(:put_string, @data)
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 initialize(data)
148
- @data = data
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
- def send_data(imap, tag)
169
- imap.__send__(:send_literal, @data, tag)
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
- private
330
+ def send_data(imap, tag)
331
+ imap.__send__(:send_literal, data, tag, non_sync: non_sync)
332
+ end
333
+ end
176
334
 
177
- def initialize(data)
178
- @data = data
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
- def self.attr_accessor(attr, type: nil)
30
- return unless type
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
+ # 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
- def self.boolean(attr)
39
- define_method :"#{attr}=" do |val| super !!val end
40
- define_method :"#{attr}?" do send attr end
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.integer(attr)
44
- define_method :"#{attr}=" do |val| super Integer val end
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
- def self.enum(attr, enum)
48
- enum = enum.dup.freeze
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
- define_method :"#{attr}=" do |val|
51
- unless enum.include?(val)
52
- raise ArgumentError, "expected %s, got %p" % [expected, val]
53
- end
54
- super val
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
@@ -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.fetch(config) do
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[0.4] = Config[default.send(:defaults_hash)]
465
+ version_defaults[:default] = Config[default.send(:defaults_hash)]
412
466
 
413
- version_defaults[0] = Config[0.4].dup.update(
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.0] = Config[0]
419
- version_defaults[0.1] = Config[0]
420
- version_defaults[0.2] = Config[0]
421
- version_defaults[0.3] = Config[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.5] = Config[0.4].dup.update(
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[:default] = Config[0.4]
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
- version_defaults[:future] = Config[0.6]
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
@@ -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 mailbox with the associated quota.
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
- # Quota limit imposed on the mailbox.
409
+ # Storage limit imposed on the mailbox.
390
410
  #
391
411
  end
392
412