net-imap 0.5.2 → 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: d271261a2609a31b8c84ae74913bf57d5046e95318db7660b3230940bdbff447
4
- data.tar.gz: 771baa4b8a6c3f83f6ef4f320dda8dc5ea9f2fa2ace15fd9b0c1c1b3a594363a
3
+ metadata.gz: cdbdda0ed73da899ec338f66022a16104562d3701c568b0a6d4897270a608ac5
4
+ data.tar.gz: b6a7ec70776b32f8eb57d01a0869503eb5d76f719ac091eb92e0206608e936e9
5
5
  SHA512:
6
- metadata.gz: 230d40d8d183b1c69f63050990df140b49aa5731d0868713e44d2da68c2713ede4fd8d4b1aff60103c25ac6586076e220973b774a43cd5a7b3eaad4e49e7bf3b
7
- data.tar.gz: 156e6c86f6fe39a76de89275bfcd3c57b5adcd375de6add45cd00a30bf92bbf04a2b475702db33cae0509fc0b7b3e015518d4991cd5c467f833cacb0f75d3ca6
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
 
data/docs/styles.css CHANGED
@@ -6,33 +6,38 @@
6
6
 
7
7
  main .method-detail {
8
8
  display: grid;
9
- grid-template-areas: "header controls"
10
- "description description";
11
- grid-template-columns: 1fr min-content;
9
+ grid-template-columns: 1fr auto;
12
10
  justify-content: space-between;
13
11
  }
14
12
 
15
- main .method-header, main .method-controls {
13
+ main .method-header,
14
+ main .method-controls,
15
+ .attribute-method-heading {
16
16
  padding: 0.5em;
17
17
  /* border: 1px solid var(--highlight-color); */
18
18
  background: var(--table-header-background-color);
19
19
  line-height: 1.6;
20
20
  }
21
21
 
22
+ .attribute-method-heading .attribute-access-type {
23
+ float: right;
24
+ }
25
+
22
26
  main .method-header {
23
- grid-area: "header";
24
27
  border-right: none;
25
28
  border-radius: 4px 0 0 4px;
26
29
  }
27
30
 
31
+ main .method-heading :any-link {
32
+ text-decoration: none;
33
+ }
34
+
28
35
  main .method-controls {
29
- grid-area: "controls";
30
36
  border-left: none;
31
37
  border-radius: 0 4px 4px 0;
32
38
  }
33
39
 
34
40
  main .method-description, main .aliases {
35
- grid-area: "description";
36
41
  grid-column: 1 / span 2;
37
42
  padding-left: 1em;
38
43
  }
@@ -153,6 +153,38 @@ module Net
153
153
  end
154
154
  end
155
155
 
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"
179
+ end
180
+
181
+ def formatted = "%d:%d" % data.minmax
182
+
183
+ def send_data(imap, tag)
184
+ imap.__send__(:put_string, formatted)
185
+ end
186
+ end
187
+
156
188
  # *DEPRECATED*. Replaced by SequenceSet.
157
189
  class MessageSet < CommandData # :nodoc:
158
190
  def send_data(imap, tag)
@@ -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]
@@ -29,7 +29,7 @@ module Net
29
29
  class IMAP
30
30
  data_or_object = RUBY_VERSION >= "3.2.0" ? ::Data : Object
31
31
  class DataLite < data_or_object
32
- def encode_with(coder) coder.map = attributes.transform_keys(&:to_s) end
32
+ def encode_with(coder) coder.map = to_h.transform_keys(&:to_s) end
33
33
  def init_with(coder) initialize(**coder.map.transform_keys(&:to_sym)) end
34
34
  end
35
35
 
@@ -159,28 +159,27 @@ module Net
159
159
 
160
160
  ##
161
161
  def members; self.class.members end
162
- def attributes; Hash[members.map {|m| [m, send(m)] }] end
163
- def to_h(&block) attributes.to_h(&block) end
164
- def hash; [self.class, attributes].hash end
162
+ def to_h(&block) block ? __to_h__.to_h(&block) : __to_h__ end
163
+ def hash; [self.class, __to_h__].hash end
165
164
  def ==(other) self.class == other.class && to_h == other.to_h end
166
165
  def eql?(other) self.class == other.class && hash == other.hash end
167
- def deconstruct; attributes.values end
166
+ def deconstruct; __to_h__.values end
168
167
 
169
168
  def deconstruct_keys(keys)
170
169
  raise TypeError unless keys.is_a?(Array) || keys.nil?
171
- return attributes if keys&.first.nil?
172
- attributes.slice(*keys)
170
+ return __to_h__ if keys&.first.nil?
171
+ __to_h__.slice(*keys)
173
172
  end
174
173
 
175
174
  def with(**kwargs)
176
175
  return self if kwargs.empty?
177
- self.class.new(**attributes.merge(kwargs))
176
+ self.class.new(**__to_h__.merge(kwargs))
178
177
  end
179
178
 
180
179
  def inspect
181
180
  __inspect_guard__(self) do |seen|
182
181
  return "#<data #{self.class}:...>" if seen
183
- attrs = attributes.map {|kv| "%s=%p" % kv }.join(", ")
182
+ attrs = __to_h__.map {|kv| "%s=%p" % kv }.join(", ")
184
183
  display = ["data", self.class.name, attrs].compact.join(" ")
185
184
  "#<#{display}>"
186
185
  end
@@ -190,7 +189,9 @@ module Net
190
189
  private
191
190
 
192
191
  def initialize_copy(source) super.freeze end
193
- def marshal_dump; attributes end
192
+ def marshal_dump; __to_h__ end
193
+
194
+ def __to_h__; Hash[members.map {|m| [m, send(m)] }] end
194
195
 
195
196
  # Yields +true+ if +obj+ has been seen already, +false+ if it hasn't.
196
197
  # Marks +obj+ as seen inside the block, so circuler references don't
@@ -35,16 +35,16 @@ module Net
35
35
 
36
36
  # :call-seq: to_a -> Array of integers
37
37
  #
38
- # When #all contains a SequenceSet of message sequence
38
+ # When either #all or #partial contains a SequenceSet of message sequence
39
39
  # numbers or UIDs, +to_a+ returns that set as an array of integers.
40
40
  #
41
- # When #all is +nil+, either because the server
42
- # returned no results or because +ALL+ was not included in
41
+ # When both #all and #partial are +nil+, either because the server
42
+ # returned no results or because +ALL+ and +PARTIAL+ were not included in
43
43
  # the IMAP#search +RETURN+ options, #to_a returns an empty array.
44
44
  #
45
45
  # Note that SearchResult also implements +to_a+, so it can be used without
46
46
  # checking if the server returned +SEARCH+ or +ESEARCH+ data.
47
- def to_a; all&.numbers || [] end
47
+ def to_a; all&.numbers || partial&.to_a || [] end
48
48
 
49
49
  ##
50
50
  # attr_reader: tag
@@ -135,6 +135,46 @@ module Net
135
135
  # and +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.2].
136
136
  def modseq; data.assoc("MODSEQ")&.last end
137
137
 
138
+ # Returned by ESearchResult#partial.
139
+ #
140
+ # Requires +PARTIAL+ {[RFC9394]}[https://www.rfc-editor.org/rfc/rfc9394.html]
141
+ # or <tt>CONTEXT=SEARCH</tt>/<tt>CONTEXT=SORT</tt>
142
+ # {[RFC5267]}[https://www.rfc-editor.org/rfc/rfc5267.html]
143
+ #
144
+ # See also: #to_a
145
+ class PartialResult < Data.define(:range, :results)
146
+ def initialize(range:, results:)
147
+ range => Range
148
+ results = SequenceSet[results] unless results.nil?
149
+ super
150
+ end
151
+
152
+ ##
153
+ # method: range
154
+ # :call-seq: range -> range
155
+
156
+ ##
157
+ # method: results
158
+ # :call-seq: results -> sequence set or nil
159
+
160
+ # Converts #results to an array of integers.
161
+ #
162
+ # See also: ESearchResult#to_a.
163
+ def to_a; results&.numbers || [] end
164
+ end
165
+
166
+ # :call-seq: partial -> PartialResult or nil
167
+ #
168
+ # A PartialResult containing a subset of the message sequence numbers or
169
+ # UIDs that satisfy the SEARCH criteria.
170
+ #
171
+ # Requires +PARTIAL+ {[RFC9394]}[https://www.rfc-editor.org/rfc/rfc9394.html]
172
+ # or <tt>CONTEXT=SEARCH</tt>/<tt>CONTEXT=SORT</tt>
173
+ # {[RFC5267]}[https://www.rfc-editor.org/rfc/rfc5267.html]
174
+ #
175
+ # See also: #to_a
176
+ def partial; data.assoc("PARTIAL")&.last end
177
+
138
178
  end
139
179
  end
140
180
  end