net-imap 0.4.19 → 0.4.20

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: b965efa83b72d27d68fe55ce65f08ffeb696e4a4fb44219e6206a4b7dea4de91
4
+ data.tar.gz: 3554d7a749873b2eccec2470142315afddb4a6dd359a1d2cd6cfeacffa27d3f6
5
5
  SHA512:
6
- metadata.gz: 45bb4a741d80d2097e487b3d1ca31196f97322d6104967c6cad6410d65321e372667a6ff5bbb5bf9aafbdddc7906c7fc05b88c2fff06984dc9efd8a309802ecc
7
- data.tar.gz: '08acdba3fdc323f01bc593ac7fde03ed5e06f1265be821263213fd53714b647e81305713d6829b67adba55dfb541a231e8c0a2303ad83e3efada4088e10c8419'
6
+ metadata.gz: b97c154cfe1b5ce9020029908ec017f316ef36ec36a4948d637d54990ad422580c99bdb2090c4fac4533a503a9678474753d664a101896164ee729d3abaacf37
7
+ data.tar.gz: 518dc7c466a5200b5945f1b21b8e8bcb4523649a3ef5678811385613834f910f5be9a193fd80dee1a8a029882b62da61c265fd3c942a874bd5cdb43c59d5ccef
@@ -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
@@ -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
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ # See https://www.rfc-editor.org/rfc/rfc9051#section-2.2.2
6
+ class ResponseReader # :nodoc:
7
+ attr_reader :client
8
+
9
+ def initialize(client, sock)
10
+ @client, @sock = client, sock
11
+ end
12
+
13
+ def read_response_buffer
14
+ @buff = String.new
15
+ catch :eof do
16
+ while true
17
+ read_line
18
+ break unless (@literal_size = get_literal_size)
19
+ read_literal
20
+ end
21
+ end
22
+ buff
23
+ ensure
24
+ @buff = nil
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :buff, :literal_size
30
+
31
+ def bytes_read; buff.bytesize end
32
+ def empty?; buff.empty? end
33
+ def done?; line_done? && !get_literal_size end
34
+ def line_done?; buff.end_with?(CRLF) end
35
+ def get_literal_size; /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i end
36
+
37
+ def read_line
38
+ buff << (@sock.gets(CRLF, read_limit) or throw :eof)
39
+ max_response_remaining! unless line_done?
40
+ end
41
+
42
+ def read_literal
43
+ # check before allocating memory for literal
44
+ max_response_remaining!
45
+ literal = String.new(capacity: literal_size)
46
+ buff << (@sock.read(read_limit(literal_size), literal) or throw :eof)
47
+ ensure
48
+ @literal_size = nil
49
+ end
50
+
51
+ def read_limit(limit = nil)
52
+ [limit, max_response_remaining!].compact.min
53
+ end
54
+
55
+ def max_response_size; client.max_response_size end
56
+ def max_response_remaining; max_response_size &.- bytes_read end
57
+ def response_too_large?; max_response_size &.< min_response_size end
58
+ def min_response_size; bytes_read + min_response_remaining end
59
+
60
+ def min_response_remaining
61
+ empty? ? 3 : done? ? 0 : (literal_size || 0) + 2
62
+ end
63
+
64
+ def max_response_remaining!
65
+ return max_response_remaining unless response_too_large?
66
+ raise ResponseTooLargeError.new(
67
+ max_response_size: max_response_size,
68
+ bytes_read: bytes_read,
69
+ literal_size: literal_size,
70
+ )
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -178,7 +178,7 @@ module Net
178
178
  #
179
179
  # <i>Set membership:</i>
180
180
  # - #include? (aliased as #member?):
181
- # Returns whether a given object (nz-number, range, or <tt>*</tt>) is
181
+ # Returns whether a given element (nz-number, range, or <tt>*</tt>) is
182
182
  # contained by the set.
183
183
  # - #include_star?: Returns whether the set contains <tt>*</tt>.
184
184
  #
@@ -243,13 +243,13 @@ module Net
243
243
  # These methods do not modify +self+.
244
244
  #
245
245
  # - #| (aliased as #union and #+): Returns a new set combining all members
246
- # from +self+ with all members from the other object.
246
+ # from +self+ with all members from the other set.
247
247
  # - #& (aliased as #intersection): Returns a new set containing all members
248
- # common to +self+ and the other object.
248
+ # common to +self+ and the other set.
249
249
  # - #- (aliased as #difference): Returns a copy of +self+ with all members
250
- # in the other object removed.
250
+ # in the other set removed.
251
251
  # - #^ (aliased as #xor): Returns a new set containing all members from
252
- # +self+ and the other object except those common to both.
252
+ # +self+ and the other set except those common to both.
253
253
  # - #~ (aliased as #complement): Returns a new set containing all members
254
254
  # that are not in +self+
255
255
  # - #limit: Returns a copy of +self+ which has replaced <tt>*</tt> with a
@@ -262,17 +262,17 @@ module Net
262
262
  #
263
263
  # These methods always update #string to be fully sorted and coalesced.
264
264
  #
265
- # - #add (aliased as #<<): Adds a given object to the set; returns +self+.
266
- # - #add?: If the given object is not an element in the set, adds it and
265
+ # - #add (aliased as #<<): Adds a given element to the set; returns +self+.
266
+ # - #add?: If the given element is not fully included the set, adds it and
267
267
  # returns +self+; otherwise, returns +nil+.
268
- # - #merge: Merges multiple elements into the set; returns +self+.
268
+ # - #merge: Adds all members of the given sets into this set; returns +self+.
269
269
  # - #complement!: Replaces the contents of the set with its own #complement.
270
270
  #
271
271
  # <i>Order preserving:</i>
272
272
  #
273
273
  # These methods _may_ cause #string to not be sorted or coalesced.
274
274
  #
275
- # - #append: Adds a given object to the set, appending it to the existing
275
+ # - #append: Adds the given entry to the set, appending it to the existing
276
276
  # string, and returns +self+.
277
277
  # - #string=: Assigns a new #string value and replaces #elements to match.
278
278
  # - #replace: Replaces the contents of the set with the contents
@@ -283,13 +283,14 @@ module Net
283
283
  # sorted and coalesced.
284
284
  #
285
285
  # - #clear: Removes all elements in the set; returns +self+.
286
- # - #delete: Removes a given object from the set; returns +self+.
287
- # - #delete?: If the given object is an element in the set, removes it and
286
+ # - #delete: Removes a given element from the set; returns +self+.
287
+ # - #delete?: If the given element is included in the set, removes it and
288
288
  # returns it; otherwise, returns +nil+.
289
289
  # - #delete_at: Removes the number at a given offset.
290
290
  # - #slice!: Removes the number or consecutive numbers at a given offset or
291
291
  # range of offsets.
292
- # - #subtract: Removes each given object from the set; returns +self+.
292
+ # - #subtract: Removes all members of the given sets from this set; returns
293
+ # +self+.
293
294
  # - #limit!: Replaces <tt>*</tt> with a given maximum value and removes all
294
295
  # members over that maximum; returns +self+.
295
296
  #
@@ -326,9 +327,12 @@ module Net
326
327
  class << self
327
328
 
328
329
  # :call-seq:
329
- # SequenceSet[*values] -> valid frozen sequence set
330
+ # SequenceSet[*inputs] -> valid frozen sequence set
330
331
  #
331
- # Returns a frozen SequenceSet, constructed from +values+.
332
+ # Returns a frozen SequenceSet, constructed from +inputs+.
333
+ #
334
+ # When only a single valid frozen SequenceSet is given, that same set is
335
+ # returned.
332
336
  #
333
337
  # An empty SequenceSet is invalid and will raise a DataFormatError.
334
338
  #
@@ -694,7 +698,7 @@ module Net
694
698
  alias complement :~
695
699
 
696
700
  # :call-seq:
697
- # add(object) -> self
701
+ # add(element) -> self
698
702
  # self << other -> self
699
703
  #
700
704
  # Adds a range or number to the set and returns +self+.
@@ -702,8 +706,8 @@ module Net
702
706
  # #string will be regenerated. Use #merge to add many elements at once.
703
707
  #
704
708
  # Related: #add?, #merge, #union
705
- def add(object)
706
- tuple_add input_to_tuple object
709
+ def add(element)
710
+ tuple_add input_to_tuple element
707
711
  normalize!
708
712
  end
709
713
  alias << add
@@ -712,9 +716,9 @@ module Net
712
716
  #
713
717
  # Unlike #add, #merge, or #union, the new value is appended to #string.
714
718
  # This may result in a #string which has duplicates or is out-of-order.
715
- def append(object)
719
+ def append(entry)
716
720
  modifying!
717
- tuple = input_to_tuple object
721
+ tuple = input_to_tuple entry
718
722
  entry = tuple_to_str tuple
719
723
  string unless empty? # write @string before tuple_add
720
724
  tuple_add tuple
@@ -722,19 +726,19 @@ module Net
722
726
  self
723
727
  end
724
728
 
725
- # :call-seq: add?(object) -> self or nil
729
+ # :call-seq: add?(element) -> self or nil
726
730
  #
727
731
  # Adds a range or number to the set and returns +self+. Returns +nil+
728
- # when the object is already included in the set.
732
+ # when the element is already included in the set.
729
733
  #
730
734
  # #string will be regenerated. Use #merge to add many elements at once.
731
735
  #
732
736
  # Related: #add, #merge, #union, #include?
733
- def add?(object)
734
- add object unless include? object
737
+ def add?(element)
738
+ add element unless include? element
735
739
  end
736
740
 
737
- # :call-seq: delete(object) -> self
741
+ # :call-seq: delete(element) -> self
738
742
  #
739
743
  # Deletes the given range or number from the set and returns +self+.
740
744
  #
@@ -742,8 +746,8 @@ module Net
742
746
  # many elements at once.
743
747
  #
744
748
  # Related: #delete?, #delete_at, #subtract, #difference
745
- def delete(object)
746
- tuple_subtract input_to_tuple object
749
+ def delete(element)
750
+ tuple_subtract input_to_tuple element
747
751
  normalize!
748
752
  end
749
753
 
@@ -779,8 +783,8 @@ module Net
779
783
  # #string will be regenerated after deletion.
780
784
  #
781
785
  # Related: #delete, #delete_at, #subtract, #difference, #disjoint?
782
- def delete?(object)
783
- tuple = input_to_tuple object
786
+ def delete?(element)
787
+ tuple = input_to_tuple element
784
788
  if tuple.first == tuple.last
785
789
  return unless include_tuple? tuple
786
790
  tuple_subtract tuple
@@ -824,33 +828,31 @@ module Net
824
828
  deleted
825
829
  end
826
830
 
827
- # Merges all of the elements that appear in any of the +inputs+ into the
831
+ # Merges all of the elements that appear in any of the +sets+ into the
828
832
  # set, and returns +self+.
829
833
  #
830
- # The +inputs+ may be any objects that would be accepted by ::new:
831
- # non-zero 32 bit unsigned integers, ranges, <tt>sequence-set</tt>
832
- # formatted strings, other sequence sets, or enumerables containing any of
833
- # these.
834
+ # The +sets+ may be any objects that would be accepted by ::new: non-zero
835
+ # 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
836
+ # strings, other sequence sets, or enumerables containing any of these.
834
837
  #
835
- # #string will be regenerated after all inputs have been merged.
838
+ # #string will be regenerated after all sets have been merged.
836
839
  #
837
840
  # Related: #add, #add?, #union
838
- def merge(*inputs)
839
- tuples_add input_to_tuples inputs
841
+ def merge(*sets)
842
+ tuples_add input_to_tuples sets
840
843
  normalize!
841
844
  end
842
845
 
843
- # Removes all of the elements that appear in any of the given +objects+
844
- # from the set, and returns +self+.
846
+ # Removes all of the elements that appear in any of the given +sets+ from
847
+ # the set, and returns +self+.
845
848
  #
846
- # The +objects+ may be any objects that would be accepted by ::new:
847
- # non-zero 32 bit unsigned integers, ranges, <tt>sequence-set</tt>
848
- # formatted strings, other sequence sets, or enumerables containing any of
849
- # these.
849
+ # The +sets+ may be any objects that would be accepted by ::new: non-zero
850
+ # 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
851
+ # strings, other sequence sets, or enumerables containing any of these.
850
852
  #
851
853
  # Related: #difference
852
- def subtract(*objects)
853
- tuples_subtract input_to_tuples objects
854
+ def subtract(*sets)
855
+ tuples_subtract input_to_tuples sets
854
856
  normalize!
855
857
  end
856
858
 
@@ -1390,29 +1392,29 @@ module Net
1390
1392
  super
1391
1393
  end
1392
1394
 
1393
- def input_to_tuple(obj)
1394
- obj = input_try_convert obj
1395
- case obj
1396
- when *STARS, Integer then [int = to_tuple_int(obj), int]
1397
- when Range then range_to_tuple(obj)
1398
- when String then str_to_tuple(obj)
1395
+ def input_to_tuple(entry)
1396
+ entry = input_try_convert entry
1397
+ case entry
1398
+ when *STARS, Integer then [int = to_tuple_int(entry), int]
1399
+ when Range then range_to_tuple(entry)
1400
+ when String then str_to_tuple(entry)
1399
1401
  else
1400
- raise DataFormatError, "expected number or range, got %p" % [obj]
1402
+ raise DataFormatError, "expected number or range, got %p" % [entry]
1401
1403
  end
1402
1404
  end
1403
1405
 
1404
- def input_to_tuples(obj)
1405
- obj = input_try_convert obj
1406
- case obj
1407
- when *STARS, Integer, Range then [input_to_tuple(obj)]
1408
- when String then str_to_tuples obj
1409
- when SequenceSet then obj.tuples
1410
- when ENUMABLE then obj.flat_map { input_to_tuples _1 }
1406
+ def input_to_tuples(set)
1407
+ set = input_try_convert set
1408
+ case set
1409
+ when *STARS, Integer, Range then [input_to_tuple(set)]
1410
+ when String then str_to_tuples set
1411
+ when SequenceSet then set.tuples
1412
+ when ENUMABLE then set.flat_map { input_to_tuples _1 }
1411
1413
  when nil then []
1412
1414
  else
1413
1415
  raise DataFormatError,
1414
1416
  "expected nz-number, range, string, or enumerable; " \
1415
- "got %p" % [obj]
1417
+ "got %p" % [set]
1416
1418
  end
1417
1419
  end
1418
1420
 
data/lib/net/imap.rb CHANGED
@@ -43,10 +43,16 @@ module Net
43
43
  # To work on the messages within a mailbox, the client must
44
44
  # first select that mailbox, using either #select or #examine
45
45
  # (for read-only access). Once the client has successfully
46
- # selected a mailbox, they enter the "_selected_" state, and that
46
+ # selected a mailbox, they enter the +selected+ state, and that
47
47
  # mailbox becomes the _current_ mailbox, on which mail-item
48
48
  # related commands implicitly operate.
49
49
  #
50
+ # === Connection state
51
+ #
52
+ # Once an IMAP connection is established, the connection is in one of four
53
+ # states: <tt>not authenticated</tt>, +authenticated+, +selected+, and
54
+ # +logout+. Most commands are valid only in certain states.
55
+ #
50
56
  # === Sequence numbers and UIDs
51
57
  #
52
58
  # Messages have two sorts of identifiers: message sequence
@@ -199,6 +205,42 @@ module Net
199
205
  #
200
206
  # This script invokes the FETCH command and the SEARCH command concurrently.
201
207
  #
208
+ # When running multiple commands, care must be taken to avoid ambiguity. For
209
+ # example, SEARCH responses are ambiguous about which command they are
210
+ # responding to, so search commands should not run simultaneously, unless the
211
+ # server supports +ESEARCH+ {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731] or
212
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]. See {RFC9051
213
+ # §5.5}[https://www.rfc-editor.org/rfc/rfc9051.html#section-5.5] for
214
+ # other examples of command sequences which should not be pipelined.
215
+ #
216
+ # == Unbounded memory use
217
+ #
218
+ # Net::IMAP reads server responses in a separate receiver thread per client.
219
+ # Unhandled response data is saved to #responses, and response_handlers run
220
+ # inside the receiver thread. See the list of methods for {handling server
221
+ # responses}[rdoc-ref:Net::IMAP@Handling+server+responses], below.
222
+ #
223
+ # Because the receiver thread continuously reads and saves new responses, some
224
+ # scenarios must be careful to avoid unbounded memory use:
225
+ #
226
+ # * Commands such as #list or #fetch can have an enormous number of responses.
227
+ # * Commands such as #fetch can result in an enormous size per response.
228
+ # * Long-lived connections will gradually accumulate unsolicited server
229
+ # responses, especially +EXISTS+, +FETCH+, and +EXPUNGE+ responses.
230
+ # * A buggy or untrusted server could send inappropriate responses, which
231
+ # could be very numerous, very large, and very rapid.
232
+ #
233
+ # Use paginated or limited versions of commands whenever possible.
234
+ #
235
+ # Use Config#max_response_size to impose a limit on incoming server responses
236
+ # as they are being read. <em>This is especially important for untrusted
237
+ # servers.</em>
238
+ #
239
+ # Use #add_response_handler to handle responses after each one is received.
240
+ # Use the +response_handlers+ argument to ::new to assign response handlers
241
+ # before the receiver thread is started. Use #extract_responses,
242
+ # #clear_responses, or #responses (with a block) to prune responses.
243
+ #
202
244
  # == Errors
203
245
  #
204
246
  # An \IMAP server can send three different types of responses to indicate
@@ -260,8 +302,9 @@ module Net
260
302
  #
261
303
  # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
262
304
  # waits for a successful server greeting before the method returns.
305
+ # - #connection_state: Returns the connection state.
263
306
  # - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
264
- # - #logout: Tells the server to end the session. Enters the "_logout_" state.
307
+ # - #logout: Tells the server to end the session. Enters the +logout+ state.
265
308
  # - #disconnect: Disconnects the connection (without sending #logout first).
266
309
  # - #disconnected?: True if the connection has been closed.
267
310
  #
@@ -317,37 +360,36 @@ module Net
317
360
  # <em>In general, #capable? should be used rather than explicitly sending a
318
361
  # +CAPABILITY+ command to the server.</em>
319
362
  # - #noop: Allows the server to send unsolicited untagged #responses.
320
- # - #logout: Tells the server to end the session. Enters the "_logout_" state.
363
+ # - #logout: Tells the server to end the session. Enters the +logout+ state.
321
364
  #
322
365
  # ==== Not Authenticated state
323
366
  #
324
367
  # In addition to the commands for any state, the following commands are valid
325
- # in the "<em>not authenticated</em>" state:
368
+ # in the +not_authenticated+ state:
326
369
  #
327
370
  # - #starttls: Upgrades a clear-text connection to use TLS.
328
371
  #
329
372
  # <em>Requires the +STARTTLS+ capability.</em>
330
373
  # - #authenticate: Identifies the client to the server using the given
331
374
  # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
332
- # and credentials. Enters the "_authenticated_" state.
375
+ # and credentials. Enters the +authenticated+ state.
333
376
  #
334
377
  # <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
335
378
  # supported mechanisms.</em>
336
379
  # - #login: Identifies the client to the server using a plain text password.
337
- # Using #authenticate is generally preferred. Enters the "_authenticated_"
338
- # state.
380
+ # Using #authenticate is preferred. Enters the +authenticated+ state.
339
381
  #
340
382
  # <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
341
383
  #
342
384
  # ==== Authenticated state
343
385
  #
344
386
  # In addition to the commands for any state, the following commands are valid
345
- # in the "_authenticated_" state:
387
+ # in the +authenticated+ state:
346
388
  #
347
389
  # - #enable: Enables backwards incompatible server extensions.
348
390
  # <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
349
- # - #select: Open a mailbox and enter the "_selected_" state.
350
- # - #examine: Open a mailbox read-only, and enter the "_selected_" state.
391
+ # - #select: Open a mailbox and enter the +selected+ state.
392
+ # - #examine: Open a mailbox read-only, and enter the +selected+ state.
351
393
  # - #create: Creates a new mailbox.
352
394
  # - #delete: Permanently remove a mailbox.
353
395
  # - #rename: Change the name of a mailbox.
@@ -369,12 +411,12 @@ module Net
369
411
  #
370
412
  # ==== Selected state
371
413
  #
372
- # In addition to the commands for any state and the "_authenticated_"
373
- # commands, the following commands are valid in the "_selected_" state:
414
+ # In addition to the commands for any state and the +authenticated+
415
+ # commands, the following commands are valid in the +selected+ state:
374
416
  #
375
- # - #close: Closes the mailbox and returns to the "_authenticated_" state,
417
+ # - #close: Closes the mailbox and returns to the +authenticated+ state,
376
418
  # expunging deleted messages, unless the mailbox was opened as read-only.
377
- # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
419
+ # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
378
420
  # without expunging any messages.
379
421
  # <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
380
422
  # - #expunge: Permanently removes messages which have the Deleted flag set.
@@ -395,7 +437,7 @@ module Net
395
437
  #
396
438
  # ==== Logout state
397
439
  #
398
- # No \IMAP commands are valid in the "_logout_" state. If the socket is still
440
+ # No \IMAP commands are valid in the +logout+ state. If the socket is still
399
441
  # open, Net::IMAP will close it after receiving server confirmation.
400
442
  # Exceptions will be raised by \IMAP commands that have already started and
401
443
  # are waiting for a response, as well as any that are called after logout.
@@ -449,7 +491,7 @@ module Net
449
491
  # ==== RFC3691: +UNSELECT+
450
492
  # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
451
493
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
452
- # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
494
+ # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
453
495
  # without expunging any messages.
454
496
  #
455
497
  # ==== RFC4314: +ACL+
@@ -719,7 +761,7 @@ module Net
719
761
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
720
762
  #
721
763
  class IMAP < Protocol
722
- VERSION = "0.4.19"
764
+ VERSION = "0.4.20"
723
765
 
724
766
  # Aliases for supported capabilities, to be used with the #enable command.
725
767
  ENABLE_ALIASES = {
@@ -727,6 +769,7 @@ module Net
727
769
  "UTF8=ONLY" => "UTF8=ACCEPT",
728
770
  }.freeze
729
771
 
772
+ autoload :ResponseReader, File.expand_path("imap/response_reader", __dir__)
730
773
  autoload :SASL, File.expand_path("imap/sasl", __dir__)
731
774
  autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
732
775
  autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
@@ -741,9 +784,11 @@ module Net
741
784
  def self.config; Config.global end
742
785
 
743
786
  # Returns the global debug mode.
787
+ # Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
744
788
  def self.debug; config.debug end
745
789
 
746
790
  # Sets the global debug mode.
791
+ # Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
747
792
  def self.debug=(val)
748
793
  config.debug = val
749
794
  end
@@ -764,7 +809,7 @@ module Net
764
809
  alias default_ssl_port default_tls_port
765
810
  end
766
811
 
767
- # Returns the initial greeting the server, an UntaggedResponse.
812
+ # Returns the initial greeting sent by the server, an UntaggedResponse.
768
813
  attr_reader :greeting
769
814
 
770
815
  # The client configuration. See Net::IMAP::Config.
@@ -773,13 +818,28 @@ module Net
773
818
  # Net::IMAP.config.
774
819
  attr_reader :config
775
820
 
776
- # Seconds to wait until a connection is opened.
777
- # If the IMAP object cannot open a connection within this time,
778
- # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
779
- def open_timeout; config.open_timeout end
821
+ ##
822
+ # :attr_reader: open_timeout
823
+ # Seconds to wait until a connection is opened. Also used by #starttls.
824
+ # Delegates to {config.open_timeout}[rdoc-ref:Config#open_timeout].
780
825
 
826
+ ##
827
+ # :attr_reader: idle_response_timeout
781
828
  # Seconds to wait until an IDLE response is received.
782
- def idle_response_timeout; config.idle_response_timeout end
829
+ # Delegates to {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout].
830
+
831
+ ##
832
+ # :attr_accessor: max_response_size
833
+ #
834
+ # The maximum allowed server response size, in bytes.
835
+ # Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size].
836
+
837
+ # :stopdoc:
838
+ def open_timeout; config.open_timeout end
839
+ def idle_response_timeout; config.idle_response_timeout end
840
+ def max_response_size; config.max_response_size end
841
+ def max_response_size=(val) config.max_response_size = val end
842
+ # :startdoc:
783
843
 
784
844
  # The hostname this client connected to
785
845
  attr_reader :host
@@ -835,6 +895,12 @@ module Net
835
895
  #
836
896
  # See DeprecatedClientOptions.new for deprecated SSL arguments.
837
897
  #
898
+ # [response_handlers]
899
+ # A list of response handlers to be added before the receiver thread is
900
+ # started. This ensures every server response is handled, including the
901
+ # #greeting. Note that the greeting is handled in the current thread, but
902
+ # all other responses are handled in the receiver thread.
903
+ #
838
904
  # [config]
839
905
  # A Net::IMAP::Config object to use as the basis for #config. By default,
840
906
  # the global Net::IMAP.config is used.
@@ -906,7 +972,7 @@ module Net
906
972
  # [Net::IMAP::ByeResponseError]
907
973
  # Connected to the host successfully, but it immediately said goodbye.
908
974
  #
909
- def initialize(host, port: nil, ssl: nil,
975
+ def initialize(host, port: nil, ssl: nil, response_handlers: nil,
910
976
  config: Config.global, **config_options)
911
977
  super()
912
978
  # Config options
@@ -929,6 +995,7 @@ module Net
929
995
  @receiver_thread = nil
930
996
  @receiver_thread_exception = nil
931
997
  @receiver_thread_terminating = false
998
+ response_handlers&.each do add_response_handler(_1) end
932
999
 
933
1000
  # Client Protocol Sender (including state for currently running commands)
934
1001
  @tag_prefix = "RUBY"
@@ -944,6 +1011,7 @@ module Net
944
1011
  # Connection
945
1012
  @tls_verified = false
946
1013
  @sock = tcp_socket(@host, @port)
1014
+ @reader = ResponseReader.new(self, @sock)
947
1015
  start_tls_session if ssl_ctx
948
1016
  start_imap_connection
949
1017
 
@@ -1204,6 +1272,10 @@ module Net
1204
1272
  # both successful. Any error indicates that the connection has not been
1205
1273
  # secured.
1206
1274
  #
1275
+ # After the server agrees to start a TLS connection, this method waits up to
1276
+ # {config.open_timeout}[rdoc-ref:Config#open_timeout] before raising
1277
+ # +Net::OpenTimeout+.
1278
+ #
1207
1279
  # *Note:*
1208
1280
  # >>>
1209
1281
  # Any #response_handlers added before STARTTLS should be aware that the
@@ -2706,6 +2778,10 @@ module Net
2706
2778
  # end
2707
2779
  # }
2708
2780
  #
2781
+ # Response handlers can also be added when the client is created before the
2782
+ # receiver thread is started, by the +response_handlers+ argument to ::new.
2783
+ # This ensures every server response is handled, including the #greeting.
2784
+ #
2709
2785
  # Related: #remove_response_handler, #response_handlers
2710
2786
  def add_response_handler(handler = nil, &block)
2711
2787
  raise ArgumentError, "two Procs are passed" if handler && block
@@ -2732,6 +2808,7 @@ module Net
2732
2808
  def start_imap_connection
2733
2809
  @greeting = get_server_greeting
2734
2810
  @capabilities = capabilities_from_resp_code @greeting
2811
+ @response_handlers.each do |handler| handler.call(@greeting) end
2735
2812
  @receiver_thread = start_receiver_thread
2736
2813
  rescue Exception
2737
2814
  @sock.close
@@ -2860,23 +2937,10 @@ module Net
2860
2937
  end
2861
2938
 
2862
2939
  def get_response
2863
- buff = String.new
2864
- while true
2865
- s = @sock.gets(CRLF)
2866
- break unless s
2867
- buff.concat(s)
2868
- if /\{(\d+)\}\r\n/n =~ s
2869
- s = @sock.read($1.to_i)
2870
- buff.concat(s)
2871
- else
2872
- break
2873
- end
2874
- end
2940
+ buff = @reader.read_response_buffer
2875
2941
  return nil if buff.length == 0
2876
- if config.debug?
2877
- $stderr.print(buff.gsub(/^/n, "S: "))
2878
- end
2879
- return @parser.parse(buff)
2942
+ $stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
2943
+ @parser.parse(buff)
2880
2944
  end
2881
2945
 
2882
2946
  #############################
@@ -3077,6 +3141,7 @@ module Net
3077
3141
  raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
3078
3142
  raise "cannot start TLS without SSLContext" unless ssl_ctx
3079
3143
  @sock = SSLSocket.new(@sock, ssl_ctx)
3144
+ @reader = ResponseReader.new(self, @sock)
3080
3145
  @sock.sync_close = true
3081
3146
  @sock.hostname = @host if @sock.respond_to? :hostname=
3082
3147
  ssl_socket_connect(@sock, open_timeout)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-imap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.19
4
+ version: 0.4.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
8
8
  - nicholas a. evans
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-07 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-protocol
@@ -68,6 +68,7 @@ files:
68
68
  - lib/net/imap/response_data.rb
69
69
  - lib/net/imap/response_parser.rb
70
70
  - lib/net/imap/response_parser/parser_utils.rb
71
+ - lib/net/imap/response_reader.rb
71
72
  - lib/net/imap/sasl.rb
72
73
  - lib/net/imap/sasl/anonymous_authenticator.rb
73
74
  - lib/net/imap/sasl/authentication_exchange.rb
@@ -124,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
125
  - !ruby/object:Gem::Version
125
126
  version: '0'
126
127
  requirements: []
127
- rubygems_version: 3.6.2
128
+ rubygems_version: 3.6.7
128
129
  specification_version: 4
129
130
  summary: Ruby client api for Internet Message Access Protocol
130
131
  test_files: []