net-imap 0.5.1 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf511b8683c6931cf9ec2d3aa4a55ef634a2cc0e670590a4142d9895594b9191
4
- data.tar.gz: db4ca0b7230553258e0d81a8a5b5cbed4273e30245200db661145ba430af20d0
3
+ metadata.gz: cdbdda0ed73da899ec338f66022a16104562d3701c568b0a6d4897270a608ac5
4
+ data.tar.gz: b6a7ec70776b32f8eb57d01a0869503eb5d76f719ac091eb92e0206608e936e9
5
5
  SHA512:
6
- metadata.gz: b61a04c6992df7d6ffcf0e5396723966c3f3a5a1c6a596caf413c63d355100c8d2c28096a2261045d52ada4d8e995bba1c4f12864bf65fe1355c331df236eb85
7
- data.tar.gz: 0bc68ec15c5d53f0f437eab5318bf8cdd428dcdc650a98f66e961658ed361b125e866db2d41954e50ce66de5ec143d108e9135f554bd240ff0d7057724d16416
6
+ metadata.gz: 381bf2428719ed8decb5d241fda0e19f28031dd4a77980b3717bb29c37bed1c927f00e5b57862e209ecf24b2e9b38c01088d6e1a90fc4b4cc026cdd9e6611100
7
+ data.tar.gz: 513c6a77d46b6d2cf67aea4511023acc76c69940e3b1a0d0eae7223b53ff63bc8e6e009f51fef826b09f76f6ad1d92e84243e8457f59d3912db7e74bf69d3d1b
data/Gemfile CHANGED
@@ -8,6 +8,7 @@ gem "digest"
8
8
  gem "strscan"
9
9
  gem "base64"
10
10
 
11
+ gem "irb"
11
12
  gem "rake"
12
13
  gem "rdoc"
13
14
  gem "test-unit"
data/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # Net::IMAP
2
2
 
3
3
  Net::IMAP implements Internet Message Access Protocol (IMAP) client
4
- functionality. The protocol is described in [IMAP](https://tools.ietf.org/html/rfc3501).
4
+ functionality. The protocol is described in
5
+ [RFC3501](https://www.rfc-editor.org/rfc/rfc3501),
6
+ [RFC9051](https://www.rfc-editor.org/rfc/rfc9051) and various extensions.
5
7
 
6
8
  ## Installation
7
9
 
@@ -50,12 +52,16 @@ end
50
52
 
51
53
  ```ruby
52
54
  imap.select('Mail/sent-mail')
53
- if not imap.list('Mail/', 'sent-apr03')
55
+ if imap.list('Mail/', 'sent-apr03').empty?
54
56
  imap.create('Mail/sent-apr03')
55
57
  end
56
58
  imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id|
57
- imap.copy(message_id, "Mail/sent-apr03")
58
- imap.store(message_id, "+FLAGS", [:Deleted])
59
+ if imap.capable?(:move) || imap.capable?(:IMAP4rev2)
60
+ imap.move(message_id, "Mail/sent-apr03")
61
+ else
62
+ imap.copy(message_id, "Mail/sent-apr03")
63
+ imap.store(message_id, "+FLAGS", [:Deleted])
64
+ end
59
65
  end
60
66
  imap.expunge
61
67
  ```
data/docs/styles.css CHANGED
@@ -1,24 +1,85 @@
1
1
  /* this is a work in progress. :) */
2
2
 
3
- main .method-header {
4
- background: rgba(27,31,35,0.05);
5
- border: 1px solid #6C8C22;
3
+ /***********************************************
4
+ * Method descriptions
5
+ ***********************************************/
6
+
7
+ main .method-detail {
8
+ display: grid;
9
+ grid-template-columns: 1fr auto;
10
+ justify-content: space-between;
11
+ }
12
+
13
+ main .method-header,
14
+ main .method-controls,
15
+ .attribute-method-heading {
6
16
  padding: 0.5em;
7
- border-radius: 4px;
8
- /* padding: 0 0.5em; */
9
- /* border-width: 0 1px; */
10
- /* border-color: #6C8C22; */
11
- /* border-style: solid; */
17
+ /* border: 1px solid var(--highlight-color); */
18
+ background: var(--table-header-background-color);
19
+ line-height: 1.6;
20
+ }
21
+
22
+ .attribute-method-heading .attribute-access-type {
23
+ float: right;
24
+ }
25
+
26
+ main .method-header {
27
+ border-right: none;
28
+ border-radius: 4px 0 0 4px;
29
+ }
30
+
31
+ main .method-heading :any-link {
32
+ text-decoration: none;
33
+ }
34
+
35
+ main .method-controls {
36
+ border-left: none;
37
+ border-radius: 0 4px 4px 0;
12
38
  }
13
39
 
14
40
  main .method-description, main .aliases {
41
+ grid-column: 1 / span 2;
15
42
  padding-left: 1em;
16
43
  }
17
44
 
18
- body {
19
- /*
20
- * The default (300) can be too low contrast. Also, many fonts don't
21
- * distinguish between 300->400, so <em>...</em> had no effect.
22
- */
23
- font-weight: 400;
45
+ @media (max-width: 700px) {
46
+ main .method-header, main .method-controls, main .method-description {
47
+ grid-column: 1 / span 2;
48
+ margin: 0;
49
+ }
50
+ main .method-controls {
51
+ background: none;
52
+ }
53
+ }
54
+
55
+ /***********************************************
56
+ * Description lists
57
+ ***********************************************/
58
+
59
+ main dt {
60
+ margin-bottom: 0; /* override rdoc 6.8 */
61
+ float: unset; /* override rdoc 6.8 */
62
+ line-height: 1.5; /* matches `main p` */
63
+ }
64
+
65
+ main dl.note-list dt {
66
+ margin-right: 1em;
67
+ float: left;
68
+ }
69
+
70
+ main dl.note-list dt:has(+ dt) {
71
+ margin-right: 0.25em;
72
+ }
73
+
74
+ main dl.note-list dt:has(+ dt)::after {
75
+ content: ', ';
76
+ font-weight: normal;
77
+ }
78
+
79
+ main dd {
80
+ margin: 0 0 1em 1em;
81
+ }
82
+
83
+ main dd p:first-child {
84
+ margin-top: 0;
24
85
  }
@@ -3,6 +3,7 @@
3
3
  require "date"
4
4
 
5
5
  require_relative "errors"
6
+ require_relative "data_lite"
6
7
 
7
8
  module Net
8
9
  class IMAP < Protocol
@@ -119,80 +120,85 @@ module Net
119
120
  put_string("\\" + symbol.to_s)
120
121
  end
121
122
 
122
- class RawData # :nodoc:
123
+ CommandData = Data.define(:data) do # :nodoc:
123
124
  def send_data(imap, tag)
124
- imap.__send__(:put_string, @data)
125
+ raise NoMethodError, "#{self.class} must implement #{__method__}"
125
126
  end
126
127
 
127
128
  def validate
128
129
  end
129
-
130
- private
131
-
132
- def initialize(data)
133
- @data = data
134
- end
135
130
  end
136
131
 
137
- class Atom # :nodoc:
132
+ class RawData < CommandData # :nodoc:
138
133
  def send_data(imap, tag)
139
- imap.__send__(:put_string, @data)
140
- end
141
-
142
- def validate
143
- end
144
-
145
- private
146
-
147
- def initialize(data)
148
- @data = data
134
+ imap.__send__(:put_string, data)
149
135
  end
150
136
  end
151
137
 
152
- class QuotedString # :nodoc:
138
+ class Atom < CommandData # :nodoc:
153
139
  def send_data(imap, tag)
154
- imap.__send__(:send_quoted_string, @data)
155
- end
156
-
157
- def validate
140
+ imap.__send__(:put_string, data)
158
141
  end
142
+ end
159
143
 
160
- private
161
-
162
- def initialize(data)
163
- @data = data
144
+ class QuotedString < CommandData # :nodoc:
145
+ def send_data(imap, tag)
146
+ imap.__send__(:send_quoted_string, data)
164
147
  end
165
148
  end
166
149
 
167
- class Literal # :nodoc:
150
+ class Literal < CommandData # :nodoc:
168
151
  def send_data(imap, tag)
169
- imap.__send__(:send_literal, @data, tag)
152
+ imap.__send__(:send_literal, data, tag)
170
153
  end
154
+ end
171
155
 
172
- def validate
156
+ class PartialRange < CommandData # :nodoc:
157
+ uint32_max = 2**32 - 1
158
+ POS_RANGE = 1..uint32_max
159
+ NEG_RANGE = -uint32_max..-1
160
+ Positive = ->{ (_1 in Range) and POS_RANGE.cover?(_1) }
161
+ Negative = ->{ (_1 in Range) and NEG_RANGE.cover?(_1) }
162
+
163
+ def initialize(data:)
164
+ min, max = case data
165
+ in Range
166
+ data.minmax.map { Integer _1 }
167
+ in ResponseParser::Patterns::PARTIAL_RANGE
168
+ data.split(":").map { Integer _1 }.minmax
169
+ else
170
+ raise ArgumentError, "invalid partial range input: %p" % [data]
171
+ end
172
+ data = min..max
173
+ unless data in Positive | Negative
174
+ raise ArgumentError, "invalid partial-range: %p" % [data]
175
+ end
176
+ super
177
+ rescue TypeError, RangeError
178
+ raise ArgumentError, "expected range min/max to be Integers"
173
179
  end
174
180
 
175
- private
181
+ def formatted = "%d:%d" % data.minmax
176
182
 
177
- def initialize(data)
178
- @data = data
183
+ def send_data(imap, tag)
184
+ imap.__send__(:put_string, formatted)
179
185
  end
180
186
  end
181
187
 
182
188
  # *DEPRECATED*. Replaced by SequenceSet.
183
- class MessageSet # :nodoc:
189
+ class MessageSet < CommandData # :nodoc:
184
190
  def send_data(imap, tag)
185
- imap.__send__(:put_string, format_internal(@data))
191
+ imap.__send__(:put_string, format_internal(data))
186
192
  end
187
193
 
188
194
  def validate
189
- validate_internal(@data)
195
+ validate_internal(data)
190
196
  end
191
197
 
192
198
  private
193
199
 
194
- def initialize(data)
195
- @data = data
200
+ def initialize(data:)
201
+ super
196
202
  warn("DEPRECATED: #{MessageSet} should be replaced with #{SequenceSet}.",
197
203
  uplevel: 1, category: :deprecated)
198
204
  begin
@@ -246,22 +252,18 @@ module Net
246
252
  end
247
253
  end
248
254
 
249
- class ClientID # :nodoc:
255
+ class ClientID < CommandData # :nodoc:
250
256
 
251
257
  def send_data(imap, tag)
252
- imap.__send__(:send_data, format_internal(@data), tag)
258
+ imap.__send__(:send_data, format_internal(data), tag)
253
259
  end
254
260
 
255
261
  def validate
256
- validate_internal(@data)
262
+ validate_internal(data)
257
263
  end
258
264
 
259
265
  private
260
266
 
261
- def initialize(data)
262
- @data = data
263
- end
264
-
265
267
  def validate_internal(client_id)
266
268
  client_id.to_h.each do |k,v|
267
269
  unless StringFormatter.valid_string?(k)
@@ -75,7 +75,7 @@ module Net
75
75
  #
76
76
  # client = Net::IMAP.new(hostname, config: :future)
77
77
  # client.config.sasl_ir # => true
78
- # client.config.responses_without_block # => :raise
78
+ # client.config.responses_without_block # => :frozen_dup
79
79
  #
80
80
  # The versioned default configs inherit certain specific config options from
81
81
  # Config.global, for example #debug:
@@ -109,9 +109,11 @@ module Net
109
109
  # [+:future+]
110
110
  # The _planned_ eventual config for some future +x.y+ version.
111
111
  #
112
- # For example, to raise exceptions for all current deprecations:
112
+ # For example, to disable all currently deprecated behavior:
113
113
  # client = Net::IMAP.new(hostname, config: :future)
114
- # client.responses # raises an ArgumentError
114
+ # client.config.response_without_args # => :frozen_dup
115
+ # client.responses.frozen? # => true
116
+ # client.responses.values.all?(&:frozen?) # => true
115
117
  #
116
118
  # == Thread Safety
117
119
  #
@@ -285,6 +287,67 @@ module Net
285
287
  #
286
288
  # Alias for responses_without_block
287
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
+
288
351
  # Creates a new config object and initialize its attribute with +attrs+.
289
352
  #
290
353
  # If +parent+ is not given, the global config is used by default.
@@ -365,6 +428,8 @@ module Net
365
428
  sasl_ir: true,
366
429
  enforce_logindisabled: true,
367
430
  responses_without_block: :warn,
431
+ parser_use_deprecated_uidplus_data: :up_to_max_size,
432
+ parser_max_deprecated_uidplus_data_size: 100,
368
433
  ).freeze
369
434
 
370
435
  @global = default.new
@@ -376,6 +441,8 @@ module Net
376
441
  sasl_ir: false,
377
442
  responses_without_block: :silence_deprecation_warning,
378
443
  enforce_logindisabled: false,
444
+ parser_use_deprecated_uidplus_data: true,
445
+ parser_max_deprecated_uidplus_data_size: 10_000,
379
446
  ).freeze
380
447
  version_defaults[0.0] = Config[0]
381
448
  version_defaults[0.1] = Config[0]
@@ -384,12 +451,15 @@ module Net
384
451
 
385
452
  version_defaults[0.4] = Config[0.3].dup.update(
386
453
  sasl_ir: true,
454
+ parser_max_deprecated_uidplus_data_size: 1000,
387
455
  ).freeze
388
456
 
389
457
  version_defaults[0.5] = Config[:current]
390
458
 
391
459
  version_defaults[0.6] = Config[0.5].dup.update(
392
460
  responses_without_block: :frozen_dup,
461
+ parser_use_deprecated_uidplus_data: false,
462
+ parser_max_deprecated_uidplus_data_size: 0,
393
463
  ).freeze
394
464
  version_defaults[:next] = Config[0.6]
395
465
  version_defaults[:future] = Config[:next]
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Some of the code in this file was copied from the polyfill-data gem.
4
+ #
5
+ # MIT License
6
+ #
7
+ # Copyright (c) 2023 Jim Gay, Joel Drapper, Nicholas Evans
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ # of this software and associated documentation files (the "Software"), to deal
11
+ # in the Software without restriction, including without limitation the rights
12
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ # copies of the Software, and to permit persons to whom the Software is
14
+ # furnished to do so, subject to the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included in all
17
+ # copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ # SOFTWARE.
26
+
27
+
28
+ module Net
29
+ class IMAP
30
+ data_or_object = RUBY_VERSION >= "3.2.0" ? ::Data : Object
31
+ class DataLite < data_or_object
32
+ def encode_with(coder) coder.map = to_h.transform_keys(&:to_s) end
33
+ def init_with(coder) initialize(**coder.map.transform_keys(&:to_sym)) end
34
+ end
35
+
36
+ Data = DataLite
37
+ end
38
+ end
39
+
40
+ # :nocov:
41
+ # Need to skip test coverage for the rest, because it isn't loaded by ruby 3.2+.
42
+ return if RUBY_VERSION >= "3.2.0"
43
+
44
+ module Net
45
+ class IMAP
46
+ # DataLite is a temporary substitute for ruby 3.2's +Data+ class. DataLite
47
+ # is aliased as Net::IMAP::Data, so that code using it won't need to be
48
+ # updated when it is removed.
49
+ #
50
+ # See {ruby 3.2's documentation for Data}[https://docs.ruby-lang.org/en/3.2/Data.html].
51
+ #
52
+ # [When running ruby 3.1]
53
+ # This class reimplements the API for ruby 3.2's +Data+, and should be
54
+ # compatible for nearly all use-cases. This reimplementation <em>will be
55
+ # removed</em> in +net-imap+ 0.6, when support for ruby 3.1 is dropped.
56
+ #
57
+ # _NOTE:_ +net-imap+ no longer supports ruby versions prior to 3.1.
58
+ # [When running ruby >= 3.2]
59
+ # This class inherits from +Data+ and _only_ defines the methods needed
60
+ # for YAML serialization. This will be dropped when +psych+ adds support
61
+ # for +Data+.
62
+ #
63
+ # Some of the code in this class was copied or adapted from the
64
+ # {polyfill-data gem}[https://rubygems.org/gems/polyfill-data], by Jim Gay
65
+ # and Joel Drapper, under the MIT license terms.
66
+ class DataLite
67
+ singleton_class.undef_method :new
68
+
69
+ TYPE_ERROR = "%p is not a symbol nor a string"
70
+ ATTRSET_ERROR = "invalid data member: %p"
71
+ DUP_ERROR = "duplicate member: %p"
72
+ ARITY_ERROR = "wrong number of arguments (given %d, expected %s)"
73
+ private_constant :TYPE_ERROR, :ATTRSET_ERROR, :DUP_ERROR, :ARITY_ERROR
74
+
75
+ # Defines a new Data class.
76
+ #
77
+ # _NOTE:_ Unlike ruby 3.2's +Data.define+, DataLite.define only supports
78
+ # member names which are valid local variable names. Member names can't
79
+ # be keywords (e.g: +next+ or +class+) or start with capital letters, "@",
80
+ # etc.
81
+ def self.define(*args, &block)
82
+ members = args.each_with_object({}) do |arg, members|
83
+ arg = arg.to_str unless arg in Symbol | String if arg.respond_to?(:to_str)
84
+ arg = arg.to_sym if arg in String
85
+ arg in Symbol or raise TypeError, TYPE_ERROR % [arg]
86
+ arg in %r{=} and raise ArgumentError, ATTRSET_ERROR % [arg]
87
+ members.key?(arg) and raise ArgumentError, DUP_ERROR % [arg]
88
+ members[arg] = true
89
+ end
90
+ members = members.keys.freeze
91
+
92
+ klass = ::Class.new(self)
93
+
94
+ klass.singleton_class.undef_method :define
95
+ klass.define_singleton_method(:members) { members }
96
+
97
+ def klass.new(*args, **kwargs, &block)
98
+ if kwargs.size.positive?
99
+ if args.size.positive?
100
+ raise ArgumentError, ARITY_ERROR % [args.size, 0]
101
+ end
102
+ elsif members.size < args.size
103
+ expected = members.size.zero? ? 0 : 0..members.size
104
+ raise ArgumentError, ARITY_ERROR % [args.size, expected]
105
+ else
106
+ kwargs = Hash[members.take(args.size).zip(args)]
107
+ end
108
+ allocate.tap do |instance|
109
+ instance.__send__(:initialize, **kwargs, &block)
110
+ end.freeze
111
+ end
112
+
113
+ klass.singleton_class.alias_method :[], :new
114
+ klass.attr_reader(*members)
115
+
116
+ # Dynamically defined initializer methods are in an included module,
117
+ # rather than directly on DataLite (like in ruby 3.2+):
118
+ # * simpler to handle required kwarg ArgumentErrors
119
+ # * easier to ensure consistent ivar assignment order (object shape)
120
+ # * faster than instance_variable_set
121
+ klass.include(Module.new do
122
+ if members.any?
123
+ kwargs = members.map{"#{_1.name}:"}.join(", ")
124
+ params = members.map(&:name).join(", ")
125
+ ivars = members.map{"@#{_1.name}"}.join(", ")
126
+ attrs = members.map{"attrs[:#{_1.name}]"}.join(", ")
127
+ module_eval <<~RUBY, __FILE__, __LINE__ + 1
128
+ protected
129
+ def initialize(#{kwargs}) #{ivars} = #{params}; freeze end
130
+ def marshal_load(attrs) #{ivars} = #{attrs}; freeze end
131
+ RUBY
132
+ end
133
+ end)
134
+
135
+ klass.module_eval do _1.module_eval(&block) end if block_given?
136
+
137
+ klass
138
+ end
139
+
140
+ ##
141
+ # singleton-method: new
142
+ # call-seq:
143
+ # new(*args) -> instance
144
+ # new(**kwargs) -> instance
145
+ #
146
+ # Constuctor for classes defined with ::define.
147
+ #
148
+ # Aliased as ::[].
149
+
150
+ ##
151
+ # singleton-method: []
152
+ # call-seq:
153
+ # ::[](*args) -> instance
154
+ # ::[](**kwargs) -> instance
155
+ #
156
+ # Constuctor for classes defined with ::define.
157
+ #
158
+ # Alias for ::new
159
+
160
+ ##
161
+ def members; self.class.members end
162
+ def to_h(&block) block ? __to_h__.to_h(&block) : __to_h__ end
163
+ def hash; [self.class, __to_h__].hash end
164
+ def ==(other) self.class == other.class && to_h == other.to_h end
165
+ def eql?(other) self.class == other.class && hash == other.hash end
166
+ def deconstruct; __to_h__.values end
167
+
168
+ def deconstruct_keys(keys)
169
+ raise TypeError unless keys.is_a?(Array) || keys.nil?
170
+ return __to_h__ if keys&.first.nil?
171
+ __to_h__.slice(*keys)
172
+ end
173
+
174
+ def with(**kwargs)
175
+ return self if kwargs.empty?
176
+ self.class.new(**__to_h__.merge(kwargs))
177
+ end
178
+
179
+ def inspect
180
+ __inspect_guard__(self) do |seen|
181
+ return "#<data #{self.class}:...>" if seen
182
+ attrs = __to_h__.map {|kv| "%s=%p" % kv }.join(", ")
183
+ display = ["data", self.class.name, attrs].compact.join(" ")
184
+ "#<#{display}>"
185
+ end
186
+ end
187
+ alias_method :to_s, :inspect
188
+
189
+ private
190
+
191
+ def initialize_copy(source) super.freeze end
192
+ def marshal_dump; __to_h__ end
193
+
194
+ def __to_h__; Hash[members.map {|m| [m, send(m)] }] end
195
+
196
+ # Yields +true+ if +obj+ has been seen already, +false+ if it hasn't.
197
+ # Marks +obj+ as seen inside the block, so circuler references don't
198
+ # recursively trigger a SystemStackError (stack level too deep).
199
+ #
200
+ # Making circular references inside a Data object _should_ be very
201
+ # uncommon, but we'll support them for the sake of completeness.
202
+ def __inspect_guard__(obj)
203
+ preexisting = Thread.current[:__net_imap_data__inspect__]
204
+ Thread.current[:__net_imap_data__inspect__] ||= {}.compare_by_identity
205
+ inspect_guard = Thread.current[:__net_imap_data__inspect__]
206
+ if inspect_guard.include?(obj)
207
+ yield true
208
+ else
209
+ begin
210
+ inspect_guard[obj] = true
211
+ yield false
212
+ ensure
213
+ inspect_guard.delete(obj)
214
+ end
215
+ end
216
+ ensure
217
+ unless preexisting.equal?(inspect_guard)
218
+ Thread.current[:__net_imap_data__inspect__] = preexisting
219
+ end
220
+ end
221
+
222
+ end
223
+
224
+ end
225
+ end
226
+ # :nocov: