net-imap 0.4.19 → 0.4.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/lib/net/imap/command_data.rb +182 -21
- data/lib/net/imap/config/attr_type_coercion.rb +23 -22
- data/lib/net/imap/config.rb +101 -20
- data/lib/net/imap/errors.rb +33 -0
- data/lib/net/imap/response_data.rb +22 -2
- data/lib/net/imap/response_reader.rb +82 -0
- data/lib/net/imap/sasl/scram_authenticator.rb +74 -0
- data/lib/net/imap/sequence_set.rb +70 -63
- data/lib/net/imap.rb +215 -69
- metadata +4 -3
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Net
|
|
4
|
+
class IMAP
|
|
5
|
+
# See https://www.rfc-editor.org/rfc/rfc9051#section-2.2.2
|
|
6
|
+
class ResponseReader # :nodoc:
|
|
7
|
+
attr_reader :client
|
|
8
|
+
|
|
9
|
+
def initialize(client, sock)
|
|
10
|
+
@client, @sock = client, sock
|
|
11
|
+
@buff = @literal_size = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def read_response_buffer
|
|
15
|
+
@buff = String.new
|
|
16
|
+
catch :eof do
|
|
17
|
+
while true
|
|
18
|
+
read_line
|
|
19
|
+
break unless literal_size
|
|
20
|
+
read_literal
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
buff
|
|
24
|
+
ensure
|
|
25
|
+
@buff = @literal_size = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :buff, :literal_size
|
|
31
|
+
|
|
32
|
+
def bytes_read; buff.bytesize end
|
|
33
|
+
def empty?; buff.empty? end
|
|
34
|
+
def done?; line_done? && !get_literal_size end
|
|
35
|
+
def done?; line_done? && !literal_size end
|
|
36
|
+
def line_done?; buff.end_with?(CRLF) end
|
|
37
|
+
|
|
38
|
+
def get_literal_size(buff)
|
|
39
|
+
buff.end_with?("}\r\n") && buff.rindex(/\{(\d+)\}\r\n\z/n) && $1.to_i
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def read_line
|
|
43
|
+
line = (@sock.gets(CRLF, read_limit) or throw :eof)
|
|
44
|
+
buff << line
|
|
45
|
+
max_response_remaining! unless line_done?
|
|
46
|
+
@literal_size = get_literal_size(line)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def read_literal
|
|
50
|
+
# check before allocating memory for literal
|
|
51
|
+
max_response_remaining!
|
|
52
|
+
literal = String.new(capacity: literal_size)
|
|
53
|
+
buff << (@sock.read(read_limit(literal_size), literal) or throw :eof)
|
|
54
|
+
ensure
|
|
55
|
+
@literal_size = nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def read_limit(limit = nil)
|
|
59
|
+
[limit, max_response_remaining!].compact.min
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def max_response_size; client.max_response_size end
|
|
63
|
+
def max_response_remaining; max_response_size &.- bytes_read end
|
|
64
|
+
def response_too_large?; max_response_size &.< min_response_size end
|
|
65
|
+
def min_response_size; bytes_read + min_response_remaining end
|
|
66
|
+
|
|
67
|
+
def min_response_remaining
|
|
68
|
+
empty? ? 3 : done? ? 0 : (literal_size || 0) + 2
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def max_response_remaining!
|
|
72
|
+
return max_response_remaining unless response_too_large?
|
|
73
|
+
raise ResponseTooLargeError.new(
|
|
74
|
+
max_response_size: max_response_size,
|
|
75
|
+
bytes_read: bytes_read,
|
|
76
|
+
literal_size: literal_size,
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -75,13 +75,19 @@ module Net
|
|
|
75
75
|
# * #password ― Password or passphrase associated with this #username.
|
|
76
76
|
# * _optional_ #authzid ― Alternate identity to act as or on behalf of.
|
|
77
77
|
# * _optional_ #min_iterations - Overrides the default value (4096).
|
|
78
|
+
# * _optional_ #max_iterations - Overrides the default value (2³¹ - 1).
|
|
78
79
|
#
|
|
79
80
|
# Any other keyword parameters are quietly ignored.
|
|
81
|
+
#
|
|
82
|
+
# *NOTE:* <em>It is the user's responsibility</em> to enforce minimum
|
|
83
|
+
# and maximum iteration counts that are appropriate for their security
|
|
84
|
+
# context.
|
|
80
85
|
def initialize(username_arg = nil, password_arg = nil,
|
|
81
86
|
authcid: nil, username: nil,
|
|
82
87
|
authzid: nil,
|
|
83
88
|
password: nil, secret: nil,
|
|
84
89
|
min_iterations: 4096, # see both RFC5802 and RFC7677
|
|
90
|
+
max_iterations: 2**31 - 1, # max int32
|
|
85
91
|
cnonce: nil, # must only be set in tests
|
|
86
92
|
**options)
|
|
87
93
|
@username = username || username_arg || authcid or
|
|
@@ -94,7 +100,22 @@ module Net
|
|
|
94
100
|
@min_iterations.positive? or
|
|
95
101
|
raise ArgumentError, "min_iterations must be positive"
|
|
96
102
|
|
|
103
|
+
@max_iterations = Integer max_iterations.to_int
|
|
104
|
+
@min_iterations <= @max_iterations or
|
|
105
|
+
raise ArgumentError, "max_iterations must be more than min_iterations"
|
|
106
|
+
|
|
97
107
|
@cnonce = cnonce || SecureRandom.base64(32)
|
|
108
|
+
|
|
109
|
+
# These attrs are set from the server challenges
|
|
110
|
+
@server_first_message = @snonce = @salt = @iterations = nil
|
|
111
|
+
@server_error = nil
|
|
112
|
+
|
|
113
|
+
# Memoized after @salt and @iterations have been sent.
|
|
114
|
+
@salted_password = @client_key = @server_key = nil
|
|
115
|
+
|
|
116
|
+
# These values are created and cached in response to server challenges
|
|
117
|
+
@client_first_message_bare = nil
|
|
118
|
+
@client_final_message_without_proof = nil
|
|
98
119
|
end
|
|
99
120
|
|
|
100
121
|
# Authentication identity: the identity that matches the #password.
|
|
@@ -127,8 +148,43 @@ module Net
|
|
|
127
148
|
|
|
128
149
|
# The minimal allowed iteration count. Lower #iterations will raise an
|
|
129
150
|
# Error.
|
|
151
|
+
#
|
|
152
|
+
# *WARNING:* The default value (4096) is set to match guidance from
|
|
153
|
+
# both {RFC5802}[https://www.rfc-editor.org/rfc/rfc5802#page-12]
|
|
154
|
+
# and RFC7677[https://www.rfc-editor.org/rfc/rfc7677#section-4], but
|
|
155
|
+
# {modern recommendations}[https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2]
|
|
156
|
+
# are significantly higher.
|
|
157
|
+
#
|
|
158
|
+
# It is ultimately the server's responsibility to securely store
|
|
159
|
+
# password hashes. While this parameter can alert the user to
|
|
160
|
+
# insecure password storage and prevent insecure authentication
|
|
161
|
+
# exchange, updating the iteration count generally requires resetting
|
|
162
|
+
# the password on the server.
|
|
130
163
|
attr_reader :min_iterations
|
|
131
164
|
|
|
165
|
+
# The maximal allowed iteration count. Higher #iterations will raise an
|
|
166
|
+
# Error.
|
|
167
|
+
#
|
|
168
|
+
# As noted in {RFC5802}[https://www.rfc-editor.org/rfc/rfc5802#section-9]
|
|
169
|
+
# >>>
|
|
170
|
+
# A hostile server can perform a computational denial-of-service
|
|
171
|
+
# attack on clients by sending a big iteration count value.
|
|
172
|
+
#
|
|
173
|
+
# *WARNING:* The default value is <tt>2³¹ - 1</tt>, the maximum signed
|
|
174
|
+
# 32-bit integer. This is large enough for the computation to take
|
|
175
|
+
# several minutes, and insufficient protection against hostile servers.
|
|
176
|
+
#
|
|
177
|
+
# Note that <tt>OpenSSL::KDF.pbkdf2_hmac</tt> is implemented by a
|
|
178
|
+
# blocking C function, and cannot be interrupted by +Timeout+ or
|
|
179
|
+
# <tt>Thread.raise</tt>. And it keeps the Global VM lock, as of v4.0 of
|
|
180
|
+
# the +openssl+ gem, so other ruby threads will not be able to run.
|
|
181
|
+
#
|
|
182
|
+
# <em>To prevent a denial of service attack,</em> this must be set to a
|
|
183
|
+
# safe value, depending on hardware and version of OpenSSL. <em>It is
|
|
184
|
+
# the user's responsibility</em> to enforce minimum and maximum
|
|
185
|
+
# iteration counts that are appropriate for their security context.
|
|
186
|
+
attr_reader :max_iterations
|
|
187
|
+
|
|
132
188
|
# The client nonce, generated by SecureRandom
|
|
133
189
|
attr_reader :cnonce
|
|
134
190
|
|
|
@@ -147,6 +203,15 @@ module Net
|
|
|
147
203
|
# Net::IMAP::NoResponseError.
|
|
148
204
|
attr_reader :server_error
|
|
149
205
|
|
|
206
|
+
# Memoized ScramAlgorithm#salted_password (needs #salt and #iterations)
|
|
207
|
+
def salted_password; @salted_password ||= compute_salted { super } end
|
|
208
|
+
|
|
209
|
+
# Memoized ScramAlgorithm#client_key (needs #salt and #iterations)
|
|
210
|
+
def client_key; @client_key ||= compute_salted { super } end
|
|
211
|
+
|
|
212
|
+
# Memoized ScramAlgorithm#server_key (needs #salt and #iterations)
|
|
213
|
+
def server_key; @server_key ||= compute_salted { super } end
|
|
214
|
+
|
|
150
215
|
# Returns a new OpenSSL::Digest object, set to the appropriate hash
|
|
151
216
|
# function for the chosen mechanism.
|
|
152
217
|
#
|
|
@@ -186,6 +251,13 @@ module Net
|
|
|
186
251
|
|
|
187
252
|
private
|
|
188
253
|
|
|
254
|
+
# Checks for +salt+ and +iterations+ before yielding
|
|
255
|
+
def compute_salted
|
|
256
|
+
String === salt or raise Error, "unknown salt"
|
|
257
|
+
Integer === iterations or raise Error, "unknown iterations"
|
|
258
|
+
yield
|
|
259
|
+
end
|
|
260
|
+
|
|
189
261
|
# Need to store this for auth_message
|
|
190
262
|
attr_reader :server_first_message
|
|
191
263
|
|
|
@@ -202,6 +274,8 @@ module Net
|
|
|
202
274
|
raise Error, "server did not send iteration count"
|
|
203
275
|
min_iterations <= iterations or
|
|
204
276
|
raise Error, "too few iterations: %d" % [iterations]
|
|
277
|
+
max_iterations.nil? || iterations <= max_iterations or
|
|
278
|
+
raise Error, "too many iterations: %d" % [iterations]
|
|
205
279
|
mext = sparams["m"] and
|
|
206
280
|
raise Error, "mandatory extension: %p" % [mext]
|
|
207
281
|
snonce.start_with? cnonce or
|
|
@@ -178,7 +178,7 @@ module Net
|
|
|
178
178
|
#
|
|
179
179
|
# <i>Set membership:</i>
|
|
180
180
|
# - #include? (aliased as #member?):
|
|
181
|
-
# Returns whether a given
|
|
181
|
+
# Returns whether a given element (nz-number, range, or <tt>*</tt>) is
|
|
182
182
|
# contained by the set.
|
|
183
183
|
# - #include_star?: Returns whether the set contains <tt>*</tt>.
|
|
184
184
|
#
|
|
@@ -243,13 +243,13 @@ module Net
|
|
|
243
243
|
# These methods do not modify +self+.
|
|
244
244
|
#
|
|
245
245
|
# - #| (aliased as #union and #+): Returns a new set combining all members
|
|
246
|
-
# from +self+ with all members from the other
|
|
246
|
+
# from +self+ with all members from the other set.
|
|
247
247
|
# - #& (aliased as #intersection): Returns a new set containing all members
|
|
248
|
-
# common to +self+ and the other
|
|
248
|
+
# common to +self+ and the other set.
|
|
249
249
|
# - #- (aliased as #difference): Returns a copy of +self+ with all members
|
|
250
|
-
# in the other
|
|
250
|
+
# in the other set removed.
|
|
251
251
|
# - #^ (aliased as #xor): Returns a new set containing all members from
|
|
252
|
-
# +self+ and the other
|
|
252
|
+
# +self+ and the other set except those common to both.
|
|
253
253
|
# - #~ (aliased as #complement): Returns a new set containing all members
|
|
254
254
|
# that are not in +self+
|
|
255
255
|
# - #limit: Returns a copy of +self+ which has replaced <tt>*</tt> with a
|
|
@@ -262,17 +262,17 @@ module Net
|
|
|
262
262
|
#
|
|
263
263
|
# These methods always update #string to be fully sorted and coalesced.
|
|
264
264
|
#
|
|
265
|
-
# - #add (aliased as #<<): Adds a given
|
|
266
|
-
# - #add?: If the given
|
|
265
|
+
# - #add (aliased as #<<): Adds a given element to the set; returns +self+.
|
|
266
|
+
# - #add?: If the given element is not fully included the set, adds it and
|
|
267
267
|
# returns +self+; otherwise, returns +nil+.
|
|
268
|
-
# - #merge:
|
|
268
|
+
# - #merge: Adds all members of the given sets into this set; returns +self+.
|
|
269
269
|
# - #complement!: Replaces the contents of the set with its own #complement.
|
|
270
270
|
#
|
|
271
271
|
# <i>Order preserving:</i>
|
|
272
272
|
#
|
|
273
273
|
# These methods _may_ cause #string to not be sorted or coalesced.
|
|
274
274
|
#
|
|
275
|
-
# - #append: Adds
|
|
275
|
+
# - #append: Adds the given entry to the set, appending it to the existing
|
|
276
276
|
# string, and returns +self+.
|
|
277
277
|
# - #string=: Assigns a new #string value and replaces #elements to match.
|
|
278
278
|
# - #replace: Replaces the contents of the set with the contents
|
|
@@ -283,13 +283,14 @@ module Net
|
|
|
283
283
|
# sorted and coalesced.
|
|
284
284
|
#
|
|
285
285
|
# - #clear: Removes all elements in the set; returns +self+.
|
|
286
|
-
# - #delete: Removes a given
|
|
287
|
-
# - #delete?: If the given
|
|
286
|
+
# - #delete: Removes a given element from the set; returns +self+.
|
|
287
|
+
# - #delete?: If the given element is included in the set, removes it and
|
|
288
288
|
# returns it; otherwise, returns +nil+.
|
|
289
289
|
# - #delete_at: Removes the number at a given offset.
|
|
290
290
|
# - #slice!: Removes the number or consecutive numbers at a given offset or
|
|
291
291
|
# range of offsets.
|
|
292
|
-
# - #subtract: Removes
|
|
292
|
+
# - #subtract: Removes all members of the given sets from this set; returns
|
|
293
|
+
# +self+.
|
|
293
294
|
# - #limit!: Replaces <tt>*</tt> with a given maximum value and removes all
|
|
294
295
|
# members over that maximum; returns +self+.
|
|
295
296
|
#
|
|
@@ -326,9 +327,12 @@ module Net
|
|
|
326
327
|
class << self
|
|
327
328
|
|
|
328
329
|
# :call-seq:
|
|
329
|
-
# SequenceSet[*
|
|
330
|
+
# SequenceSet[*inputs] -> valid frozen sequence set
|
|
330
331
|
#
|
|
331
|
-
# Returns a frozen SequenceSet, constructed from +
|
|
332
|
+
# Returns a frozen SequenceSet, constructed from +inputs+.
|
|
333
|
+
#
|
|
334
|
+
# When only a single valid frozen SequenceSet is given, that same set is
|
|
335
|
+
# returned.
|
|
332
336
|
#
|
|
333
337
|
# An empty SequenceSet is invalid and will raise a DataFormatError.
|
|
334
338
|
#
|
|
@@ -672,7 +676,7 @@ module Net
|
|
|
672
676
|
#
|
|
673
677
|
# <tt>(seqset ^ other)</tt> is equivalent to <tt>((seqset | other) -
|
|
674
678
|
# (seqset & other))</tt>.
|
|
675
|
-
def ^(other) remain_frozen (
|
|
679
|
+
def ^(other) remain_frozen (dup | other).subtract(self & other) end
|
|
676
680
|
alias xor :^
|
|
677
681
|
|
|
678
682
|
# :call-seq:
|
|
@@ -694,7 +698,7 @@ module Net
|
|
|
694
698
|
alias complement :~
|
|
695
699
|
|
|
696
700
|
# :call-seq:
|
|
697
|
-
# add(
|
|
701
|
+
# add(element) -> self
|
|
698
702
|
# self << other -> self
|
|
699
703
|
#
|
|
700
704
|
# Adds a range or number to the set and returns +self+.
|
|
@@ -702,8 +706,8 @@ module Net
|
|
|
702
706
|
# #string will be regenerated. Use #merge to add many elements at once.
|
|
703
707
|
#
|
|
704
708
|
# Related: #add?, #merge, #union
|
|
705
|
-
def add(
|
|
706
|
-
tuple_add input_to_tuple
|
|
709
|
+
def add(element)
|
|
710
|
+
tuple_add input_to_tuple element
|
|
707
711
|
normalize!
|
|
708
712
|
end
|
|
709
713
|
alias << add
|
|
@@ -712,9 +716,9 @@ module Net
|
|
|
712
716
|
#
|
|
713
717
|
# Unlike #add, #merge, or #union, the new value is appended to #string.
|
|
714
718
|
# This may result in a #string which has duplicates or is out-of-order.
|
|
715
|
-
def append(
|
|
719
|
+
def append(entry)
|
|
716
720
|
modifying!
|
|
717
|
-
tuple = input_to_tuple
|
|
721
|
+
tuple = input_to_tuple entry
|
|
718
722
|
entry = tuple_to_str tuple
|
|
719
723
|
string unless empty? # write @string before tuple_add
|
|
720
724
|
tuple_add tuple
|
|
@@ -722,19 +726,19 @@ module Net
|
|
|
722
726
|
self
|
|
723
727
|
end
|
|
724
728
|
|
|
725
|
-
# :call-seq: add?(
|
|
729
|
+
# :call-seq: add?(element) -> self or nil
|
|
726
730
|
#
|
|
727
731
|
# Adds a range or number to the set and returns +self+. Returns +nil+
|
|
728
|
-
# when the
|
|
732
|
+
# when the element is already included in the set.
|
|
729
733
|
#
|
|
730
734
|
# #string will be regenerated. Use #merge to add many elements at once.
|
|
731
735
|
#
|
|
732
736
|
# Related: #add, #merge, #union, #include?
|
|
733
|
-
def add?(
|
|
734
|
-
add
|
|
737
|
+
def add?(element)
|
|
738
|
+
add element unless include? element
|
|
735
739
|
end
|
|
736
740
|
|
|
737
|
-
# :call-seq: delete(
|
|
741
|
+
# :call-seq: delete(element) -> self
|
|
738
742
|
#
|
|
739
743
|
# Deletes the given range or number from the set and returns +self+.
|
|
740
744
|
#
|
|
@@ -742,8 +746,8 @@ module Net
|
|
|
742
746
|
# many elements at once.
|
|
743
747
|
#
|
|
744
748
|
# Related: #delete?, #delete_at, #subtract, #difference
|
|
745
|
-
def delete(
|
|
746
|
-
tuple_subtract input_to_tuple
|
|
749
|
+
def delete(element)
|
|
750
|
+
tuple_subtract input_to_tuple element
|
|
747
751
|
normalize!
|
|
748
752
|
end
|
|
749
753
|
|
|
@@ -779,8 +783,8 @@ module Net
|
|
|
779
783
|
# #string will be regenerated after deletion.
|
|
780
784
|
#
|
|
781
785
|
# Related: #delete, #delete_at, #subtract, #difference, #disjoint?
|
|
782
|
-
def delete?(
|
|
783
|
-
tuple = input_to_tuple
|
|
786
|
+
def delete?(element)
|
|
787
|
+
tuple = input_to_tuple element
|
|
784
788
|
if tuple.first == tuple.last
|
|
785
789
|
return unless include_tuple? tuple
|
|
786
790
|
tuple_subtract tuple
|
|
@@ -824,33 +828,31 @@ module Net
|
|
|
824
828
|
deleted
|
|
825
829
|
end
|
|
826
830
|
|
|
827
|
-
# Merges all of the elements that appear in any of the +
|
|
831
|
+
# Merges all of the elements that appear in any of the +sets+ into the
|
|
828
832
|
# set, and returns +self+.
|
|
829
833
|
#
|
|
830
|
-
# The +
|
|
831
|
-
#
|
|
832
|
-
#
|
|
833
|
-
# these.
|
|
834
|
+
# The +sets+ may be any objects that would be accepted by ::new: non-zero
|
|
835
|
+
# 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
|
|
836
|
+
# strings, other sequence sets, or enumerables containing any of these.
|
|
834
837
|
#
|
|
835
|
-
# #string will be regenerated after all
|
|
838
|
+
# #string will be regenerated after all sets have been merged.
|
|
836
839
|
#
|
|
837
840
|
# Related: #add, #add?, #union
|
|
838
|
-
def merge(*
|
|
839
|
-
tuples_add input_to_tuples
|
|
841
|
+
def merge(*sets)
|
|
842
|
+
tuples_add input_to_tuples sets
|
|
840
843
|
normalize!
|
|
841
844
|
end
|
|
842
845
|
|
|
843
|
-
# Removes all of the elements that appear in any of the given +
|
|
844
|
-
#
|
|
846
|
+
# Removes all of the elements that appear in any of the given +sets+ from
|
|
847
|
+
# the set, and returns +self+.
|
|
845
848
|
#
|
|
846
|
-
# The +
|
|
847
|
-
#
|
|
848
|
-
#
|
|
849
|
-
# these.
|
|
849
|
+
# The +sets+ may be any objects that would be accepted by ::new: non-zero
|
|
850
|
+
# 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
|
|
851
|
+
# strings, other sequence sets, or enumerables containing any of these.
|
|
850
852
|
#
|
|
851
853
|
# Related: #difference
|
|
852
|
-
def subtract(*
|
|
853
|
-
tuples_subtract input_to_tuples
|
|
854
|
+
def subtract(*sets)
|
|
855
|
+
tuples_subtract input_to_tuples sets
|
|
854
856
|
normalize!
|
|
855
857
|
end
|
|
856
858
|
|
|
@@ -1243,14 +1245,18 @@ module Net
|
|
|
1243
1245
|
def slice_range(range)
|
|
1244
1246
|
first = range.begin || 0
|
|
1245
1247
|
last = range.end || -1
|
|
1246
|
-
|
|
1248
|
+
if range.exclude_end?
|
|
1249
|
+
return remain_frozen_empty if last.zero?
|
|
1250
|
+
last -= 1 if range.end && last != STAR_INT
|
|
1251
|
+
end
|
|
1247
1252
|
if (first * last).positive? && last < first
|
|
1248
|
-
|
|
1253
|
+
remain_frozen_empty
|
|
1249
1254
|
elsif (min = at(first))
|
|
1250
1255
|
max = at(last)
|
|
1256
|
+
max = :* if max.nil?
|
|
1251
1257
|
if max == :* then self & (min..)
|
|
1252
1258
|
elsif min <= max then self & (min..max)
|
|
1253
|
-
else
|
|
1259
|
+
else remain_frozen_empty
|
|
1254
1260
|
end
|
|
1255
1261
|
end
|
|
1256
1262
|
end
|
|
@@ -1378,6 +1384,7 @@ module Net
|
|
|
1378
1384
|
private
|
|
1379
1385
|
|
|
1380
1386
|
def remain_frozen(set) frozen? ? set.freeze : set end
|
|
1387
|
+
def remain_frozen_empty; frozen? ? SequenceSet.empty : SequenceSet.new end
|
|
1381
1388
|
|
|
1382
1389
|
# frozen clones are shallow copied
|
|
1383
1390
|
def initialize_clone(other)
|
|
@@ -1390,29 +1397,29 @@ module Net
|
|
|
1390
1397
|
super
|
|
1391
1398
|
end
|
|
1392
1399
|
|
|
1393
|
-
def input_to_tuple(
|
|
1394
|
-
|
|
1395
|
-
case
|
|
1396
|
-
when *STARS, Integer then [int = to_tuple_int(
|
|
1397
|
-
when Range then range_to_tuple(
|
|
1398
|
-
when String then str_to_tuple(
|
|
1400
|
+
def input_to_tuple(entry)
|
|
1401
|
+
entry = input_try_convert entry
|
|
1402
|
+
case entry
|
|
1403
|
+
when *STARS, Integer then [int = to_tuple_int(entry), int]
|
|
1404
|
+
when Range then range_to_tuple(entry)
|
|
1405
|
+
when String then str_to_tuple(entry)
|
|
1399
1406
|
else
|
|
1400
|
-
raise DataFormatError, "expected number or range, got %p" % [
|
|
1407
|
+
raise DataFormatError, "expected number or range, got %p" % [entry]
|
|
1401
1408
|
end
|
|
1402
1409
|
end
|
|
1403
1410
|
|
|
1404
|
-
def input_to_tuples(
|
|
1405
|
-
|
|
1406
|
-
case
|
|
1407
|
-
when *STARS, Integer, Range then [input_to_tuple(
|
|
1408
|
-
when String then str_to_tuples
|
|
1409
|
-
when SequenceSet then
|
|
1410
|
-
when ENUMABLE then
|
|
1411
|
+
def input_to_tuples(set)
|
|
1412
|
+
set = input_try_convert set
|
|
1413
|
+
case set
|
|
1414
|
+
when *STARS, Integer, Range then [input_to_tuple(set)]
|
|
1415
|
+
when String then str_to_tuples set
|
|
1416
|
+
when SequenceSet then set.tuples
|
|
1417
|
+
when ENUMABLE then set.flat_map { input_to_tuples _1 }
|
|
1411
1418
|
when nil then []
|
|
1412
1419
|
else
|
|
1413
1420
|
raise DataFormatError,
|
|
1414
1421
|
"expected nz-number, range, string, or enumerable; " \
|
|
1415
|
-
"got %p" % [
|
|
1422
|
+
"got %p" % [set]
|
|
1416
1423
|
end
|
|
1417
1424
|
end
|
|
1418
1425
|
|