net-imap 0.5.6 → 0.5.8

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: cdbdda0ed73da899ec338f66022a16104562d3701c568b0a6d4897270a608ac5
4
- data.tar.gz: b6a7ec70776b32f8eb57d01a0869503eb5d76f719ac091eb92e0206608e936e9
3
+ metadata.gz: b6d2d79f907ce00d056ead396fafe73bbdf84fc54b01e1f2536d6490c001c723
4
+ data.tar.gz: 6fba3cd04144fedd7b178959451631ea14cff8f248ca963961b3945b636cec4b
5
5
  SHA512:
6
- metadata.gz: 381bf2428719ed8decb5d241fda0e19f28031dd4a77980b3717bb29c37bed1c927f00e5b57862e209ecf24b2e9b38c01088d6e1a90fc4b4cc026cdd9e6611100
7
- data.tar.gz: 513c6a77d46b6d2cf67aea4511023acc76c69940e3b1a0d0eae7223b53ff63bc8e6e009f51fef826b09f76f6ad1d92e84243e8457f59d3912db7e74bf69d3d1b
6
+ metadata.gz: 80f15f967d4260638d423b802204360e25704cc47c59d110a85f401c1554882521097d5014ecc3e7a0a17f87e8995ae87f553d73a26f3c8abc84744ad5d236ed
7
+ data.tar.gz: 2790cbc5ee60b3da3648e8bd91df90ecac7c72e0febca11464400460fd5c34db3123d307f39901ea7c313942f2feb11bb3c0f7dbc06b96094b6d1738426b278a
@@ -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
21
23
  end
22
24
  private_constant :Macros
23
25
 
@@ -26,34 +28,33 @@ module Net
26
28
  end
27
29
  private_class_method :included
28
30
 
29
- def self.attr_accessor(attr, type: nil)
30
- return unless type
31
- if :boolean == type then boolean attr
32
- elsif Integer == type then integer attr
33
- elsif Array === type then enum attr, type
34
- else raise ArgumentError, "unknown type coercion %p" % [type]
35
- end
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
41
- end
38
+ Types = Hash.new do |h, type| type => Proc | nil; safe{type} end
39
+ Types[:boolean] = Boolean = safe{-> {!!_1}}
40
+ Types[Integer] = safe{->{Integer(_1)}}
42
41
 
43
- def self.integer(attr)
44
- define_method :"#{attr}=" do |val| super Integer val end
42
+ def self.attr_accessor(attr, type: nil)
43
+ type = Types[type] or return
44
+ define_method :"#{attr}=" do |val| super type[val] end
45
+ define_method :"#{attr}?" do send attr end if type == Boolean
45
46
  end
46
47
 
47
- def self.enum(attr, enum)
48
- enum = enum.dup.freeze
48
+ NilOrInteger = safe{->val { Integer val unless val.nil? }}
49
+
50
+ Enum = ->(*enum) {
51
+ enum = safe{enum}
49
52
  expected = -"one of #{enum.map(&:inspect).join(", ")}"
50
- define_method :"#{attr}=" do |val|
51
- unless enum.include?(val)
52
- raise ArgumentError, "expected %s, got %p" % [expected, val]
53
- end
54
- super val
55
- end
56
- end
53
+ safe{->val {
54
+ return val if enum.include?(val)
55
+ raise ArgumentError, "expected %s, got %p" % [expected, val]
56
+ }}
57
+ }
57
58
 
58
59
  end
59
60
  end
@@ -131,8 +131,25 @@ module Net
131
131
  def self.global; @global if defined?(@global) end
132
132
 
133
133
  # A hash of hard-coded configurations, indexed by version number or name.
134
+ # Values can be accessed with any object that responds to +to_sym+ or
135
+ # +to_r+/+to_f+ with a non-zero number.
136
+ #
137
+ # Config::[] gets named or numbered versions from this hash.
138
+ #
139
+ # For example:
140
+ # Net::IMAP::Config.version_defaults[0.5] == Net::IMAP::Config[0.5]
141
+ # Net::IMAP::Config[0.5] == Net::IMAP::Config[0.5r] # => true
142
+ # Net::IMAP::Config["current"] == Net::IMAP::Config[:current] # => true
143
+ # Net::IMAP::Config["0.5.6"] == Net::IMAP::Config[0.5r] # => true
134
144
  def self.version_defaults; @version_defaults end
135
- @version_defaults = {}
145
+ @version_defaults = Hash.new {|h, k|
146
+ # NOTE: String responds to both so the order is significant.
147
+ # And ignore non-numeric conversion to zero, because: "wat!?".to_r == 0
148
+ (h.fetch(k.to_r, nil) || h.fetch(k.to_f, nil) if k.is_a?(Numeric)) ||
149
+ (h.fetch(k.to_sym, nil) if k.respond_to?(:to_sym)) ||
150
+ (h.fetch(k.to_r, nil) if k.respond_to?(:to_r) && k.to_r != 0r) ||
151
+ (h.fetch(k.to_f, nil) if k.respond_to?(:to_f) && k.to_f != 0.0)
152
+ }
136
153
 
137
154
  # :call-seq:
138
155
  # Net::IMAP::Config[number] -> versioned config
@@ -155,18 +172,17 @@ module Net
155
172
  elsif config.nil? && global.nil? then nil
156
173
  elsif config.respond_to?(:to_hash) then new(global, **config).freeze
157
174
  else
158
- version_defaults.fetch(config) do
175
+ version_defaults[config] or
159
176
  case config
160
177
  when Numeric
161
178
  raise RangeError, "unknown config version: %p" % [config]
162
- when Symbol
179
+ when String, Symbol
163
180
  raise KeyError, "unknown config name: %p" % [config]
164
181
  else
165
182
  raise TypeError, "no implicit conversion of %s to %s" % [
166
183
  config.class, Config
167
184
  ]
168
185
  end
169
- end
170
186
  end
171
187
  end
172
188
 
@@ -193,10 +209,13 @@ module Net
193
209
 
194
210
  # Seconds to wait until a connection is opened.
195
211
  #
212
+ # Applied separately for establishing TCP connection and starting a TLS
213
+ # connection.
214
+ #
196
215
  # If the IMAP object cannot open a connection within this time,
197
216
  # it raises a Net::OpenTimeout exception.
198
217
  #
199
- # See Net::IMAP.new.
218
+ # See Net::IMAP.new and Net::IMAP#starttls.
200
219
  #
201
220
  # The default value is +30+ seconds.
202
221
  attr_accessor :open_timeout, type: Integer
@@ -245,10 +264,44 @@ module Net
245
264
  # present. When capabilities are unknown, Net::IMAP will automatically
246
265
  # send a +CAPABILITY+ command first before sending +LOGIN+.
247
266
  #
248
- attr_accessor :enforce_logindisabled, type: [
267
+ attr_accessor :enforce_logindisabled, type: Enum[
249
268
  false, :when_capabilities_cached, true
250
269
  ]
251
270
 
271
+ # The maximum allowed server response size. When +nil+, there is no limit
272
+ # on response size.
273
+ #
274
+ # The default value (512 MiB, since +v0.5.7+) is <em>very high</em> and
275
+ # unlikely to be reached. A _much_ lower value should be used with
276
+ # untrusted servers (for example, when connecting to a user-provided
277
+ # hostname). When using a lower limit, message bodies should be fetched
278
+ # in chunks rather than all at once.
279
+ #
280
+ # <em>Please Note:</em> this only limits the size per response. It does
281
+ # not prevent a flood of individual responses and it does not limit how
282
+ # many unhandled responses may be stored on the responses hash. See
283
+ # Net::IMAP@Unbounded+memory+use.
284
+ #
285
+ # Socket reads are limited to the maximum remaining bytes for the current
286
+ # response: max_response_size minus the bytes that have already been read.
287
+ # When the limit is reached, or reading a +literal+ _would_ go over the
288
+ # limit, ResponseTooLargeError is raised and the connection is closed.
289
+ #
290
+ # Note that changes will not take effect immediately, because the receiver
291
+ # thread may already be waiting for the next response using the previous
292
+ # value. Net::IMAP#noop can force a response and enforce the new setting
293
+ # immediately.
294
+ #
295
+ # ==== Versioned Defaults
296
+ #
297
+ # Net::IMAP#max_response_size <em>was added in +v0.2.5+ and +v0.3.9+ as an
298
+ # attr_accessor, and in +v0.4.20+ and +v0.5.7+ as a delegator to this
299
+ # config attribute.</em>
300
+ #
301
+ # * original: +nil+ <em>(no limit)</em>
302
+ # * +0.5+: 512 MiB
303
+ attr_accessor :max_response_size, type: Integer?
304
+
252
305
  # Controls the behavior of Net::IMAP#responses when called without any
253
306
  # arguments (+type+ or +block+).
254
307
  #
@@ -275,7 +328,7 @@ module Net
275
328
  # Raise an ArgumentError with the deprecation warning.
276
329
  #
277
330
  # Note: #responses_without_args is an alias for #responses_without_block.
278
- attr_accessor :responses_without_block, type: [
331
+ attr_accessor :responses_without_block, type: Enum[
279
332
  :silence_deprecation_warning, :warn, :frozen_dup, :raise,
280
333
  ]
281
334
 
@@ -320,7 +373,7 @@ module Net
320
373
  #
321
374
  # [+false+ <em>(planned default for +v0.6+)</em>]
322
375
  # ResponseParser _only_ uses AppendUIDData and CopyUIDData.
323
- attr_accessor :parser_use_deprecated_uidplus_data, type: [
376
+ attr_accessor :parser_use_deprecated_uidplus_data, type: Enum[
324
377
  true, :up_to_max_size, false
325
378
  ]
326
379
 
@@ -427,6 +480,7 @@ module Net
427
480
  idle_response_timeout: 5,
428
481
  sasl_ir: true,
429
482
  enforce_logindisabled: true,
483
+ max_response_size: 512 << 20, # 512 MiB
430
484
  responses_without_block: :warn,
431
485
  parser_use_deprecated_uidplus_data: :up_to_max_size,
432
486
  parser_max_deprecated_uidplus_data_size: 100,
@@ -435,36 +489,64 @@ module Net
435
489
  @global = default.new
436
490
 
437
491
  version_defaults[:default] = Config[default.send(:defaults_hash)]
438
- version_defaults[:current] = Config[:default]
439
492
 
440
- version_defaults[0] = Config[:current].dup.update(
493
+ version_defaults[0r] = Config[:default].dup.update(
441
494
  sasl_ir: false,
442
495
  responses_without_block: :silence_deprecation_warning,
443
496
  enforce_logindisabled: false,
497
+ max_response_size: nil,
444
498
  parser_use_deprecated_uidplus_data: true,
445
499
  parser_max_deprecated_uidplus_data_size: 10_000,
446
500
  ).freeze
447
- version_defaults[0.0] = Config[0]
448
- version_defaults[0.1] = Config[0]
449
- version_defaults[0.2] = Config[0]
450
- version_defaults[0.3] = Config[0]
501
+ version_defaults[0.0r] = Config[0r]
502
+ version_defaults[0.1r] = Config[0r]
503
+ version_defaults[0.2r] = Config[0r]
504
+ version_defaults[0.3r] = Config[0r]
451
505
 
452
- version_defaults[0.4] = Config[0.3].dup.update(
506
+ version_defaults[0.4r] = Config[0.3r].dup.update(
453
507
  sasl_ir: true,
454
508
  parser_max_deprecated_uidplus_data_size: 1000,
455
509
  ).freeze
456
510
 
457
- version_defaults[0.5] = Config[:current]
511
+ version_defaults[0.5r] = Config[0.4r].dup.update(
512
+ enforce_logindisabled: true,
513
+ max_response_size: 512 << 20, # 512 MiB
514
+ responses_without_block: :warn,
515
+ parser_use_deprecated_uidplus_data: :up_to_max_size,
516
+ parser_max_deprecated_uidplus_data_size: 100,
517
+ ).freeze
458
518
 
459
- version_defaults[0.6] = Config[0.5].dup.update(
519
+ version_defaults[0.6r] = Config[0.5r].dup.update(
460
520
  responses_without_block: :frozen_dup,
461
521
  parser_use_deprecated_uidplus_data: false,
462
522
  parser_max_deprecated_uidplus_data_size: 0,
463
523
  ).freeze
464
- version_defaults[:next] = Config[0.6]
465
- version_defaults[:future] = Config[:next]
524
+
525
+ version_defaults[0.7r] = Config[0.6r].dup.update(
526
+ ).freeze
527
+
528
+ # Safe conversions one way only:
529
+ # 0.6r.to_f == 0.6 # => true
530
+ # 0.6 .to_r == 0.6r # => false
531
+ version_defaults.to_a.each do |k, v|
532
+ next unless k in Rational
533
+ version_defaults[k.to_f] = v
534
+ end
535
+
536
+ current = VERSION.to_r
537
+ version_defaults[:original] = Config[0]
538
+ version_defaults[:current] = Config[current]
539
+ version_defaults[:next] = Config[current + 0.1r]
540
+ version_defaults[:future] = Config[current + 0.2r]
466
541
 
467
542
  version_defaults.freeze
543
+
544
+ if ($VERBOSE || $DEBUG) && self[:current].to_h != self[:default].to_h
545
+ warn "Misconfigured Net::IMAP::Config[:current] => %p,\n" \
546
+ " not equal to Net::IMAP::Config[:default] => %p" % [
547
+ self[:current].to_h, self[:default].to_h
548
+ ]
549
+ end
468
550
  end
469
551
  end
470
552
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ class ConnectionState < Net::IMAP::Data # :nodoc:
6
+ def self.define(symbol, *attrs)
7
+ symbol => Symbol
8
+ state = super(*attrs)
9
+ state.const_set :NAME, symbol
10
+ state
11
+ end
12
+
13
+ def symbol; self.class::NAME end
14
+ def name; self.class::NAME.name end
15
+ alias to_sym symbol
16
+
17
+ def deconstruct; [symbol, *super] end
18
+
19
+ def deconstruct_keys(names)
20
+ hash = super
21
+ hash[:symbol] = symbol if names.nil? || names.include?(:symbol)
22
+ hash[:name] = name if names.nil? || names.include?(:name)
23
+ hash
24
+ end
25
+
26
+ def to_h(&block)
27
+ hash = deconstruct_keys(nil)
28
+ block ? hash.to_h(&block) : hash
29
+ end
30
+
31
+ def not_authenticated?; to_sym == :not_authenticated end
32
+ def authenticated?; to_sym == :authenticated end
33
+ def selected?; to_sym == :selected end
34
+ def logout?; to_sym == :logout end
35
+
36
+ NotAuthenticated = define(:not_authenticated)
37
+ Authenticated = define(:authenticated)
38
+ Selected = define(:selected)
39
+ Logout = define(:logout)
40
+
41
+ class << self
42
+ undef :define
43
+ end
44
+ freeze
45
+ end
46
+
47
+ end
48
+ end
@@ -17,6 +17,39 @@ module Net
17
17
  class DataFormatError < Error
18
18
  end
19
19
 
20
+ # Error raised when the socket cannot be read, due to a Config limit.
21
+ class ResponseReadError < Error
22
+ end
23
+
24
+ # Error raised when a response is larger than IMAP#max_response_size.
25
+ class ResponseTooLargeError < ResponseReadError
26
+ attr_reader :bytes_read, :literal_size
27
+ attr_reader :max_response_size
28
+
29
+ def initialize(msg = nil, *args,
30
+ bytes_read: nil,
31
+ literal_size: nil,
32
+ max_response_size: nil,
33
+ **kwargs)
34
+ @bytes_read = bytes_read
35
+ @literal_size = literal_size
36
+ @max_response_size = max_response_size
37
+ msg ||= [
38
+ "Response size", response_size_msg, "exceeds max_response_size",
39
+ max_response_size && "(#{max_response_size}B)",
40
+ ].compact.join(" ")
41
+ super(msg, *args, **kwargs)
42
+ end
43
+
44
+ private
45
+
46
+ def response_size_msg
47
+ if bytes_read && literal_size
48
+ "(#{bytes_read}B read + #{literal_size}B literal)"
49
+ end
50
+ end
51
+ end
52
+
20
53
  # Error raised when a response from the server is non-parsable.
21
54
  class ResponseParseError < Error
22
55
  end
@@ -0,0 +1,73 @@
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
32
+ def empty? = buff.empty?
33
+ def done? = line_done? && !get_literal_size
34
+ def line_done? = buff.end_with?(CRLF)
35
+ def get_literal_size = /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i
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
56
+ def max_response_remaining = max_response_size &.- bytes_read
57
+ def response_too_large? = max_response_size &.< min_response_size
58
+ def min_response_size = bytes_read + min_response_remaining
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:, bytes_read:, literal_size:,
68
+ )
69
+ end
70
+
71
+ end
72
+ end
73
+ end