net-imap 0.4.25 → 0.5.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf05b4f7aec9127531e83134b740620da216f847851f8ca464ef54e71e3431ad
4
- data.tar.gz: 18361d6ee25983da0e4814d42a0ec851e10ce88789559e30778a945a7024a297
3
+ metadata.gz: 2c89d97e842bce303df112c3fbc0f048f20a2ff1dbf3b85bb2314ea0d2e207c5
4
+ data.tar.gz: 11d6ec196e1e768f61a17b0ce7f385a11eb03e3e92af8ab327ba9a90699ee940
5
5
  SHA512:
6
- metadata.gz: 697214c436f129746ad8986dde31e4b819854cf131472158c4aacf5a35ef665b8529528bd0a843ba17d9be105711b4163ecccfa525989d9fe0b3f807f4ae9d58
7
- data.tar.gz: ef657a1dbfd5bc73fa317248ae19589a842f87eabfe966b43b026e9dceff68d510621d3e17136b266d22f68b85d20fc22b4ea67a482fdca193d8a5290c643abf
6
+ metadata.gz: ca70e20c3c70f4ca57822f70936546adb3129dd7c0771ef2d054b0356ed4f6a994b7453803cc06bd25ebb37dc0ed10f60b5541bc56749dee38a6126264344599
7
+ data.tar.gz: 2921a79dbdab7bd0476d883c18db50150388d587c711270faac99ad4505574a23e6532fe61a4fb48f51c8bddea6641d96f81d9dcbe245b4d4f4542fcc7418566
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
- # gem "digest" # not included as a workaround for #576
7
+ gem "digest"
8
8
  gem "strscan"
9
9
  gem "base64"
10
10
 
@@ -13,4 +13,10 @@ gem "rdoc"
13
13
  gem "test-unit"
14
14
  gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core"
15
15
 
16
- gem "benchmark-driver"
16
+ gem "benchmark-driver", require: false
17
+
18
+ group :test do
19
+ gem "simplecov", require: false
20
+ gem "simplecov-html", require: false
21
+ gem "simplecov-json", require: false
22
+ end
@@ -9,7 +9,7 @@ module Net::IMAP::Authenticators
9
9
  "%s.%s is deprecated. Use %s.%s instead." % [
10
10
  Net::IMAP, __method__, Net::IMAP::SASL, __method__
11
11
  ],
12
- uplevel: 1
12
+ uplevel: 1, category: :deprecated
13
13
  )
14
14
  Net::IMAP::SASL.add_authenticator(...)
15
15
  end
@@ -20,7 +20,7 @@ module Net::IMAP::Authenticators
20
20
  "%s.%s is deprecated. Use %s.%s instead." % [
21
21
  Net::IMAP, __method__, Net::IMAP::SASL, __method__
22
22
  ],
23
- uplevel: 1
23
+ uplevel: 1, category: :deprecated
24
24
  )
25
25
  Net::IMAP::SASL.authenticator(...)
26
26
  end
@@ -14,19 +14,17 @@ module Net
14
14
  when nil
15
15
  when String
16
16
  when Integer
17
- # Covers modseq-valzer, which is the largest valid IMAP integer
18
- if data.negative?
19
- raise DataFormatError, "Integer argument must be unsigned: #{data}"
20
- elsif 0xffff_ffff_ffff_ffff < data
21
- raise DataFormatError, "Integer argument must fit in 64 bits: #{data}"
22
- end
17
+ NumValidator.ensure_number(data)
23
18
  when Array
24
- data.each do |i|
25
- validate_data(i)
19
+ if data[0] == 'CHANGEDSINCE'
20
+ NumValidator.ensure_mod_sequence_value(data[1])
21
+ else
22
+ data.each do |i|
23
+ validate_data(i)
24
+ end
26
25
  end
27
26
  when Time, Date, DateTime
28
27
  when Symbol
29
- Flag.validate(data)
30
28
  else
31
29
  data.validate
32
30
  end
@@ -47,7 +45,7 @@ module Net
47
45
  when Date
48
46
  send_date_data(data)
49
47
  when Symbol
50
- Flag[data].send_data(self, tag)
48
+ send_symbol_data(data)
51
49
  else
52
50
  data.send_data(self, tag)
53
51
  end
@@ -79,31 +77,9 @@ module Net
79
77
  put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"')
80
78
  end
81
79
 
82
- def send_binary_literal(*a, **kw) send_literal(*a, **kw, binary: true) end
83
-
84
- # `non_sync` is an optional tri-state flag:
85
- # * `true` -> Force non-synchronizing `LITERAL+`/`LITERAL-` behavior.
86
- # NOTE: raises DataFormatError when server doesn't support
87
- # non-synchronizing literal, or literal is too large for LITERAL-.
88
- # * `false` -> Force normal synchronizing literal behavior.
89
- # * `nil` -> (default) Currently behaves like `false` (will be dynamic).
90
- # TODO: Dynamic, based on capabilities and bytesize.
91
- def send_literal(str, tag = nil, binary: false, non_sync: nil)
92
- bytesize = str.bytesize
80
+ def send_literal(str, tag = nil)
93
81
  synchronize do
94
- if non_sync && !non_sync_literal_allowed?(bytesize)
95
- # TODO: check in Printer, so we don't need to close the connection.
96
- @sock.close
97
- raise DataFormatError, "Connection closed: " \
98
- "Cannot send non-synchronizing literal without known server support"
99
- end
100
- prefix = "~" if binary
101
- plus = "+" if non_sync
102
- put_string("#{prefix}{#{bytesize}#{plus}}\r\n")
103
- if non_sync
104
- put_string(str)
105
- return
106
- end
82
+ put_string("{" + str.bytesize.to_s + "}" + CRLF)
107
83
  @continued_command_tag = tag
108
84
  @continuation_request_exception = nil
109
85
  begin
@@ -118,18 +94,8 @@ module Net
118
94
  end
119
95
  end
120
96
 
121
- def non_sync_literal_allowed?(bytesize)
122
- return unless capabilities_cached?
123
- return "+" if capable?("LITERAL+")
124
- return "-" if capable_literal_minus? && bytesize <= 4096
125
- false
126
- end
127
-
128
- def capable_literal_minus?; capable?("LITERAL-") || capable?("IMAP4rev2") end
129
-
130
- # NOTE: +num+ should already be an Integer
131
97
  def send_number_data(num)
132
- put_string(Integer(num).to_s)
98
+ put_string(num.to_s)
133
99
  end
134
100
 
135
101
  def send_list_data(list, tag = nil)
@@ -149,236 +115,71 @@ module Net
149
115
  def send_date_data(date) put_string Net::IMAP.encode_date(date) end
150
116
  def send_time_data(time) put_string Net::IMAP.encode_time(time) end
151
117
 
152
- # simplistic emulation of CommandData = Data.define(:data)
153
- class CommandData # :nodoc:
154
- class << self
155
- def new(arg = nil, data: arg) super(data: data) end
156
- alias :[] :new
157
- end
158
-
159
- def initialize(data:)
160
- @data = data
161
- freeze
162
- end
163
-
164
- attr_reader :data
165
-
166
- def to_h(&block) block ? to_h.to_h(&block) : { data: data } end
167
- def ==(other) self.class === other && to_h == other.to_h end
168
- def eql?(other) self.class === other && to_h.eql?(other.to_h) end
169
-
170
- # following class definition goes beyond the basic Data.define(:data)
171
- ##
172
-
173
- def self.validate(...)
174
- data = new(...)
175
- data.validate
176
- data
177
- end
118
+ def send_symbol_data(symbol)
119
+ put_string("\\" + symbol.to_s)
120
+ end
178
121
 
122
+ class RawData # :nodoc:
179
123
  def send_data(imap, tag)
180
- raise NoMethodError, "#{self.class} must implement #{__method__}"
124
+ imap.__send__(:put_string, @data)
181
125
  end
182
126
 
183
127
  def validate
184
128
  end
185
- end
186
129
 
187
- # Represents IMAP +text+ or +quoted+ data, which share the same
188
- # validations of decoded #data, and differ only in how they are formatted.
189
- #
190
- # +data+ may contain any 7-bit ASCII character except +NULL+, +CR+, or +LF+.
191
- # Any multibyte +UTF-8+ character is also allowed when the connection
192
- # supports UTF8: either +UTF8=ACCEPT+ or +IMAP4rev2+ have been enabled, or
193
- # the server supports only +IMAP4rev2+ and not earlier IMAP revisions, or
194
- # the server advertises +UTF8=ONLY+.
195
- #
196
- # NOTE: This does not verify whether the connection supports UTF-8, but that
197
- # may change in future versions.
198
- #
199
- # The string's bytes must be valid ASCII or valid UTF-8. The string's
200
- # reported encoding is ignored, but the string is _not_ transcoded.
201
- class ValidNonLiteralData < CommandData
202
- def initialize(data:)
203
- data = String(data.to_str)
204
- unless [Encoding::ASCII, Encoding::UTF_8].include?(data.encoding)
205
- data = data.dup.force_encoding(data.ascii_only? ? "ASCII" : "UTF-8")
206
- end
207
- data = -data
208
- super
209
- validate
210
- end
130
+ private
211
131
 
212
- def validate
213
- if ![Encoding::ASCII, Encoding::UTF_8].include?(data.encoding)
214
- raise DataFormatError, "must use ASCII or UTF-8 encoding"
215
- elsif !data.valid_encoding?
216
- raise DataFormatError, "invalid UTF-8 must be literal encoded"
217
- elsif data.include?("\0")
218
- raise DataFormatError, "NULL byte must be binary literal encoded"
219
- elsif /[\r\n]/.match?(data)
220
- raise DataFormatError, "CR and LF bytes must be literal encoded"
221
- end
132
+ def initialize(data)
133
+ @data = data
222
134
  end
223
-
224
- def ascii_only?; data.ascii_only? end
225
-
226
- def send_data(imap, tag = nil) imap.__send__(:put_string, formatted) end
227
- end
228
-
229
- # Represents IMAP +text+ data, which covers everything in the IMAP grammar,
230
- # except for +literal+, +literal8+, and the concluding +CRLF+.
231
- #
232
- # NOTE: The current implementation does not verify that the connection
233
- # supports UTF-8. Future versions may validate this.
234
- class RawText < ValidNonLiteralData # :nodoc:
235
- # raw: no formatting necessary
236
- alias formatted data
237
135
  end
238
136
 
239
- class RawData < CommandData # :nodoc:
240
- def initialize(data:)
241
- case data
242
- when String
243
- data = self.class.split(data)
244
- when Array
245
- unless data.all? { |part| RawText === part || Literal === part }
246
- raise TypeError, "expected String or Array[#{RawText} | #{Literal}]"
247
- end
248
- else
249
- raise TypeError, "expected String or Array[#{RawText} | #{Literal}]"
250
- end
251
- super
252
- validate
137
+ class Atom # :nodoc:
138
+ def send_data(imap, tag)
139
+ imap.__send__(:put_string, @data)
253
140
  end
254
141
 
255
- def send_data(imap, tag) data.each do _1.send_data(imap, tag) end end
256
-
257
142
  def validate
258
- return unless RawText === data.last
259
- text = data.last.data
260
- if text.rindex(/\{\d+\+?\}\z/n)
261
- raise DataFormatError, "RawData cannot end with literal continuation"
262
- end
263
143
  end
264
144
 
265
- # Splits an input +string+ into an array of RawText and Literal/Literal8.
266
- #
267
- # NOTE: unlike RawData#validate, this does not prevent the final RawText
268
- # from ending with a literal prefix.
269
- def self.split(data)
270
- data = data.b # dups and ensures BINARY encoding
271
- parts = []
272
- while data.match(/(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n)
273
- text, binary, bytesize, non_sync, data = $`, !!$1, $2, !!$3, $'
274
- bytesize = Integer bytesize, 10
275
- parts << RawText[text] unless text.empty?
276
- parts << extract_literal(data,
277
- binary: binary,
278
- bytesize: bytesize,
279
- non_sync: non_sync)
280
- data[0, bytesize] = ""
281
- end
282
- parts << RawText[data] unless data.empty?
283
- parts
284
- end
145
+ private
285
146
 
286
- def self.extract_literal(data, binary:, bytesize:, non_sync:)
287
- if data.bytesize < bytesize
288
- raise DataFormatError, "Too few bytes in string for literal, " \
289
- "expected: %s, remaining: %s" % [bytesize, data.bytesize]
290
- end
291
- literal = data.byteslice(0, bytesize)
292
- (binary ? Literal8 : Literal).new(data: literal, non_sync: non_sync)
147
+ def initialize(data)
148
+ @data = data
293
149
  end
294
- private_class_method :extract_literal
295
150
  end
296
151
 
297
- class Atom < CommandData # :nodoc:
298
- def initialize(**)
299
- super
300
- validate
152
+ class QuotedString # :nodoc:
153
+ def send_data(imap, tag)
154
+ imap.__send__(:send_quoted_string, @data)
301
155
  end
302
156
 
303
157
  def validate
304
- data.to_s.ascii_only? \
305
- or raise DataFormatError, "#{self.class} must be ASCII only"
306
- data.match?(ResponseParser::Patterns::ATOM_SPECIALS) \
307
- and raise DataFormatError, "#{self.class} must not contain atom-specials"
308
- data.empty? \
309
- and raise DataFormatError, "#{self.class} must not be empty"
310
158
  end
311
159
 
312
- def send_data(imap, tag)
313
- imap.__send__(:put_string, data.to_s)
314
- end
315
- end
160
+ private
316
161
 
317
- class Flag < Atom # :nodoc:
318
- def send_data(imap, tag)
319
- imap.__send__(:put_string, "\\#{data}")
162
+ def initialize(data)
163
+ @data = data
320
164
  end
321
165
  end
322
166
 
323
- # Represents a IMAP +quoted+ string, which can encode any valid ASCII or
324
- # UTF-8 string, unless it contains any +CR+, +LF+, or +NULL+ bytes.
325
- #
326
- # NOTE: The current implementation does not verify that the connection
327
- # supports UTF-8. Future versions may validate this.
328
- class QuotedString < ValidNonLiteralData # :nodoc:
329
- def formatted; %("#{data.gsub(/["\\]/, "\\\\\\&")}") end
330
- end
331
-
332
167
  class Literal # :nodoc:
333
- class << self
334
- def new(_data = nil, _non_sync = nil, data: _data, non_sync: _non_sync)
335
- super(data: data, non_sync: non_sync)
336
- end
337
- alias :[] :new
338
- end
339
-
340
- attr_reader :data, :non_sync
341
-
342
- def to_h(&block) block ? to_h.to_h(&block) : { data: data, non_sync: non_sync } end
343
- def ==(other) self.class === other && to_h == other.to_h end
344
- def eql?(other) self.class === other && to_h.eql?(other.to_h) end
345
-
346
- def initialize(data:, non_sync: nil)
347
- data = -String(data.to_str).b or
348
- raise DataFormatError, "#{self.class} expects string input"
349
- @data, @non_sync = data, non_sync
350
- validate
351
- freeze
352
- end
353
-
354
- def self.validate(...)
355
- data = new(...)
356
- data.validate
357
- data
168
+ def send_data(imap, tag)
169
+ imap.__send__(:send_literal, @data, tag)
358
170
  end
359
171
 
360
- def bytesize; data.bytesize end
361
-
362
172
  def validate
363
- if data.include?("\0")
364
- raise DataFormatError, "NULL byte not allowed in #{self.class}. " \
365
- "Use #{Literal8} or a null-safe encoding."
366
- end
367
173
  end
368
174
 
369
- def send_data(imap, tag)
370
- imap.__send__(:send_literal, data, tag, non_sync: non_sync)
371
- end
372
- end
373
-
374
- class Literal8 < Literal # :nodoc:
375
- def validate; nil end # all bytes are okay
175
+ private
376
176
 
377
- def send_data(imap, tag)
378
- imap.__send__(:send_binary_literal, data, tag, non_sync: non_sync)
177
+ def initialize(data)
178
+ @data = data
379
179
  end
380
180
  end
381
181
 
182
+ # *DEPRECATED*. Replaced by SequenceSet.
382
183
  class MessageSet # :nodoc:
383
184
  def send_data(imap, tag)
384
185
  imap.__send__(:put_string, format_internal(@data))
@@ -392,6 +193,16 @@ module Net
392
193
 
393
194
  def initialize(data)
394
195
  @data = data
196
+ warn("DEPRECATED: #{MessageSet} should be replaced with #{SequenceSet}.",
197
+ uplevel: 1, category: :deprecated)
198
+ begin
199
+ # to ensure the input works with SequenceSet, too
200
+ SequenceSet.new(data)
201
+ rescue
202
+ warn "MessageSet input is incompatible with SequenceSet: [%s] %s" % [
203
+ $!.class, $!.message
204
+ ]
205
+ end
395
206
  end
396
207
 
397
208
  def format_internal(data)
@@ -18,8 +18,6 @@ module Net
18
18
  super(attr)
19
19
  AttrTypeCoercion.attr_accessor(attr, type: type)
20
20
  end
21
-
22
- module_function def Integer?; NilOrInteger end
23
21
  end
24
22
  private_constant :Macros
25
23
 
@@ -28,33 +26,34 @@ module Net
28
26
  end
29
27
  private_class_method :included
30
28
 
31
- # Used in v0.5.8+ for Ractor sharability.
32
- def self.safe(...) nil.instance_eval(...).freeze end
33
- private_class_method :safe
34
-
35
- Types = Hash.new do |h, type|
36
- type.nil? || Proc === type or raise TypeError, "type not nil or Proc"
37
- safe{type}
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
38
36
  end
39
- Types[:boolean] = Boolean = safe{-> {!!_1}}
40
- Types[Integer] = safe{->{Integer(_1)}}
41
37
 
42
- def self.attr_accessor(attr, type: nil)
43
- type = Types[type] or return
44
- define_method :"#{attr}=" do |val| super type[val] end
45
- define_method :"#{attr}?" do send attr end if type == Boolean
38
+ def self.boolean(attr)
39
+ define_method :"#{attr}=" do |val| super !!val end
40
+ define_method :"#{attr}?" do send attr end
46
41
  end
47
42
 
48
- NilOrInteger = safe{->val { Integer val unless val.nil? }}
43
+ def self.integer(attr)
44
+ define_method :"#{attr}=" do |val| super Integer val end
45
+ end
49
46
 
50
- Enum = ->(*enum) {
51
- enum = safe{enum}
47
+ def self.enum(attr, enum)
48
+ enum = enum.dup.freeze
52
49
  expected = -"one of #{enum.map(&:inspect).join(", ")}"
53
- safe{->val {
54
- return val if enum.include?(val)
55
- raise ArgumentError, "expected %s, got %p" % [expected, val]
56
- }}
57
- }
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
58
57
 
59
58
  end
60
59
  end