net-imap 0.4.14 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06be993e038a4432ec954b11701ce86f946c456f9125d2b539e6c21d05e728ab
4
- data.tar.gz: 7807fb77537a843e9d9a2ad0ad892dc3e75af3a734f2c4c68a1e2cbae4d34ef1
3
+ metadata.gz: ab1b84ea6eac7b0d57793127bc365c79b63eb9564386427eb66cca91cf5df294
4
+ data.tar.gz: 3040f462d83f7a364fed076a730d5a9ba0512ea52ab0368765befb0e2d700dd4
5
5
  SHA512:
6
- metadata.gz: 182edade4a45d1c6f4772332f2f9cd4704827895770764202aa843bfbb754defff11a8037c1955e352642201c57cea850ea51aa8e4fe6c1761b36c78562b1c5b
7
- data.tar.gz: 2fff884a15a87ae7cb4a5ac422f743b5accd1ea98b34bf994cbb18238577cc259a1a672a04e5dac14864ad61bdafd90c43c160ede8ba369246fd3bb265e5cff2
6
+ metadata.gz: a7565d3002323cdaad204bbd5eceb0b64d265e61daa41d9e3c61b8ea12ff3bbbb5f5e62298138f48a96c32553b1fcf3c6dcab7cae5776a22a521abe77b6e153c
7
+ data.tar.gz: 73b9338a55d33e703c86217ee80ee7486e92dbf67b232c152d4115a89a76c4f384c39e34888d24ec8cf389af1d67ca1d1d84e3edc676ecc501495cf6b66d7dfe
@@ -40,10 +40,10 @@ module Net
40
40
  send_number_data(data)
41
41
  when Array
42
42
  send_list_data(data, tag)
43
- when Date
44
- send_date_data(data)
45
43
  when Time, DateTime
46
44
  send_time_data(data)
45
+ when Date
46
+ send_date_data(data)
47
47
  when Symbol
48
48
  send_symbol_data(data)
49
49
  else
@@ -18,6 +18,8 @@ module Net
18
18
  super(attr)
19
19
  AttrTypeCoercion.attr_accessor(attr, type: type)
20
20
  end
21
+
22
+ module_function def Integer?; NilOrInteger end
21
23
  end
22
24
  private_constant :Macros
23
25
 
@@ -26,34 +28,36 @@ module Net
26
28
  end
27
29
  private_class_method :included
28
30
 
29
- 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
31
+ if defined?(Ractor.make_shareable)
32
+ def self.safe(...) Ractor.make_shareable nil.instance_eval(...).freeze end
33
+ else
34
+ def self.safe(...) nil.instance_eval(...).freeze end
36
35
  end
36
+ private_class_method :safe
37
37
 
38
- def self.boolean(attr)
39
- define_method :"#{attr}=" do |val| super !!val end
40
- define_method :"#{attr}?" do send attr end
38
+ Types = Hash.new do |h, type|
39
+ type.nil? || Proc === type or raise TypeError, "type not nil or Proc"
40
+ safe{type}
41
41
  end
42
+ Types[:boolean] = Boolean = safe{-> {!!_1}}
43
+ Types[Integer] = safe{->{Integer(_1)}}
42
44
 
43
- def self.integer(attr)
44
- define_method :"#{attr}=" do |val| super Integer val end
45
+ def self.attr_accessor(attr, type: nil)
46
+ type = Types[type] or return
47
+ define_method :"#{attr}=" do |val| super type[val] end
48
+ define_method :"#{attr}?" do send attr end if type == Boolean
45
49
  end
46
50
 
47
- def self.enum(attr, enum)
48
- enum = enum.dup.freeze
51
+ NilOrInteger = safe{->val { Integer val unless val.nil? }}
52
+
53
+ Enum = ->(*enum) {
54
+ enum = safe{enum}
49
55
  expected = -"one of #{enum.map(&:inspect).join(", ")}"
50
- 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
56
+ safe{->val {
57
+ return val if enum.include?(val)
58
+ raise ArgumentError, "expected %s, got %p" % [expected, val]
59
+ }}
60
+ }
57
61
 
58
62
  end
59
63
  end
@@ -7,10 +7,10 @@ require_relative "config/attr_type_coercion"
7
7
  module Net
8
8
  class IMAP
9
9
 
10
- # Net::IMAP::Config stores configuration options for Net::IMAP clients.
11
- # The global configuration can be seen at either Net::IMAP.config or
12
- # Net::IMAP::Config.global, and the client-specific configuration can be
13
- # seen at Net::IMAP#config.
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
14
  #
15
15
  # When creating a new client, all unhandled keyword arguments to
16
16
  # Net::IMAP.new are delegated to Config.new. Every client has its own
@@ -128,9 +128,26 @@ module Net
128
128
  # The global config object. Also available from Net::IMAP.config.
129
129
  def self.global; @global if defined?(@global) end
130
130
 
131
- # A hash of hard-coded configurations, indexed by version number.
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
 
@@ -172,9 +188,16 @@ module Net
172
188
  include AttrInheritance
173
189
  include AttrTypeCoercion
174
190
 
175
- # The debug mode (boolean)
191
+ # The debug mode (boolean). The default value is +false+.
176
192
  #
177
- # The default value is +false+.
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.
178
201
  attr_accessor :debug, type: :boolean
179
202
 
180
203
  # method: debug?
@@ -184,10 +207,13 @@ module Net
184
207
 
185
208
  # Seconds to wait until a connection is opened.
186
209
  #
210
+ # Applied separately for establishing TCP connection and starting a TLS
211
+ # connection.
212
+ #
187
213
  # If the IMAP object cannot open a connection within this time,
188
214
  # it raises a Net::OpenTimeout exception.
189
215
  #
190
- # See Net::IMAP.new.
216
+ # See Net::IMAP.new and Net::IMAP#starttls.
191
217
  #
192
218
  # The default value is +30+ seconds.
193
219
  attr_accessor :open_timeout, type: Integer
@@ -200,34 +226,156 @@ module Net
200
226
  # The default value is +5+ seconds.
201
227
  attr_accessor :idle_response_timeout, type: Integer
202
228
 
203
- # :markup: markdown
204
- #
205
229
  # Whether to use the +SASL-IR+ extension when the server and \SASL
206
- # mechanism both support it.
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
207
236
  #
208
- # See Net::IMAP#authenticate.
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.
209
240
  #
210
- # | Starting with version | The default value is |
211
- # |-----------------------|------------------------------------------|
212
- # | _original_ | +false+ <em>(extension unsupported)</em> |
213
- # | v0.4 | +true+ <em>(support added)</em> |
241
+ # [+true+ <em>(default since +v0.4+)</em>]
242
+ # Use +SASL-IR+ when it is supported by the server and the mechanism.
214
243
  attr_accessor :sasl_ir, type: :boolean
215
244
 
216
- # :markup: markdown
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
217
340
  #
218
- # Controls the behavior of Net::IMAP#responses when called without a
219
- # block. Valid options are `:warn`, `:raise`, or
220
- # `:silence_deprecation_warning`.
341
+ # [+true+ <em>(original default)</em>]
342
+ # ResponseParser only uses UIDPlusData.
221
343
  #
222
- # | Starting with version | The default value is |
223
- # |-------------------------|--------------------------------|
224
- # | v0.4.13 | +:silence_deprecation_warning+ |
225
- # | v0.5 <em>(planned)</em> | +:warn+ |
226
- # | _eventually_ | +:raise+ |
227
- attr_accessor :responses_without_block, type: [
228
- :silence_deprecation_warning, :warn, :raise,
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
229
353
  ]
230
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
+
231
379
  # Creates a new config object and initialize its attribute with +attrs+.
232
380
  #
233
381
  # If +parent+ is not given, the global config is used by default.
@@ -306,34 +454,71 @@ module Net
306
454
  open_timeout: 30,
307
455
  idle_response_timeout: 5,
308
456
  sasl_ir: true,
457
+ max_response_size: nil,
309
458
  responses_without_block: :silence_deprecation_warning,
459
+ parser_use_deprecated_uidplus_data: true,
460
+ parser_max_deprecated_uidplus_data_size: 1000,
310
461
  ).freeze
311
462
 
312
463
  @global = default.new
313
464
 
314
- version_defaults[0.4] = Config[default.send(:defaults_hash)]
465
+ version_defaults[:default] = Config[default.send(:defaults_hash)]
315
466
 
316
- version_defaults[0] = Config[0.4].dup.update(
467
+ version_defaults[0r] = Config[:default].dup.update(
317
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,
318
472
  ).freeze
319
- version_defaults[0.0] = Config[0]
320
- version_defaults[0.1] = Config[0]
321
- version_defaults[0.2] = Config[0]
322
- 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]
323
477
 
324
- 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
325
485
  responses_without_block: :warn,
486
+ parser_use_deprecated_uidplus_data: :up_to_max_size,
487
+ parser_max_deprecated_uidplus_data_size: 100,
326
488
  ).freeze
327
489
 
328
- version_defaults[:default] = Config[0.4]
329
- version_defaults[:current] = Config[0.4]
330
- version_defaults[:next] = Config[0.5]
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
331
495
 
332
- version_defaults[:future] = Config[0.5].dup.update(
333
- responses_without_block: :raise,
496
+ version_defaults[0.7r] = Config[0.6r].dup.update(
334
497
  ).freeze
335
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
+
336
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
337
522
  end
338
523
  end
339
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
@@ -5,6 +5,9 @@ module Net
5
5
  autoload :FetchData, "#{__dir__}/fetch_data"
6
6
  autoload :SearchResult, "#{__dir__}/search_result"
7
7
  autoload :SequenceSet, "#{__dir__}/sequence_set"
8
+ autoload :UIDPlusData, "#{__dir__}/uidplus_data"
9
+ autoload :AppendUIDData, "#{__dir__}/uidplus_data"
10
+ autoload :CopyUIDData, "#{__dir__}/uidplus_data"
8
11
 
9
12
  # Net::IMAP::ContinuationRequest represents command continuation requests.
10
13
  #
@@ -324,60 +327,6 @@ module Net
324
327
  # code data can take.
325
328
  end
326
329
 
327
- # Net::IMAP::UIDPlusData represents the ResponseCode#data that accompanies
328
- # the +APPENDUID+ and +COPYUID+ response codes.
329
- #
330
- # See [[UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]].
331
- #
332
- # ==== Capability requirement
333
- #
334
- # The +UIDPLUS+ capability[rdoc-ref:Net::IMAP#capability] must be supported.
335
- # A server that supports +UIDPLUS+ should send a UIDPlusData object inside
336
- # every TaggedResponse returned by the append[rdoc-ref:Net::IMAP#append],
337
- # copy[rdoc-ref:Net::IMAP#copy], move[rdoc-ref:Net::IMAP#move], {uid
338
- # copy}[rdoc-ref:Net::IMAP#uid_copy], and {uid
339
- # move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the destination
340
- # mailbox reports +UIDNOTSTICKY+.
341
- #
342
- #--
343
- # TODO: support MULTIAPPEND
344
- #++
345
- #
346
- class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
347
- ##
348
- # method: uidvalidity
349
- # :call-seq: uidvalidity -> nonzero uint32
350
- #
351
- # The UIDVALIDITY of the destination mailbox.
352
-
353
- ##
354
- # method: source_uids
355
- # :call-seq: source_uids -> nil or an array of nonzero uint32
356
- #
357
- # The UIDs of the copied or moved messages.
358
- #
359
- # Note:: Returns +nil+ for Net::IMAP#append.
360
-
361
- ##
362
- # method: assigned_uids
363
- # :call-seq: assigned_uids -> an array of nonzero uint32
364
- #
365
- # The newly assigned UIDs of the copied, moved, or appended messages.
366
- #
367
- # Note:: This always returns an array, even when it contains only one UID.
368
-
369
- ##
370
- # :call-seq: uid_mapping -> nil or a hash
371
- #
372
- # Returns a hash mapping each source UID to the newly assigned destination
373
- # UID.
374
- #
375
- # Note:: Returns +nil+ for Net::IMAP#append.
376
- def uid_mapping
377
- source_uids&.zip(assigned_uids)&.to_h
378
- end
379
- end
380
-
381
330
  # Net::IMAP::MailboxList represents contents of the LIST response,
382
331
  # representing a single mailbox path.
383
332
  #
@@ -13,13 +13,17 @@ module Net
13
13
 
14
14
  attr_reader :config
15
15
 
16
- # :call-seq: Net::IMAP::ResponseParser.new -> Net::IMAP::ResponseParser
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:
@@ -1339,7 +1343,8 @@ module Net
1339
1343
  assert_no_lookahead
1340
1344
  start = @pos
1341
1345
  astring
1342
- @str[start...@pos - 1]
1346
+ end_pos = @token ? @pos - 1 : @pos
1347
+ @str[start...end_pos]
1343
1348
  end
1344
1349
 
1345
1350
  # mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list /
@@ -1862,11 +1867,10 @@ module Net
1862
1867
  #
1863
1868
  # n.b, uniqueid ⊂ uid-set. To avoid inconsistent return types, we always
1864
1869
  # match uid_set even if that returns a single-member array.
1865
- #
1866
1870
  def resp_code_apnd__data
1867
1871
  validity = number; SP!
1868
1872
  dst_uids = uid_set # uniqueid ⊂ uid-set
1869
- UIDPlusData.new(validity, nil, dst_uids)
1873
+ AppendUID(validity, dst_uids)
1870
1874
  end
1871
1875
 
1872
1876
  # already matched: "COPYUID"
@@ -1876,7 +1880,25 @@ module Net
1876
1880
  validity = number; SP!
1877
1881
  src_uids = uid_set; SP!
1878
1882
  dst_uids = uid_set
1879
- UIDPlusData.new(validity, src_uids, dst_uids)
1883
+ CopyUID(validity, src_uids, dst_uids)
1884
+ end
1885
+
1886
+ def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
1887
+ def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end
1888
+
1889
+ # TODO: remove this code in the v0.6.0 release
1890
+ def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids)
1891
+ return unless config.parser_use_deprecated_uidplus_data
1892
+ compact_uid_sets = [src_uids, dst_uids].compact
1893
+ count = compact_uid_sets.map { _1.count_with_duplicates }.max
1894
+ max = config.parser_max_deprecated_uidplus_data_size
1895
+ if count <= max
1896
+ src_uids &&= src_uids.each_ordered_number.to_a
1897
+ dst_uids = dst_uids.each_ordered_number.to_a
1898
+ UIDPlusData.new(validity, src_uids, dst_uids)
1899
+ elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size
1900
+ parse_error("uid-set is too large: %d > %d", count, max)
1901
+ end
1880
1902
  end
1881
1903
 
1882
1904
  ADDRESS_REGEXP = /\G
@@ -2002,15 +2024,9 @@ module Net
2002
2024
  # uniqueid = nz-number
2003
2025
  # ; Strictly ascending
2004
2026
  def uid_set
2005
- token = match(T_NUMBER, T_ATOM)
2006
- case token.symbol
2007
- when T_NUMBER then [Integer(token.value)]
2008
- when T_ATOM
2009
- token.value.split(",").flat_map {|range|
2010
- range = range.split(":").map {|uniqueid| Integer(uniqueid) }
2011
- range.size == 1 ? range : Range.new(range.min, range.max).to_a
2012
- }
2013
- end
2027
+ set = sequence_set
2028
+ parse_error("uid-set cannot contain '*'") if set.include_star?
2029
+ set
2014
2030
  end
2015
2031
 
2016
2032
  def nil_atom