net-imap 0.4.12 → 0.4.21

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.
@@ -0,0 +1,524 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "config/attr_accessors"
4
+ require_relative "config/attr_inheritance"
5
+ require_relative "config/attr_type_coercion"
6
+
7
+ module Net
8
+ class IMAP
9
+
10
+ # Net::IMAP::Config <em>(available since +v0.4.13+)</em> stores
11
+ # configuration options for Net::IMAP clients. The global configuration can
12
+ # be seen at either Net::IMAP.config or Net::IMAP::Config.global, and the
13
+ # client-specific configuration can be seen at Net::IMAP#config.
14
+ #
15
+ # When creating a new client, all unhandled keyword arguments to
16
+ # Net::IMAP.new are delegated to Config.new. Every client has its own
17
+ # config.
18
+ #
19
+ # debug_client = Net::IMAP.new(hostname, debug: true)
20
+ # quiet_client = Net::IMAP.new(hostname, debug: false)
21
+ # debug_client.config.debug? # => true
22
+ # quiet_client.config.debug? # => false
23
+ #
24
+ # == Inheritance
25
+ #
26
+ # Configs have a parent[rdoc-ref:Config::AttrInheritance#parent] config, and
27
+ # any attributes which have not been set locally will inherit the parent's
28
+ # value. Every client creates its own specific config. By default, client
29
+ # configs inherit from Config.global.
30
+ #
31
+ # plain_client = Net::IMAP.new(hostname)
32
+ # debug_client = Net::IMAP.new(hostname, debug: true)
33
+ # quiet_client = Net::IMAP.new(hostname, debug: false)
34
+ #
35
+ # plain_client.config.inherited?(:debug) # => true
36
+ # debug_client.config.inherited?(:debug) # => false
37
+ # quiet_client.config.inherited?(:debug) # => false
38
+ #
39
+ # plain_client.config.debug? # => false
40
+ # debug_client.config.debug? # => true
41
+ # quiet_client.config.debug? # => false
42
+ #
43
+ # # Net::IMAP.debug is delegated to Net::IMAP::Config.global.debug
44
+ # Net::IMAP.debug = true
45
+ # plain_client.config.debug? # => true
46
+ # debug_client.config.debug? # => true
47
+ # quiet_client.config.debug? # => false
48
+ #
49
+ # Net::IMAP.debug = false
50
+ # plain_client.config.debug = true
51
+ # plain_client.config.inherited?(:debug) # => false
52
+ # plain_client.config.debug? # => true
53
+ # plain_client.config.reset(:debug)
54
+ # plain_client.config.inherited?(:debug) # => true
55
+ # plain_client.config.debug? # => false
56
+ #
57
+ # == Versioned defaults
58
+ #
59
+ # The effective default configuration for a specific +x.y+ version of
60
+ # +net-imap+ can be loaded with the +config+ keyword argument to
61
+ # Net::IMAP.new. Requesting default configurations for previous versions
62
+ # enables extra backward compatibility with those versions:
63
+ #
64
+ # client = Net::IMAP.new(hostname, config: 0.3)
65
+ # client.config.sasl_ir # => false
66
+ # client.config.responses_without_block # => :silence_deprecation_warning
67
+ #
68
+ # client = Net::IMAP.new(hostname, config: 0.4)
69
+ # client.config.sasl_ir # => true
70
+ # client.config.responses_without_block # => :silence_deprecation_warning
71
+ #
72
+ # client = Net::IMAP.new(hostname, config: 0.5)
73
+ # client.config.sasl_ir # => true
74
+ # client.config.responses_without_block # => :warn
75
+ #
76
+ # client = Net::IMAP.new(hostname, config: :future)
77
+ # client.config.sasl_ir # => true
78
+ # client.config.responses_without_block # => :raise
79
+ #
80
+ # The versioned default configs inherit certain specific config options from
81
+ # Config.global, for example #debug:
82
+ #
83
+ # client = Net::IMAP.new(hostname, config: 0.4)
84
+ # Net::IMAP.debug = false
85
+ # client.config.debug? # => false
86
+ #
87
+ # Net::IMAP.debug = true
88
+ # client.config.debug? # => true
89
+ #
90
+ # Use #load_defaults to globally behave like a specific version:
91
+ # client = Net::IMAP.new(hostname)
92
+ # client.config.sasl_ir # => true
93
+ # Net::IMAP.config.load_defaults 0.3
94
+ # client.config.sasl_ir # => false
95
+ #
96
+ # === Named defaults
97
+ # In addition to +x.y+ version numbers, the following aliases are supported:
98
+ #
99
+ # [+:default+]
100
+ # An alias for +:current+.
101
+ #
102
+ # >>>
103
+ # *NOTE*: This is _not_ the same as Config.default. It inherits some
104
+ # attributes from Config.global, for example: #debug.
105
+ # [+:current+]
106
+ # An alias for the current +x.y+ version's defaults.
107
+ # [+:next+]
108
+ # The _planned_ config for the next +x.y+ version.
109
+ # [+:future+]
110
+ # The _planned_ eventual config for some future +x.y+ version.
111
+ #
112
+ # For example, to raise exceptions for all current deprecations:
113
+ # client = Net::IMAP.new(hostname, config: :future)
114
+ # client.responses # raises an ArgumentError
115
+ #
116
+ # == Thread Safety
117
+ #
118
+ # *NOTE:* Updates to config objects are not synchronized for thread-safety.
119
+ #
120
+ class Config
121
+ # Array of attribute names that are _not_ loaded by #load_defaults.
122
+ DEFAULT_TO_INHERIT = %i[debug].freeze
123
+ private_constant :DEFAULT_TO_INHERIT
124
+
125
+ # The default config, which is hardcoded and frozen.
126
+ def self.default; @default end
127
+
128
+ # The global config object. Also available from Net::IMAP.config.
129
+ def self.global; @global if defined?(@global) end
130
+
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
142
+ def self.version_defaults; @version_defaults end
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
+ }
151
+
152
+ # :call-seq:
153
+ # Net::IMAP::Config[number] -> versioned config
154
+ # Net::IMAP::Config[symbol] -> named config
155
+ # Net::IMAP::Config[hash] -> new frozen config
156
+ # Net::IMAP::Config[config] -> same config
157
+ #
158
+ # Given a version number, returns the default configuration for the target
159
+ # version. See Config@Versioned+defaults.
160
+ #
161
+ # Given a version name, returns the default configuration for the target
162
+ # version. See Config@Named+defaults.
163
+ #
164
+ # Given a Hash, creates a new _frozen_ config which inherits from
165
+ # Config.global. Use Config.new for an unfrozen config.
166
+ #
167
+ # Given a config, returns that same config.
168
+ def self.[](config)
169
+ if config.is_a?(Config) then config
170
+ elsif config.nil? && global.nil? then nil
171
+ elsif config.respond_to?(:to_hash) then new(global, **config).freeze
172
+ else
173
+ version_defaults[config] or
174
+ case config
175
+ when Numeric
176
+ raise RangeError, "unknown config version: %p" % [config]
177
+ when String, Symbol
178
+ raise KeyError, "unknown config name: %p" % [config]
179
+ else
180
+ raise TypeError, "no implicit conversion of %s to %s" % [
181
+ config.class, Config
182
+ ]
183
+ end
184
+ end
185
+ end
186
+
187
+ include AttrAccessors
188
+ include AttrInheritance
189
+ include AttrTypeCoercion
190
+
191
+ # The debug mode (boolean). The default value is +false+.
192
+ #
193
+ # When #debug is +true+:
194
+ # * Data sent to and received from the server will be logged.
195
+ # * ResponseParser will print warnings with extra detail for parse
196
+ # errors. _This may include recoverable errors._
197
+ # * ResponseParser makes extra assertions.
198
+ #
199
+ # *NOTE:* Versioned default configs inherit #debug from Config.global, and
200
+ # #load_defaults will not override #debug.
201
+ attr_accessor :debug, type: :boolean
202
+
203
+ # method: debug?
204
+ # :call-seq: debug? -> boolean
205
+ #
206
+ # Alias for #debug
207
+
208
+ # Seconds to wait until a connection is opened.
209
+ #
210
+ # Applied separately for establishing TCP connection and starting a TLS
211
+ # connection.
212
+ #
213
+ # If the IMAP object cannot open a connection within this time,
214
+ # it raises a Net::OpenTimeout exception.
215
+ #
216
+ # See Net::IMAP.new and Net::IMAP#starttls.
217
+ #
218
+ # The default value is +30+ seconds.
219
+ attr_accessor :open_timeout, type: Integer
220
+
221
+ # Seconds to wait until an IDLE response is received, after
222
+ # the client asks to leave the IDLE state.
223
+ #
224
+ # See Net::IMAP#idle and Net::IMAP#idle_done.
225
+ #
226
+ # The default value is +5+ seconds.
227
+ attr_accessor :idle_response_timeout, type: Integer
228
+
229
+ # Whether to use the +SASL-IR+ extension when the server and \SASL
230
+ # mechanism both support it. Can be overridden by the +sasl_ir+ keyword
231
+ # parameter to Net::IMAP#authenticate.
232
+ #
233
+ # <em>(Support for +SASL-IR+ was added in +v0.4.0+.)</em>
234
+ #
235
+ # ==== Valid options
236
+ #
237
+ # [+false+ <em>(original behavior, before support was added)</em>]
238
+ # Do not use +SASL-IR+, even when it is supported by the server and the
239
+ # mechanism.
240
+ #
241
+ # [+true+ <em>(default since +v0.4+)</em>]
242
+ # Use +SASL-IR+ when it is supported by the server and the mechanism.
243
+ attr_accessor :sasl_ir, type: :boolean
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?
279
+
280
+ # Controls the behavior of Net::IMAP#responses when called without any
281
+ # arguments (+type+ or +block+).
282
+ #
283
+ # ==== Valid options
284
+ #
285
+ # [+:silence_deprecation_warning+ <em>(original behavior)</em>]
286
+ # Returns the mutable responses hash (without any warnings).
287
+ # <em>This is not thread-safe.</em>
288
+ #
289
+ # [+:warn+ <em>(default since +v0.5+)</em>]
290
+ # Prints a warning and returns the mutable responses hash.
291
+ # <em>This is not thread-safe.</em>
292
+ #
293
+ # [+:frozen_dup+ <em>(planned default for +v0.6+)</em>]
294
+ # Returns a frozen copy of the unhandled responses hash, with frozen
295
+ # array values.
296
+ #
297
+ # Note that calling IMAP#responses with a +type+ and without a block is
298
+ # not configurable and always behaves like +:frozen_dup+.
299
+ #
300
+ # <em>(+:frozen_dup+ config option was added in +v0.4.17+)</em>
301
+ #
302
+ # [+:raise+]
303
+ # Raise an ArgumentError with the deprecation warning.
304
+ #
305
+ # Note: #responses_without_args is an alias for #responses_without_block.
306
+ attr_accessor :responses_without_block, type: Enum[
307
+ :silence_deprecation_warning, :warn, :frozen_dup, :raise,
308
+ ]
309
+
310
+ alias responses_without_args responses_without_block # :nodoc:
311
+ alias responses_without_args= responses_without_block= # :nodoc:
312
+
313
+ ##
314
+ # :attr_accessor: responses_without_args
315
+ #
316
+ # Alias for responses_without_block
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
+
379
+ # Creates a new config object and initialize its attribute with +attrs+.
380
+ #
381
+ # If +parent+ is not given, the global config is used by default.
382
+ #
383
+ # If a block is given, the new config object is yielded to it.
384
+ def initialize(parent = Config.global, **attrs)
385
+ super(parent)
386
+ update(**attrs)
387
+ yield self if block_given?
388
+ end
389
+
390
+ # :call-seq: update(**attrs) -> self
391
+ #
392
+ # Assigns all of the provided +attrs+ to this config, and returns +self+.
393
+ #
394
+ # An ArgumentError is raised unless every key in +attrs+ matches an
395
+ # assignment method on Config.
396
+ #
397
+ # >>>
398
+ # *NOTE:* #update is not atomic. If an exception is raised due to an
399
+ # invalid attribute value, +attrs+ may be partially applied.
400
+ def update(**attrs)
401
+ unless (bad = attrs.keys.reject { respond_to?(:"#{_1}=") }).empty?
402
+ raise ArgumentError, "invalid config options: #{bad.join(", ")}"
403
+ end
404
+ attrs.each do send(:"#{_1}=", _2) end
405
+ self
406
+ end
407
+
408
+ # :call-seq:
409
+ # with(**attrs) -> config
410
+ # with(**attrs) {|config| } -> result
411
+ #
412
+ # Without a block, returns a new config which inherits from self. With a
413
+ # block, yields the new config and returns the block's result.
414
+ #
415
+ # If no keyword arguments are given, an ArgumentError will be raised.
416
+ #
417
+ # If +self+ is frozen, the copy will also be frozen.
418
+ def with(**attrs)
419
+ attrs.empty? and
420
+ raise ArgumentError, "expected keyword arguments, none given"
421
+ copy = new(**attrs)
422
+ copy.freeze if frozen?
423
+ block_given? ? yield(copy) : copy
424
+ end
425
+
426
+ # :call-seq: load_defaults(version) -> self
427
+ #
428
+ # Resets the current config to behave like the versioned default
429
+ # configuration for +version+. #parent will not be changed.
430
+ #
431
+ # Some config attributes default to inheriting from their #parent (which
432
+ # is usually Config.global) and are left unchanged, for example: #debug.
433
+ #
434
+ # See Config@Versioned+defaults and Config@Named+defaults.
435
+ def load_defaults(version)
436
+ [Numeric, Symbol, String].any? { _1 === version } or
437
+ raise ArgumentError, "expected number or symbol, got %p" % [version]
438
+ update(**Config[version].defaults_hash)
439
+ end
440
+
441
+ # :call-seq: to_h -> hash
442
+ #
443
+ # Returns all config attributes in a hash.
444
+ def to_h; data.members.to_h { [_1, send(_1)] } end
445
+
446
+ protected
447
+
448
+ def defaults_hash
449
+ to_h.reject {|k,v| DEFAULT_TO_INHERIT.include?(k) }
450
+ end
451
+
452
+ @default = new(
453
+ debug: false,
454
+ open_timeout: 30,
455
+ idle_response_timeout: 5,
456
+ sasl_ir: true,
457
+ max_response_size: nil,
458
+ responses_without_block: :silence_deprecation_warning,
459
+ parser_use_deprecated_uidplus_data: true,
460
+ parser_max_deprecated_uidplus_data_size: 1000,
461
+ ).freeze
462
+
463
+ @global = default.new
464
+
465
+ version_defaults[:default] = Config[default.send(:defaults_hash)]
466
+
467
+ version_defaults[0r] = Config[:default].dup.update(
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,
472
+ ).freeze
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]
477
+
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
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
489
+
490
+ version_defaults[0.6r] = Config[0.5r].dup.update(
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(
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]
513
+
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
522
+ end
523
+ end
524
+ end
@@ -16,8 +16,8 @@ module Net
16
16
  #
17
17
  # ==== Obsolete arguments
18
18
  #
19
- # Using obsolete arguments does not a warning. Obsolete arguments will be
20
- # deprecated by a future release.
19
+ # Use of obsolete arguments does not print a warning. Obsolete arguments
20
+ # will be deprecated by a future release.
21
21
  #
22
22
  # If a second positional argument is given and it is a hash (or is
23
23
  # convertible via +#to_hash+), it is converted to keyword arguments.
@@ -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
  #
@@ -154,7 +154,7 @@ module Net
154
154
  end
155
155
 
156
156
  # To be used conditionally:
157
- # assert_no_lookahead if Net::IMAP.debug
157
+ # assert_no_lookahead if config.debug?
158
158
  def assert_no_lookahead
159
159
  @token.nil? or
160
160
  parse_error("assertion failed: expected @token.nil?, actual %s: %p",
@@ -181,23 +181,23 @@ module Net
181
181
  end
182
182
 
183
183
  def peek_str?(str)
184
- assert_no_lookahead if Net::IMAP.debug
184
+ assert_no_lookahead if config.debug?
185
185
  @str[@pos, str.length] == str
186
186
  end
187
187
 
188
188
  def peek_re(re)
189
- assert_no_lookahead if Net::IMAP.debug
189
+ assert_no_lookahead if config.debug?
190
190
  re.match(@str, @pos)
191
191
  end
192
192
 
193
193
  def accept_re(re)
194
- assert_no_lookahead if Net::IMAP.debug
194
+ assert_no_lookahead if config.debug?
195
195
  re.match(@str, @pos) and @pos = $~.end(0)
196
196
  $~
197
197
  end
198
198
 
199
199
  def match_re(re, name)
200
- assert_no_lookahead if Net::IMAP.debug
200
+ assert_no_lookahead if config.debug?
201
201
  if re.match(@str, @pos)
202
202
  @pos = $~.end(0)
203
203
  $~
@@ -212,7 +212,7 @@ module Net
212
212
 
213
213
  def parse_error(fmt, *args)
214
214
  msg = format(fmt, *args)
215
- if IMAP.debug
215
+ if config.debug?
216
216
  local_path = File.dirname(__dir__)
217
217
  tok = @token ? "%s: %p" % [@token.symbol, @token.value] : "nil"
218
218
  warn "%s %s: %s" % [self.class, __method__, msg]