net-imap 0.3.9 → 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.
Files changed (73) 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 +62 -0
  14. data/lib/net/imap/config.rb +552 -0
  15. data/lib/net/imap/connection_state.rb +48 -0
  16. data/lib/net/imap/data_encoding.rb +18 -6
  17. data/lib/net/imap/data_lite.rb +226 -0
  18. data/lib/net/imap/deprecated_client_options.rb +142 -0
  19. data/lib/net/imap/errors.rb +28 -3
  20. data/lib/net/imap/esearch_result.rb +180 -0
  21. data/lib/net/imap/fetch_data.rb +597 -0
  22. data/lib/net/imap/flags.rb +1 -1
  23. data/lib/net/imap/response_data.rb +250 -440
  24. data/lib/net/imap/response_parser/parser_utils.rb +245 -0
  25. data/lib/net/imap/response_parser.rb +1873 -1210
  26. data/lib/net/imap/response_reader.rb +10 -12
  27. data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
  28. data/lib/net/imap/sasl/authentication_exchange.rb +139 -0
  29. data/lib/net/imap/sasl/authenticators.rb +122 -0
  30. data/lib/net/imap/sasl/client_adapter.rb +123 -0
  31. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +24 -14
  32. data/lib/net/imap/sasl/digest_md5_authenticator.rb +342 -0
  33. data/lib/net/imap/sasl/external_authenticator.rb +83 -0
  34. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  35. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +28 -18
  36. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
  37. data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
  38. data/lib/net/imap/sasl/protocol_adapters.rb +101 -0
  39. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  40. data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
  41. data/lib/net/imap/sasl/stringprep.rb +6 -66
  42. data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
  43. data/lib/net/imap/sasl.rb +148 -44
  44. data/lib/net/imap/sasl_adapter.rb +20 -0
  45. data/lib/net/imap/search_result.rb +146 -0
  46. data/lib/net/imap/sequence_set.rb +1721 -0
  47. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  48. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  49. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  50. data/lib/net/imap/stringprep/tables.rb +146 -0
  51. data/lib/net/imap/stringprep/trace.rb +85 -0
  52. data/lib/net/imap/stringprep.rb +159 -0
  53. data/lib/net/imap/uidplus_data.rb +244 -0
  54. data/lib/net/imap/vanished_data.rb +56 -0
  55. data/lib/net/imap.rb +2217 -861
  56. data/net-imap.gemspec +7 -8
  57. data/rakelib/benchmarks.rake +91 -0
  58. data/rakelib/rfcs.rake +2 -0
  59. data/rakelib/saslprep.rake +4 -4
  60. data/rakelib/string_prep_tables_generator.rb +84 -60
  61. data/sample/net-imap.rb +167 -0
  62. metadata +45 -45
  63. data/.github/dependabot.yml +0 -6
  64. data/.github/workflows/test.yml +0 -38
  65. data/.gitignore +0 -10
  66. data/benchmarks/stringprep.yml +0 -65
  67. data/benchmarks/table-regexps.yml +0 -39
  68. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  69. data/lib/net/imap/authenticators/plain.rb +0 -41
  70. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  71. data/lib/net/imap/sasl/saslprep.rb +0 -55
  72. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  73. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
@@ -0,0 +1,552 @@
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
+ # 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
144
+ def self.version_defaults; @version_defaults end
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
+ }
153
+
154
+ # :call-seq:
155
+ # Net::IMAP::Config[number] -> versioned config
156
+ # Net::IMAP::Config[symbol] -> named config
157
+ # Net::IMAP::Config[hash] -> new frozen config
158
+ # Net::IMAP::Config[config] -> same config
159
+ #
160
+ # Given a version number, returns the default configuration for the target
161
+ # version. See Config@Versioned+defaults.
162
+ #
163
+ # Given a version name, returns the default configuration for the target
164
+ # version. See Config@Named+defaults.
165
+ #
166
+ # Given a Hash, creates a new _frozen_ config which inherits from
167
+ # Config.global. Use Config.new for an unfrozen config.
168
+ #
169
+ # Given a config, returns that same config.
170
+ def self.[](config)
171
+ if config.is_a?(Config) then config
172
+ elsif config.nil? && global.nil? then nil
173
+ elsif config.respond_to?(:to_hash) then new(global, **config).freeze
174
+ else
175
+ version_defaults[config] or
176
+ case config
177
+ when Numeric
178
+ raise RangeError, "unknown config version: %p" % [config]
179
+ when String, Symbol
180
+ raise KeyError, "unknown config name: %p" % [config]
181
+ else
182
+ raise TypeError, "no implicit conversion of %s to %s" % [
183
+ config.class, Config
184
+ ]
185
+ end
186
+ end
187
+ end
188
+
189
+ include AttrAccessors
190
+ include AttrInheritance
191
+ include AttrTypeCoercion
192
+
193
+ # The debug mode (boolean). The default value is +false+.
194
+ #
195
+ # When #debug is +true+:
196
+ # * Data sent to and received from the server will be logged.
197
+ # * ResponseParser will print warnings with extra detail for parse
198
+ # errors. _This may include recoverable errors._
199
+ # * ResponseParser makes extra assertions.
200
+ #
201
+ # *NOTE:* Versioned default configs inherit #debug from Config.global, and
202
+ # #load_defaults will not override #debug.
203
+ attr_accessor :debug, type: :boolean
204
+
205
+ # method: debug?
206
+ # :call-seq: debug? -> boolean
207
+ #
208
+ # Alias for #debug
209
+
210
+ # Seconds to wait until a connection is opened.
211
+ #
212
+ # Applied separately for establishing TCP connection and starting a TLS
213
+ # connection.
214
+ #
215
+ # If the IMAP object cannot open a connection within this time,
216
+ # it raises a Net::OpenTimeout exception.
217
+ #
218
+ # See Net::IMAP.new and Net::IMAP#starttls.
219
+ #
220
+ # The default value is +30+ seconds.
221
+ attr_accessor :open_timeout, type: Integer
222
+
223
+ # Seconds to wait until an IDLE response is received, after
224
+ # the client asks to leave the IDLE state.
225
+ #
226
+ # See Net::IMAP#idle and Net::IMAP#idle_done.
227
+ #
228
+ # The default value is +5+ seconds.
229
+ attr_accessor :idle_response_timeout, type: Integer
230
+
231
+ # Whether to use the +SASL-IR+ extension when the server and \SASL
232
+ # mechanism both support it. Can be overridden by the +sasl_ir+ keyword
233
+ # parameter to Net::IMAP#authenticate.
234
+ #
235
+ # <em>(Support for +SASL-IR+ was added in +v0.4.0+.)</em>
236
+ #
237
+ # ==== Valid options
238
+ #
239
+ # [+false+ <em>(original behavior, before support was added)</em>]
240
+ # Do not use +SASL-IR+, even when it is supported by the server and the
241
+ # mechanism.
242
+ #
243
+ # [+true+ <em>(default since +v0.4+)</em>]
244
+ # Use +SASL-IR+ when it is supported by the server and the mechanism.
245
+ attr_accessor :sasl_ir, type: :boolean
246
+
247
+ # Controls the behavior of Net::IMAP#login when the +LOGINDISABLED+
248
+ # capability is present. When enforced, Net::IMAP will raise a
249
+ # LoginDisabledError when that capability is present.
250
+ #
251
+ # <em>(Support for +LOGINDISABLED+ was added in +v0.5.0+.)</em>
252
+ #
253
+ # ==== Valid options
254
+ #
255
+ # [+false+ <em>(original behavior, before support was added)</em>]
256
+ # Send the +LOGIN+ command without checking for +LOGINDISABLED+.
257
+ #
258
+ # [+:when_capabilities_cached+]
259
+ # Enforce the requirement when Net::IMAP#capabilities_cached? is true,
260
+ # but do not send a +CAPABILITY+ command to discover the capabilities.
261
+ #
262
+ # [+true+ <em>(default since +v0.5+)</em>]
263
+ # Only send the +LOGIN+ command if the +LOGINDISABLED+ capability is not
264
+ # present. When capabilities are unknown, Net::IMAP will automatically
265
+ # send a +CAPABILITY+ command first before sending +LOGIN+.
266
+ #
267
+ attr_accessor :enforce_logindisabled, type: Enum[
268
+ false, :when_capabilities_cached, true
269
+ ]
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
+
305
+ # Controls the behavior of Net::IMAP#responses when called without any
306
+ # arguments (+type+ or +block+).
307
+ #
308
+ # ==== Valid options
309
+ #
310
+ # [+:silence_deprecation_warning+ <em>(original behavior)</em>]
311
+ # Returns the mutable responses hash (without any warnings).
312
+ # <em>This is not thread-safe.</em>
313
+ #
314
+ # [+:warn+ <em>(default since +v0.5+)</em>]
315
+ # Prints a warning and returns the mutable responses hash.
316
+ # <em>This is not thread-safe.</em>
317
+ #
318
+ # [+:frozen_dup+ <em>(planned default for +v0.6+)</em>]
319
+ # Returns a frozen copy of the unhandled responses hash, with frozen
320
+ # array values.
321
+ #
322
+ # Note that calling IMAP#responses with a +type+ and without a block is
323
+ # not configurable and always behaves like +:frozen_dup+.
324
+ #
325
+ # <em>(+:frozen_dup+ config option was added in +v0.4.17+)</em>
326
+ #
327
+ # [+:raise+]
328
+ # Raise an ArgumentError with the deprecation warning.
329
+ #
330
+ # Note: #responses_without_args is an alias for #responses_without_block.
331
+ attr_accessor :responses_without_block, type: Enum[
332
+ :silence_deprecation_warning, :warn, :frozen_dup, :raise,
333
+ ]
334
+
335
+ alias responses_without_args responses_without_block # :nodoc:
336
+ alias responses_without_args= responses_without_block= # :nodoc:
337
+
338
+ ##
339
+ # :attr_accessor: responses_without_args
340
+ #
341
+ # Alias for responses_without_block
342
+
343
+ # Whether ResponseParser should use the deprecated UIDPlusData or
344
+ # CopyUIDData for +COPYUID+ response codes, and UIDPlusData or
345
+ # AppendUIDData for +APPENDUID+ response codes.
346
+ #
347
+ # UIDPlusData stores its data in arrays of numbers, which is vulnerable to
348
+ # a memory exhaustion denial of service attack from an untrusted or
349
+ # compromised server. Set this option to +false+ to completely block this
350
+ # vulnerability. Otherwise, parser_max_deprecated_uidplus_data_size
351
+ # mitigates this vulnerability.
352
+ #
353
+ # AppendUIDData and CopyUIDData are _mostly_ backward-compatible with
354
+ # UIDPlusData. Most applications should be able to upgrade with little
355
+ # or no changes.
356
+ #
357
+ # <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
358
+ #
359
+ # <em>(Config option added in +v0.4.19+ and +v0.5.6+.)</em>
360
+ #
361
+ # <em>UIDPlusData will be removed in +v0.6+ and this config setting will
362
+ # be ignored.</em>
363
+ #
364
+ # ==== Valid options
365
+ #
366
+ # [+true+ <em>(original default)</em>]
367
+ # ResponseParser only uses UIDPlusData.
368
+ #
369
+ # [+:up_to_max_size+ <em>(default since +v0.5.6+)</em>]
370
+ # ResponseParser uses UIDPlusData when the +uid-set+ size is below
371
+ # parser_max_deprecated_uidplus_data_size. Above that size,
372
+ # ResponseParser uses AppendUIDData or CopyUIDData.
373
+ #
374
+ # [+false+ <em>(planned default for +v0.6+)</em>]
375
+ # ResponseParser _only_ uses AppendUIDData and CopyUIDData.
376
+ attr_accessor :parser_use_deprecated_uidplus_data, type: Enum[
377
+ true, :up_to_max_size, false
378
+ ]
379
+
380
+ # The maximum +uid-set+ size that ResponseParser will parse into
381
+ # deprecated UIDPlusData. This limit only applies when
382
+ # parser_use_deprecated_uidplus_data is not +false+.
383
+ #
384
+ # <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
385
+ #
386
+ # <em>Support for limiting UIDPlusData to a maximum size was added in
387
+ # +v0.3.8+, +v0.4.19+, and +v0.5.6+.</em>
388
+ #
389
+ # <em>UIDPlusData will be removed in +v0.6+.</em>
390
+ #
391
+ # ==== Versioned Defaults
392
+ #
393
+ # Because this limit guards against a remote server causing catastrophic
394
+ # memory exhaustion, the versioned default (used by #load_defaults) also
395
+ # applies to versions without the feature.
396
+ #
397
+ # * +0.3+ and prior: <tt>10,000</tt>
398
+ # * +0.4+: <tt>1,000</tt>
399
+ # * +0.5+: <tt>100</tt>
400
+ # * +0.6+: <tt>0</tt>
401
+ #
402
+ attr_accessor :parser_max_deprecated_uidplus_data_size, type: Integer
403
+
404
+ # Creates a new config object and initialize its attribute with +attrs+.
405
+ #
406
+ # If +parent+ is not given, the global config is used by default.
407
+ #
408
+ # If a block is given, the new config object is yielded to it.
409
+ def initialize(parent = Config.global, **attrs)
410
+ super(parent)
411
+ update(**attrs)
412
+ yield self if block_given?
413
+ end
414
+
415
+ # :call-seq: update(**attrs) -> self
416
+ #
417
+ # Assigns all of the provided +attrs+ to this config, and returns +self+.
418
+ #
419
+ # An ArgumentError is raised unless every key in +attrs+ matches an
420
+ # assignment method on Config.
421
+ #
422
+ # >>>
423
+ # *NOTE:* #update is not atomic. If an exception is raised due to an
424
+ # invalid attribute value, +attrs+ may be partially applied.
425
+ def update(**attrs)
426
+ unless (bad = attrs.keys.reject { respond_to?(:"#{_1}=") }).empty?
427
+ raise ArgumentError, "invalid config options: #{bad.join(", ")}"
428
+ end
429
+ attrs.each do send(:"#{_1}=", _2) end
430
+ self
431
+ end
432
+
433
+ # :call-seq:
434
+ # with(**attrs) -> config
435
+ # with(**attrs) {|config| } -> result
436
+ #
437
+ # Without a block, returns a new config which inherits from self. With a
438
+ # block, yields the new config and returns the block's result.
439
+ #
440
+ # If no keyword arguments are given, an ArgumentError will be raised.
441
+ #
442
+ # If +self+ is frozen, the copy will also be frozen.
443
+ def with(**attrs)
444
+ attrs.empty? and
445
+ raise ArgumentError, "expected keyword arguments, none given"
446
+ copy = new(**attrs)
447
+ copy.freeze if frozen?
448
+ block_given? ? yield(copy) : copy
449
+ end
450
+
451
+ # :call-seq: load_defaults(version) -> self
452
+ #
453
+ # Resets the current config to behave like the versioned default
454
+ # configuration for +version+. #parent will not be changed.
455
+ #
456
+ # Some config attributes default to inheriting from their #parent (which
457
+ # is usually Config.global) and are left unchanged, for example: #debug.
458
+ #
459
+ # See Config@Versioned+defaults and Config@Named+defaults.
460
+ def load_defaults(version)
461
+ [Numeric, Symbol, String].any? { _1 === version } or
462
+ raise ArgumentError, "expected number or symbol, got %p" % [version]
463
+ update(**Config[version].defaults_hash)
464
+ end
465
+
466
+ # :call-seq: to_h -> hash
467
+ #
468
+ # Returns all config attributes in a hash.
469
+ def to_h; data.members.to_h { [_1, send(_1)] } end
470
+
471
+ protected
472
+
473
+ def defaults_hash
474
+ to_h.reject {|k,v| DEFAULT_TO_INHERIT.include?(k) }
475
+ end
476
+
477
+ @default = new(
478
+ debug: false,
479
+ open_timeout: 30,
480
+ idle_response_timeout: 5,
481
+ sasl_ir: true,
482
+ enforce_logindisabled: true,
483
+ max_response_size: 512 << 20, # 512 MiB
484
+ responses_without_block: :warn,
485
+ parser_use_deprecated_uidplus_data: :up_to_max_size,
486
+ parser_max_deprecated_uidplus_data_size: 100,
487
+ ).freeze
488
+
489
+ @global = default.new
490
+
491
+ version_defaults[:default] = Config[default.send(:defaults_hash)]
492
+
493
+ version_defaults[0r] = Config[:default].dup.update(
494
+ sasl_ir: false,
495
+ responses_without_block: :silence_deprecation_warning,
496
+ enforce_logindisabled: false,
497
+ max_response_size: nil,
498
+ parser_use_deprecated_uidplus_data: true,
499
+ parser_max_deprecated_uidplus_data_size: 10_000,
500
+ ).freeze
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]
505
+
506
+ version_defaults[0.4r] = Config[0.3r].dup.update(
507
+ sasl_ir: true,
508
+ parser_max_deprecated_uidplus_data_size: 1000,
509
+ ).freeze
510
+
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
518
+
519
+ version_defaults[0.6r] = Config[0.5r].dup.update(
520
+ responses_without_block: :frozen_dup,
521
+ parser_use_deprecated_uidplus_data: false,
522
+ parser_max_deprecated_uidplus_data_size: 0,
523
+ ).freeze
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]
541
+
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
550
+ end
551
+ end
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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "date"
4
+ require "time"
4
5
 
5
6
  require_relative "errors"
6
7
 
@@ -102,8 +103,16 @@ module Net
102
103
  #
103
104
  # Decodes +string+ as an IMAP4 formatted "date-time".
104
105
  #
105
- # Note that double quotes are not optional. See STRFTIME.
106
+ # NOTE: Although double-quotes are not optional in the IMAP grammar,
107
+ # Net::IMAP currently parses "date-time" values as "quoted" strings and this
108
+ # removes the quotation marks. To be useful for strings which have already
109
+ # been parsed as a quoted string, this method makes double-quotes optional.
110
+ #
111
+ # See STRFTIME.
106
112
  def self.decode_datetime(string)
113
+ unless string.start_with?(?") && string.end_with?(?")
114
+ string = '"%s"' % [string]
115
+ end
107
116
  DateTime.strptime(string, STRFTIME)
108
117
  end
109
118
 
@@ -113,7 +122,10 @@ module Net
113
122
  #
114
123
  # Same as +decode_datetime+, but returning a Time instead.
115
124
  def self.decode_time(string)
116
- decode_datetime(string).to_time
125
+ unless string.start_with?(?") && string.end_with?(?")
126
+ string = '"%s"' % [string]
127
+ end
128
+ Time.strptime(string, STRFTIME)
117
129
  end
118
130
 
119
131
  class << self
@@ -124,7 +136,7 @@ module Net
124
136
  alias parse_datetime decode_datetime
125
137
  alias parse_time decode_time
126
138
 
127
- # alias format_datetime encode_datetime # n.b. this is overridden below...
139
+ # alias format_datetime encode_datetime # n.b: this is overridden below...
128
140
  end
129
141
 
130
142
  # DEPRECATED:: The original version returned incorrectly formatted strings.
@@ -174,7 +186,7 @@ module Net
174
186
 
175
187
  # Ensure argument is 'number' or raise DataFormatError
176
188
  def ensure_number(num)
177
- return if valid_number?(num)
189
+ return num if valid_number?(num)
178
190
 
179
191
  msg = "number must be unsigned 32-bit integer: #{num}"
180
192
  raise DataFormatError, msg
@@ -182,7 +194,7 @@ module Net
182
194
 
183
195
  # Ensure argument is 'nz_number' or raise DataFormatError
184
196
  def ensure_nz_number(num)
185
- return if valid_nz_number?(num)
197
+ return num if valid_nz_number?(num)
186
198
 
187
199
  msg = "nz_number must be non-zero unsigned 32-bit integer: #{num}"
188
200
  raise DataFormatError, msg
@@ -190,7 +202,7 @@ module Net
190
202
 
191
203
  # Ensure argument is 'mod_sequence_value' or raise DataFormatError
192
204
  def ensure_mod_sequence_value(num)
193
- return if valid_mod_sequence_value?(num)
205
+ return num if valid_mod_sequence_value?(num)
194
206
 
195
207
  msg = "mod_sequence_value must be unsigned 64-bit integer: #{num}"
196
208
  raise DataFormatError, msg