net-imap 0.3.4 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/COPYING +56 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +3 -22
  6. data/README.md +25 -8
  7. data/Rakefile +0 -7
  8. data/docs/styles.css +72 -23
  9. data/lib/net/imap/authenticators.rb +26 -57
  10. data/lib/net/imap/command_data.rb +74 -54
  11. data/lib/net/imap/config/attr_accessors.rb +75 -0
  12. data/lib/net/imap/config/attr_inheritance.rb +90 -0
  13. data/lib/net/imap/config/attr_type_coercion.rb +61 -0
  14. data/lib/net/imap/config.rb +470 -0
  15. data/lib/net/imap/data_encoding.rb +21 -9
  16. data/lib/net/imap/data_lite.rb +226 -0
  17. data/lib/net/imap/deprecated_client_options.rb +142 -0
  18. data/lib/net/imap/errors.rb +27 -1
  19. data/lib/net/imap/esearch_result.rb +180 -0
  20. data/lib/net/imap/fetch_data.rb +597 -0
  21. data/lib/net/imap/flags.rb +1 -1
  22. data/lib/net/imap/response_data.rb +250 -440
  23. data/lib/net/imap/response_parser/parser_utils.rb +245 -0
  24. data/lib/net/imap/response_parser.rb +1867 -1184
  25. data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
  26. data/lib/net/imap/sasl/authentication_exchange.rb +139 -0
  27. data/lib/net/imap/sasl/authenticators.rb +122 -0
  28. data/lib/net/imap/sasl/client_adapter.rb +123 -0
  29. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +24 -14
  30. data/lib/net/imap/sasl/digest_md5_authenticator.rb +342 -0
  31. data/lib/net/imap/sasl/external_authenticator.rb +83 -0
  32. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  33. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +28 -18
  34. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
  35. data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
  36. data/lib/net/imap/sasl/protocol_adapters.rb +101 -0
  37. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  38. data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
  39. data/lib/net/imap/sasl/stringprep.rb +6 -66
  40. data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
  41. data/lib/net/imap/sasl.rb +148 -44
  42. data/lib/net/imap/sasl_adapter.rb +20 -0
  43. data/lib/net/imap/search_result.rb +146 -0
  44. data/lib/net/imap/sequence_set.rb +1565 -0
  45. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  46. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  47. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  48. data/lib/net/imap/stringprep/tables.rb +146 -0
  49. data/lib/net/imap/stringprep/trace.rb +85 -0
  50. data/lib/net/imap/stringprep.rb +159 -0
  51. data/lib/net/imap/uidplus_data.rb +244 -0
  52. data/lib/net/imap/vanished_data.rb +56 -0
  53. data/lib/net/imap.rb +2090 -823
  54. data/net-imap.gemspec +7 -8
  55. data/rakelib/benchmarks.rake +91 -0
  56. data/rakelib/rfcs.rake +2 -0
  57. data/rakelib/saslprep.rake +4 -4
  58. data/rakelib/string_prep_tables_generator.rb +84 -60
  59. data/sample/net-imap.rb +167 -0
  60. metadata +45 -49
  61. data/.github/dependabot.yml +0 -6
  62. data/.github/workflows/test.yml +0 -31
  63. data/.gitignore +0 -10
  64. data/benchmarks/stringprep.yml +0 -65
  65. data/benchmarks/table-regexps.yml +0 -39
  66. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  67. data/lib/net/imap/authenticators/plain.rb +0 -41
  68. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  69. data/lib/net/imap/sasl/saslprep.rb +0 -55
  70. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  71. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Net
6
+ class IMAP
7
+ class Config
8
+ # >>>
9
+ # *NOTE:* This module is an internal implementation detail, with no
10
+ # guarantee of backward compatibility.
11
+ #
12
+ # +attr_accessor+ values are stored in a struct rather than ivars, making
13
+ # it simpler to ensure that all config objects share a single object
14
+ # shape. This also simplifies iteration over all defined attributes.
15
+ module AttrAccessors
16
+ module Macros # :nodoc: internal API
17
+ def attr_accessor(name) AttrAccessors.attr_accessor(name) end
18
+ end
19
+ private_constant :Macros
20
+
21
+ def self.included(mod)
22
+ mod.extend Macros
23
+ end
24
+ private_class_method :included
25
+
26
+ extend Forwardable
27
+
28
+ def self.attr_accessor(name) # :nodoc: internal API
29
+ name = name.to_sym
30
+ def_delegators :data, name, :"#{name}="
31
+ end
32
+
33
+ def self.attributes
34
+ instance_methods.grep(/=\z/).map { _1.to_s.delete_suffix("=").to_sym }
35
+ end
36
+ private_class_method :attributes
37
+
38
+ def self.struct # :nodoc: internal API
39
+ unless defined?(self::Struct)
40
+ const_set :Struct, Struct.new(*attributes)
41
+ end
42
+ self::Struct
43
+ end
44
+
45
+ def initialize # :notnew:
46
+ super()
47
+ @data = AttrAccessors.struct.new
48
+ end
49
+
50
+ # Freezes the internal attributes struct, in addition to +self+.
51
+ def freeze
52
+ data.freeze
53
+ super
54
+ end
55
+
56
+ protected
57
+
58
+ attr_reader :data # :nodoc: internal API
59
+
60
+ private
61
+
62
+ def initialize_clone(other)
63
+ super
64
+ @data = other.data.clone
65
+ end
66
+
67
+ def initialize_dup(other)
68
+ super
69
+ @data = other.data.dup
70
+ end
71
+
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ class Config
6
+ # >>>
7
+ # *NOTE:* The public methods on this module are part of the stable
8
+ # public API of Net::IMAP::Config. But the module itself is an internal
9
+ # implementation detail, with no guarantee of backward compatibility.
10
+ #
11
+ # +attr_accessor+ methods will delegate to their #parent when the local
12
+ # value does not contain an override. Inheritance forms a singly linked
13
+ # list, so lookup will be <tt>O(n)</tt> on the number of ancestors. In
14
+ # practice, the ancestor chain is not expected to be long. Without
15
+ # customization, it is only three deep:
16
+ # >>>
17
+ # IMAP#config → Config.global → Config.default
18
+ #
19
+ # When creating a client with the +config+ keyword, for example to use
20
+ # the appropriate defaults for an application or a library while still
21
+ # relying on global for configuration of +debug+ or +logger+, most likely
22
+ # the ancestor chain is still only four deep:
23
+ # >>>
24
+ # IMAP#config → alternate defaults → Config.global → Config.default
25
+ module AttrInheritance
26
+ INHERITED = Module.new.freeze
27
+ private_constant :INHERITED
28
+
29
+ module Macros # :nodoc: internal API
30
+ def attr_accessor(name) super; AttrInheritance.attr_accessor(name) end
31
+ end
32
+ private_constant :Macros
33
+
34
+ def self.included(mod)
35
+ mod.extend Macros
36
+ end
37
+ private_class_method :included
38
+
39
+ def self.attr_accessor(name) # :nodoc: internal API
40
+ module_eval <<~RUBY, __FILE__, __LINE__ + 1
41
+ def #{name}; (val = super) == INHERITED ? parent&.#{name} : val end
42
+ RUBY
43
+ end
44
+
45
+ # The parent Config object
46
+ attr_reader :parent
47
+
48
+ def initialize(parent = nil) # :notnew:
49
+ super()
50
+ @parent = Config[parent]
51
+ reset
52
+ end
53
+
54
+ # Creates a new config, which inherits from +self+.
55
+ def new(**attrs) self.class.new(self, **attrs) end
56
+
57
+ # Returns +true+ if +attr+ is inherited from #parent and not overridden
58
+ # by this config.
59
+ def inherited?(attr) data[attr] == INHERITED end
60
+
61
+ # :call-seq:
62
+ # reset -> self
63
+ # reset(attr) -> attribute value
64
+ #
65
+ # Resets an +attr+ to inherit from the #parent config.
66
+ #
67
+ # When +attr+ is nil or not given, all attributes are reset.
68
+ def reset(attr = nil)
69
+ if attr.nil?
70
+ data.members.each do |attr| data[attr] = INHERITED end
71
+ self
72
+ elsif inherited?(attr)
73
+ nil
74
+ else
75
+ old, data[attr] = data[attr], INHERITED
76
+ old
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def initialize_copy(other)
83
+ super
84
+ @parent ||= other # only default has nil parent
85
+ end
86
+
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ class Config
6
+ # >>>
7
+ # *NOTE:* This module is an internal implementation detail, with no
8
+ # guarantee of backward compatibility.
9
+ #
10
+ # Adds a +type+ keyword parameter to +attr_accessor+, to enforce that
11
+ # config attributes have valid types, for example: boolean, numeric,
12
+ # enumeration, non-nullable, etc.
13
+ module AttrTypeCoercion
14
+ # :stopdoc: internal APIs only
15
+
16
+ module Macros # :nodoc: internal API
17
+ def attr_accessor(attr, type: nil)
18
+ super(attr)
19
+ AttrTypeCoercion.attr_accessor(attr, type: type)
20
+ end
21
+ end
22
+ private_constant :Macros
23
+
24
+ def self.included(mod)
25
+ mod.extend Macros
26
+ end
27
+ private_class_method :included
28
+
29
+ def self.attr_accessor(attr, type: nil)
30
+ return unless type
31
+ if :boolean == type then boolean attr
32
+ elsif Integer == type then integer attr
33
+ elsif Array === type then enum attr, type
34
+ else raise ArgumentError, "unknown type coercion %p" % [type]
35
+ end
36
+ end
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
42
+
43
+ def self.integer(attr)
44
+ define_method :"#{attr}=" do |val| super Integer val end
45
+ end
46
+
47
+ def self.enum(attr, enum)
48
+ enum = enum.dup.freeze
49
+ 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
57
+
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,470 @@
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 # => :frozen_dup
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 disable all currently deprecated behavior:
113
+ # client = Net::IMAP.new(hostname, config: :future)
114
+ # client.config.response_without_args # => :frozen_dup
115
+ # client.responses.frozen? # => true
116
+ # client.responses.values.all?(&:frozen?) # => true
117
+ #
118
+ # == Thread Safety
119
+ #
120
+ # *NOTE:* Updates to config objects are not synchronized for thread-safety.
121
+ #
122
+ class Config
123
+ # Array of attribute names that are _not_ loaded by #load_defaults.
124
+ DEFAULT_TO_INHERIT = %i[debug].freeze
125
+ private_constant :DEFAULT_TO_INHERIT
126
+
127
+ # The default config, which is hardcoded and frozen.
128
+ def self.default; @default end
129
+
130
+ # The global config object. Also available from Net::IMAP.config.
131
+ def self.global; @global if defined?(@global) end
132
+
133
+ # A hash of hard-coded configurations, indexed by version number or name.
134
+ def self.version_defaults; @version_defaults end
135
+ @version_defaults = {}
136
+
137
+ # :call-seq:
138
+ # Net::IMAP::Config[number] -> versioned config
139
+ # Net::IMAP::Config[symbol] -> named config
140
+ # Net::IMAP::Config[hash] -> new frozen config
141
+ # Net::IMAP::Config[config] -> same config
142
+ #
143
+ # Given a version number, returns the default configuration for the target
144
+ # version. See Config@Versioned+defaults.
145
+ #
146
+ # Given a version name, returns the default configuration for the target
147
+ # version. See Config@Named+defaults.
148
+ #
149
+ # Given a Hash, creates a new _frozen_ config which inherits from
150
+ # Config.global. Use Config.new for an unfrozen config.
151
+ #
152
+ # Given a config, returns that same config.
153
+ def self.[](config)
154
+ if config.is_a?(Config) then config
155
+ elsif config.nil? && global.nil? then nil
156
+ elsif config.respond_to?(:to_hash) then new(global, **config).freeze
157
+ else
158
+ version_defaults.fetch(config) do
159
+ case config
160
+ when Numeric
161
+ raise RangeError, "unknown config version: %p" % [config]
162
+ when Symbol
163
+ raise KeyError, "unknown config name: %p" % [config]
164
+ else
165
+ raise TypeError, "no implicit conversion of %s to %s" % [
166
+ config.class, Config
167
+ ]
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ include AttrAccessors
174
+ include AttrInheritance
175
+ include AttrTypeCoercion
176
+
177
+ # The debug mode (boolean). The default value is +false+.
178
+ #
179
+ # When #debug is +true+:
180
+ # * Data sent to and received from the server will be logged.
181
+ # * ResponseParser will print warnings with extra detail for parse
182
+ # errors. _This may include recoverable errors._
183
+ # * ResponseParser makes extra assertions.
184
+ #
185
+ # *NOTE:* Versioned default configs inherit #debug from Config.global, and
186
+ # #load_defaults will not override #debug.
187
+ attr_accessor :debug, type: :boolean
188
+
189
+ # method: debug?
190
+ # :call-seq: debug? -> boolean
191
+ #
192
+ # Alias for #debug
193
+
194
+ # Seconds to wait until a connection is opened.
195
+ #
196
+ # If the IMAP object cannot open a connection within this time,
197
+ # it raises a Net::OpenTimeout exception.
198
+ #
199
+ # See Net::IMAP.new.
200
+ #
201
+ # The default value is +30+ seconds.
202
+ attr_accessor :open_timeout, type: Integer
203
+
204
+ # Seconds to wait until an IDLE response is received, after
205
+ # the client asks to leave the IDLE state.
206
+ #
207
+ # See Net::IMAP#idle and Net::IMAP#idle_done.
208
+ #
209
+ # The default value is +5+ seconds.
210
+ attr_accessor :idle_response_timeout, type: Integer
211
+
212
+ # Whether to use the +SASL-IR+ extension when the server and \SASL
213
+ # mechanism both support it. Can be overridden by the +sasl_ir+ keyword
214
+ # parameter to Net::IMAP#authenticate.
215
+ #
216
+ # <em>(Support for +SASL-IR+ was added in +v0.4.0+.)</em>
217
+ #
218
+ # ==== Valid options
219
+ #
220
+ # [+false+ <em>(original behavior, before support was added)</em>]
221
+ # Do not use +SASL-IR+, even when it is supported by the server and the
222
+ # mechanism.
223
+ #
224
+ # [+true+ <em>(default since +v0.4+)</em>]
225
+ # Use +SASL-IR+ when it is supported by the server and the mechanism.
226
+ attr_accessor :sasl_ir, type: :boolean
227
+
228
+ # Controls the behavior of Net::IMAP#login when the +LOGINDISABLED+
229
+ # capability is present. When enforced, Net::IMAP will raise a
230
+ # LoginDisabledError when that capability is present.
231
+ #
232
+ # <em>(Support for +LOGINDISABLED+ was added in +v0.5.0+.)</em>
233
+ #
234
+ # ==== Valid options
235
+ #
236
+ # [+false+ <em>(original behavior, before support was added)</em>]
237
+ # Send the +LOGIN+ command without checking for +LOGINDISABLED+.
238
+ #
239
+ # [+:when_capabilities_cached+]
240
+ # Enforce the requirement when Net::IMAP#capabilities_cached? is true,
241
+ # but do not send a +CAPABILITY+ command to discover the capabilities.
242
+ #
243
+ # [+true+ <em>(default since +v0.5+)</em>]
244
+ # Only send the +LOGIN+ command if the +LOGINDISABLED+ capability is not
245
+ # present. When capabilities are unknown, Net::IMAP will automatically
246
+ # send a +CAPABILITY+ command first before sending +LOGIN+.
247
+ #
248
+ attr_accessor :enforce_logindisabled, type: [
249
+ false, :when_capabilities_cached, true
250
+ ]
251
+
252
+ # Controls the behavior of Net::IMAP#responses when called without any
253
+ # arguments (+type+ or +block+).
254
+ #
255
+ # ==== Valid options
256
+ #
257
+ # [+:silence_deprecation_warning+ <em>(original behavior)</em>]
258
+ # Returns the mutable responses hash (without any warnings).
259
+ # <em>This is not thread-safe.</em>
260
+ #
261
+ # [+:warn+ <em>(default since +v0.5+)</em>]
262
+ # Prints a warning and returns the mutable responses hash.
263
+ # <em>This is not thread-safe.</em>
264
+ #
265
+ # [+:frozen_dup+ <em>(planned default for +v0.6+)</em>]
266
+ # Returns a frozen copy of the unhandled responses hash, with frozen
267
+ # array values.
268
+ #
269
+ # Note that calling IMAP#responses with a +type+ and without a block is
270
+ # not configurable and always behaves like +:frozen_dup+.
271
+ #
272
+ # <em>(+:frozen_dup+ config option was added in +v0.4.17+)</em>
273
+ #
274
+ # [+:raise+]
275
+ # Raise an ArgumentError with the deprecation warning.
276
+ #
277
+ # Note: #responses_without_args is an alias for #responses_without_block.
278
+ attr_accessor :responses_without_block, type: [
279
+ :silence_deprecation_warning, :warn, :frozen_dup, :raise,
280
+ ]
281
+
282
+ alias responses_without_args responses_without_block # :nodoc:
283
+ alias responses_without_args= responses_without_block= # :nodoc:
284
+
285
+ ##
286
+ # :attr_accessor: responses_without_args
287
+ #
288
+ # Alias for responses_without_block
289
+
290
+ # Whether ResponseParser should use the deprecated UIDPlusData or
291
+ # CopyUIDData for +COPYUID+ response codes, and UIDPlusData or
292
+ # AppendUIDData for +APPENDUID+ response codes.
293
+ #
294
+ # UIDPlusData stores its data in arrays of numbers, which is vulnerable to
295
+ # a memory exhaustion denial of service attack from an untrusted or
296
+ # compromised server. Set this option to +false+ to completely block this
297
+ # vulnerability. Otherwise, parser_max_deprecated_uidplus_data_size
298
+ # mitigates this vulnerability.
299
+ #
300
+ # AppendUIDData and CopyUIDData are _mostly_ backward-compatible with
301
+ # UIDPlusData. Most applications should be able to upgrade with little
302
+ # or no changes.
303
+ #
304
+ # <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
305
+ #
306
+ # <em>(Config option added in +v0.4.19+ and +v0.5.6+.)</em>
307
+ #
308
+ # <em>UIDPlusData will be removed in +v0.6+ and this config setting will
309
+ # be ignored.</em>
310
+ #
311
+ # ==== Valid options
312
+ #
313
+ # [+true+ <em>(original default)</em>]
314
+ # ResponseParser only uses UIDPlusData.
315
+ #
316
+ # [+:up_to_max_size+ <em>(default since +v0.5.6+)</em>]
317
+ # ResponseParser uses UIDPlusData when the +uid-set+ size is below
318
+ # parser_max_deprecated_uidplus_data_size. Above that size,
319
+ # ResponseParser uses AppendUIDData or CopyUIDData.
320
+ #
321
+ # [+false+ <em>(planned default for +v0.6+)</em>]
322
+ # ResponseParser _only_ uses AppendUIDData and CopyUIDData.
323
+ attr_accessor :parser_use_deprecated_uidplus_data, type: [
324
+ true, :up_to_max_size, false
325
+ ]
326
+
327
+ # The maximum +uid-set+ size that ResponseParser will parse into
328
+ # deprecated UIDPlusData. This limit only applies when
329
+ # parser_use_deprecated_uidplus_data is not +false+.
330
+ #
331
+ # <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
332
+ #
333
+ # <em>Support for limiting UIDPlusData to a maximum size was added in
334
+ # +v0.3.8+, +v0.4.19+, and +v0.5.6+.</em>
335
+ #
336
+ # <em>UIDPlusData will be removed in +v0.6+.</em>
337
+ #
338
+ # ==== Versioned Defaults
339
+ #
340
+ # Because this limit guards against a remote server causing catastrophic
341
+ # memory exhaustion, the versioned default (used by #load_defaults) also
342
+ # applies to versions without the feature.
343
+ #
344
+ # * +0.3+ and prior: <tt>10,000</tt>
345
+ # * +0.4+: <tt>1,000</tt>
346
+ # * +0.5+: <tt>100</tt>
347
+ # * +0.6+: <tt>0</tt>
348
+ #
349
+ attr_accessor :parser_max_deprecated_uidplus_data_size, type: Integer
350
+
351
+ # Creates a new config object and initialize its attribute with +attrs+.
352
+ #
353
+ # If +parent+ is not given, the global config is used by default.
354
+ #
355
+ # If a block is given, the new config object is yielded to it.
356
+ def initialize(parent = Config.global, **attrs)
357
+ super(parent)
358
+ update(**attrs)
359
+ yield self if block_given?
360
+ end
361
+
362
+ # :call-seq: update(**attrs) -> self
363
+ #
364
+ # Assigns all of the provided +attrs+ to this config, and returns +self+.
365
+ #
366
+ # An ArgumentError is raised unless every key in +attrs+ matches an
367
+ # assignment method on Config.
368
+ #
369
+ # >>>
370
+ # *NOTE:* #update is not atomic. If an exception is raised due to an
371
+ # invalid attribute value, +attrs+ may be partially applied.
372
+ def update(**attrs)
373
+ unless (bad = attrs.keys.reject { respond_to?(:"#{_1}=") }).empty?
374
+ raise ArgumentError, "invalid config options: #{bad.join(", ")}"
375
+ end
376
+ attrs.each do send(:"#{_1}=", _2) end
377
+ self
378
+ end
379
+
380
+ # :call-seq:
381
+ # with(**attrs) -> config
382
+ # with(**attrs) {|config| } -> result
383
+ #
384
+ # Without a block, returns a new config which inherits from self. With a
385
+ # block, yields the new config and returns the block's result.
386
+ #
387
+ # If no keyword arguments are given, an ArgumentError will be raised.
388
+ #
389
+ # If +self+ is frozen, the copy will also be frozen.
390
+ def with(**attrs)
391
+ attrs.empty? and
392
+ raise ArgumentError, "expected keyword arguments, none given"
393
+ copy = new(**attrs)
394
+ copy.freeze if frozen?
395
+ block_given? ? yield(copy) : copy
396
+ end
397
+
398
+ # :call-seq: load_defaults(version) -> self
399
+ #
400
+ # Resets the current config to behave like the versioned default
401
+ # configuration for +version+. #parent will not be changed.
402
+ #
403
+ # Some config attributes default to inheriting from their #parent (which
404
+ # is usually Config.global) and are left unchanged, for example: #debug.
405
+ #
406
+ # See Config@Versioned+defaults and Config@Named+defaults.
407
+ def load_defaults(version)
408
+ [Numeric, Symbol, String].any? { _1 === version } or
409
+ raise ArgumentError, "expected number or symbol, got %p" % [version]
410
+ update(**Config[version].defaults_hash)
411
+ end
412
+
413
+ # :call-seq: to_h -> hash
414
+ #
415
+ # Returns all config attributes in a hash.
416
+ def to_h; data.members.to_h { [_1, send(_1)] } end
417
+
418
+ protected
419
+
420
+ def defaults_hash
421
+ to_h.reject {|k,v| DEFAULT_TO_INHERIT.include?(k) }
422
+ end
423
+
424
+ @default = new(
425
+ debug: false,
426
+ open_timeout: 30,
427
+ idle_response_timeout: 5,
428
+ sasl_ir: true,
429
+ enforce_logindisabled: true,
430
+ responses_without_block: :warn,
431
+ parser_use_deprecated_uidplus_data: :up_to_max_size,
432
+ parser_max_deprecated_uidplus_data_size: 100,
433
+ ).freeze
434
+
435
+ @global = default.new
436
+
437
+ version_defaults[:default] = Config[default.send(:defaults_hash)]
438
+ version_defaults[:current] = Config[:default]
439
+
440
+ version_defaults[0] = Config[:current].dup.update(
441
+ sasl_ir: false,
442
+ responses_without_block: :silence_deprecation_warning,
443
+ enforce_logindisabled: false,
444
+ parser_use_deprecated_uidplus_data: true,
445
+ parser_max_deprecated_uidplus_data_size: 10_000,
446
+ ).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]
451
+
452
+ version_defaults[0.4] = Config[0.3].dup.update(
453
+ sasl_ir: true,
454
+ parser_max_deprecated_uidplus_data_size: 1000,
455
+ ).freeze
456
+
457
+ version_defaults[0.5] = Config[:current]
458
+
459
+ version_defaults[0.6] = Config[0.5].dup.update(
460
+ responses_without_block: :frozen_dup,
461
+ parser_use_deprecated_uidplus_data: false,
462
+ parser_max_deprecated_uidplus_data_size: 0,
463
+ ).freeze
464
+ version_defaults[:next] = Config[0.6]
465
+ version_defaults[:future] = Config[:next]
466
+
467
+ version_defaults.freeze
468
+ end
469
+ end
470
+ end