net-imap 0.5.15 → 0.6.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: '0917528437aff2a931fa3b40d476ffbad713cf46655163dc21a6a7a2333f7a49'
4
- data.tar.gz: 90b3bf050a4f403408e30eecfaf449069e55bc293297b482e627201821997e0c
3
+ metadata.gz: 561e6151a9540c8eaa6c12fd16d625061843a053fb0b0c8a2f215eb1a518ef70
4
+ data.tar.gz: 5be61c15097d2e0007822fc6c4825f59795d6078d6cf9b5cbe6ed4dd2bd1d22e
5
5
  SHA512:
6
- metadata.gz: c2aaf64941ac0537b7ba6da23ea501dacf438d9eb99581a2ca9906ad150bf5f55a7702141cb573e38306e3eb16000388acb4eb0dbc478727488bc21fa1c61288
7
- data.tar.gz: 9d6b93b6c963e4e9c226044e1d8f9c4c5e09365ab57beaf1157128f85cc511402927d50033b0ad154b75914cf7191f2ec111dd240c566907203e4263a24f5e49
6
+ metadata.gz: f84a8adf89f177e953fe57e390b027232bc4c7325ecb0049221a88db87fccc76a09281aea87c4f48de05bc547db1248b628b9c271134a466aedba953305001cc
7
+ data.tar.gz: 0b82898fb8581bab9a30c5b3edb96c89d81c4bddf197ade29a25ef166db827983a6c2c25d1eda9989a9f985cfcbec52f4bf484dbc1b57cfde32f432dcd0dfbc3
data/Gemfile CHANGED
@@ -7,6 +7,7 @@ gemspec
7
7
  # gem "digest" # not included as a workaround for #576
8
8
  gem "strscan"
9
9
  gem "base64"
10
+ gem "psych", ">= 5.3.0" # 5.2.5 for Data serialization, 5.3.0 for TruffleRuby
10
11
 
11
12
  gem "irb"
12
13
  gem "rake"
@@ -3,9 +3,6 @@
3
3
  require "date"
4
4
 
5
5
  require_relative "errors"
6
- require_relative "data_lite"
7
-
8
- # :enddoc:
9
6
 
10
7
  module Net
11
8
  class IMAP < Protocol
@@ -17,19 +14,17 @@ module Net
17
14
  when nil
18
15
  when String
19
16
  when Integer
20
- # Covers modseq-valzer, which is the largest valid IMAP integer
21
- if data.negative?
22
- raise DataFormatError, "Integer argument must be unsigned: #{data}"
23
- elsif 0xffff_ffff_ffff_ffff < data
24
- raise DataFormatError, "Integer argument must fit in 64 bits: #{data}"
25
- end
17
+ NumValidator.ensure_number(data)
26
18
  when Array
27
- data.each do |i|
28
- 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
29
25
  end
30
26
  when Time, Date, DateTime
31
27
  when Symbol
32
- Flag.validate(data)
33
28
  else
34
29
  data.validate
35
30
  end
@@ -50,7 +45,7 @@ module Net
50
45
  when Date
51
46
  send_date_data(data)
52
47
  when Symbol
53
- Flag[data].send_data(self, tag)
48
+ send_symbol_data(data)
54
49
  else
55
50
  data.send_data(self, tag)
56
51
  end
@@ -82,31 +77,9 @@ module Net
82
77
  put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"')
83
78
  end
84
79
 
85
- def send_binary_literal(*a, **kw); send_literal(*a, **kw, binary: true) end
86
-
87
- # `non_sync` is an optional tri-state flag:
88
- # * `true` -> Force non-synchronizing `LITERAL+`/`LITERAL-` behavior.
89
- # NOTE: raises DataFormatError when server doesn't support
90
- # non-synchronizing literal, or literal is too large for LITERAL-.
91
- # * `false` -> Force normal synchronizing literal behavior.
92
- # * `nil` -> (default) Currently behaves like `false` (will be dynamic).
93
- # TODO: Dynamic, based on capabilities and bytesize.
94
- def send_literal(str, tag = nil, binary: false, non_sync: nil)
95
- bytesize = str.bytesize
80
+ def send_literal(str, tag = nil)
96
81
  synchronize do
97
- if non_sync && !non_sync_literal_allowed?(bytesize)
98
- # TODO: check in Printer, so we don't need to close the connection.
99
- @sock.close
100
- raise DataFormatError, "Connection closed: " \
101
- "Cannot send non-synchronizing literal without known server support"
102
- end
103
- prefix = "~" if binary
104
- plus = "+" if non_sync
105
- put_string("#{prefix}{#{bytesize}#{plus}}\r\n")
106
- if non_sync
107
- put_string(str)
108
- return
109
- end
82
+ put_string("{" + str.bytesize.to_s + "}" + CRLF)
110
83
  @continued_command_tag = tag
111
84
  @continuation_request_exception = nil
112
85
  begin
@@ -121,18 +94,8 @@ module Net
121
94
  end
122
95
  end
123
96
 
124
- def non_sync_literal_allowed?(bytesize)
125
- return unless capabilities_cached?
126
- return "+" if capable?("LITERAL+")
127
- return "-" if capable_literal_minus? && bytesize <= 4096
128
- false
129
- end
130
-
131
- def capable_literal_minus? = capable?("LITERAL-") || capable?("IMAP4rev2")
132
-
133
- # NOTE: +num+ should already be an Integer
134
97
  def send_number_data(num)
135
- put_string(Integer(num).to_s)
98
+ put_string(num.to_s)
136
99
  end
137
100
 
138
101
  def send_list_data(list, tag = nil)
@@ -152,13 +115,11 @@ module Net
152
115
  def send_date_data(date) put_string Net::IMAP.encode_date(date) end
153
116
  def send_time_data(time) put_string Net::IMAP.encode_time(time) end
154
117
 
155
- CommandData = Data.define(:data) do # :nodoc:
156
- def self.validate(...)
157
- data = new(...)
158
- data.validate
159
- data
160
- end
118
+ def send_symbol_data(symbol)
119
+ put_string("\\" + symbol.to_s)
120
+ end
161
121
 
122
+ CommandData = Data.define(:data) do # :nodoc:
162
123
  def send_data(imap, tag)
163
124
  raise NoMethodError, "#{self.class} must implement #{__method__}"
164
125
  end
@@ -167,176 +128,27 @@ module Net
167
128
  end
168
129
  end
169
130
 
170
- # Represents IMAP +text+ or +quoted+ data, which share the same
171
- # validations of decoded #data, and differ only in how they are formatted.
172
- #
173
- # +data+ may contain any 7-bit ASCII character except +NULL+, +CR+, or +LF+.
174
- # Any multibyte +UTF-8+ character is also allowed when the connection
175
- # supports UTF8: either +UTF8=ACCEPT+ or +IMAP4rev2+ have been enabled, or
176
- # the server supports only +IMAP4rev2+ and not earlier IMAP revisions, or
177
- # the server advertises +UTF8=ONLY+.
178
- #
179
- # NOTE: This does not verify whether the connection supports UTF-8, but that
180
- # may change in future versions.
181
- #
182
- # The string's bytes must be valid ASCII or valid UTF-8. The string's
183
- # reported encoding is ignored, but the string is _not_ transcoded.
184
- class ValidNonLiteralData < CommandData
185
- def initialize(data:)
186
- data = String(data.to_str)
187
- unless data.encoding in Encoding::ASCII | Encoding::UTF_8
188
- data = data.dup.force_encoding(data.ascii_only? ? "ASCII" : "UTF-8")
189
- end
190
- data = -data
191
- super
192
- validate
193
- end
194
-
195
- def validate
196
- if !(data.encoding in Encoding::ASCII | Encoding::UTF_8)
197
- raise DataFormatError, "must use ASCII or UTF-8 encoding"
198
- elsif !data.valid_encoding?
199
- raise DataFormatError, "invalid UTF-8 must be literal encoded"
200
- elsif data.include?("\0")
201
- raise DataFormatError, "NULL byte must be binary literal encoded"
202
- elsif /[\r\n]/.match?(data)
203
- raise DataFormatError, "CR and LF bytes must be literal encoded"
204
- end
205
- end
206
-
207
- def ascii_only? = data.ascii_only?
208
-
209
- def send_data(imap, tag = nil) = imap.__send__(:put_string, formatted)
210
- end
211
-
212
- # Represents IMAP +text+ data, which covers everything in the IMAP grammar,
213
- # except for +literal+, +literal8+, and the concluding +CRLF+.
214
- #
215
- # NOTE: The current implementation does not verify that the connection
216
- # supports UTF-8. Future versions may validate this.
217
- class RawText < ValidNonLiteralData # :nodoc:
218
- # raw: no formatting necessary
219
- alias formatted data
220
- end
221
-
222
131
  class RawData < CommandData # :nodoc:
223
- def initialize(data:)
224
- case data
225
- in String then data = self.class.split(data)
226
- in Array if data.all? { _1 in RawText | Literal }
227
- else
228
- raise TypeError, "expected String or Array[#{RawText} | #{Literal}]"
229
- end
230
- super
231
- validate
232
- end
233
-
234
- def send_data(imap, tag) = data.each do _1.send_data(imap, tag) end
235
-
236
- def validate
237
- return unless data.last in RawText(data: text)
238
- if text.rindex(/\{\d+\+?\}\z/n)
239
- raise DataFormatError, "RawData cannot end with literal continuation"
240
- end
241
- end
242
-
243
- # Splits an input +string+ into an array of RawText and Literal/Literal8.
244
- #
245
- # NOTE: unlike RawData#validate, this does not prevent the final RawText
246
- # from ending with a literal prefix.
247
- def self.split(data)
248
- data = data.b # dups and ensures BINARY encoding
249
- parts = []
250
- while data.match(/(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n)
251
- text, binary, bytesize, non_sync, data = $`, !!$1, $2, !!$3, $'
252
- bytesize = Integer bytesize, 10
253
- parts << RawText[text] unless text.empty?
254
- parts << extract_literal(data, binary:, bytesize:, non_sync:)
255
- data[0, bytesize] = ""
256
- end
257
- parts << RawText[data] unless data.empty?
258
- parts
259
- end
260
-
261
- def self.extract_literal(data, binary:, bytesize:, non_sync:)
262
- if data.bytesize < bytesize
263
- raise DataFormatError, "Too few bytes in string for literal, " \
264
- "expected: %s, remaining: %s" % [bytesize, data.bytesize]
265
- end
266
- literal = data.byteslice(0, bytesize)
267
- (binary ? Literal8 : Literal).new(data: literal, non_sync:)
268
- end
269
- private_class_method :extract_literal
270
- end
271
-
272
- class Atom < CommandData # :nodoc:
273
- def initialize(**)
274
- super
275
- validate
276
- end
277
-
278
- def validate
279
- data.to_s.ascii_only? \
280
- or raise DataFormatError, "#{self.class} must be ASCII only"
281
- data.match?(ResponseParser::Patterns::ATOM_SPECIALS) \
282
- and raise DataFormatError, "#{self.class} must not contain atom-specials"
283
- data.empty? \
284
- and raise DataFormatError, "#{self.class} must not be empty"
285
- end
286
-
287
132
  def send_data(imap, tag)
288
- imap.__send__(:put_string, data.to_s)
133
+ imap.__send__(:put_string, data)
289
134
  end
290
135
  end
291
136
 
292
- class Flag < Atom # :nodoc:
137
+ class Atom < CommandData # :nodoc:
293
138
  def send_data(imap, tag)
294
- imap.__send__(:put_string, "\\#{data}")
139
+ imap.__send__(:put_string, data)
295
140
  end
296
141
  end
297
142
 
298
- # Represents a IMAP +quoted+ string, which can encode any valid ASCII or
299
- # UTF-8 string, unless it contains any +CR+, +LF+, or +NULL+ bytes.
300
- #
301
- # NOTE: The current implementation does not verify that the connection
302
- # supports UTF-8. Future versions may validate this.
303
- class QuotedString < ValidNonLiteralData # :nodoc:
304
- def formatted = %("#{data.gsub(/["\\]/, "\\\\\\&")}")
305
- end
306
-
307
- class Literal < Data.define(:data, :non_sync) # :nodoc:
308
- def self.validate(...)
309
- data = new(...)
310
- data.validate
311
- data
312
- end
313
-
314
- def initialize(data:, non_sync: nil)
315
- data = -String(data.to_str).b or
316
- raise DataFormatError, "#{self.class} expects string input"
317
- super
318
- validate
319
- end
320
-
321
- def bytesize; data.bytesize end
322
-
323
- def validate
324
- if data.include?("\0")
325
- raise DataFormatError, "NULL byte not allowed in #{self.class}. " \
326
- "Use #{Literal8} or a null-safe encoding."
327
- end
328
- end
329
-
143
+ class QuotedString < CommandData # :nodoc:
330
144
  def send_data(imap, tag)
331
- imap.__send__(:send_literal, data, tag, non_sync:)
145
+ imap.__send__(:send_quoted_string, data)
332
146
  end
333
147
  end
334
148
 
335
- class Literal8 < Literal # :nodoc:
336
- def validate; nil end # all bytes are okay
337
-
149
+ class Literal < CommandData # :nodoc:
338
150
  def send_data(imap, tag)
339
- imap.__send__(:send_binary_literal, data, tag, non_sync:)
151
+ imap.__send__(:send_literal, data, tag)
340
152
  end
341
153
  end
342
154
 
@@ -372,73 +184,6 @@ module Net
372
184
  end
373
185
  end
374
186
 
375
- # *DEPRECATED*. Replaced by SequenceSet.
376
- class MessageSet < CommandData # :nodoc:
377
- def send_data(imap, tag)
378
- imap.__send__(:put_string, format_internal(data))
379
- end
380
-
381
- def validate
382
- validate_internal(data)
383
- end
384
-
385
- private
386
-
387
- def initialize(data:)
388
- super
389
- warn("DEPRECATED: #{MessageSet} should be replaced with #{SequenceSet}.",
390
- uplevel: 1, category: :deprecated)
391
- begin
392
- # to ensure the input works with SequenceSet, too
393
- SequenceSet.new(data)
394
- rescue
395
- warn "MessageSet input is incompatible with SequenceSet: [%s] %s" % [
396
- $!.class, $!.message
397
- ]
398
- end
399
- end
400
-
401
- def format_internal(data)
402
- case data
403
- when "*"
404
- return data
405
- when Integer
406
- if data == -1
407
- return "*"
408
- else
409
- return data.to_s
410
- end
411
- when Range
412
- return format_internal(data.first) +
413
- ":" + format_internal(data.last)
414
- when Array
415
- return data.collect {|i| format_internal(i)}.join(",")
416
- when ThreadMember
417
- return data.seqno.to_s +
418
- ":" + data.children.collect {|i| format_internal(i).join(",")}
419
- end
420
- end
421
-
422
- def validate_internal(data)
423
- case data
424
- when "*"
425
- when Integer
426
- NumValidator.ensure_nz_number(data)
427
- when Range
428
- when Array
429
- data.each do |i|
430
- validate_internal(i)
431
- end
432
- when ThreadMember
433
- data.children.each do |i|
434
- validate_internal(i)
435
- end
436
- else
437
- raise DataFormatError, data.inspect
438
- end
439
- end
440
- end
441
-
442
187
  class ClientID < CommandData # :nodoc:
443
188
 
444
189
  def send_data(imap, tag)
@@ -54,9 +54,22 @@ module Net
54
54
  # Creates a new config, which inherits from +self+.
55
55
  def new(**attrs) self.class.new(self, **attrs) end
56
56
 
57
+ # :call-seq:
58
+ # inherited?(attr) -> true or false
59
+ # inherited?(*attrs) -> true or false
60
+ # inherited? -> true or false
61
+ #
57
62
  # Returns +true+ if +attr+ is inherited from #parent and not overridden
58
63
  # by this config.
59
- def inherited?(attr) data[attr] == INHERITED end
64
+ #
65
+ # When multiple +attrs+ are given, returns +true+ if *all* of them are
66
+ # inherited, or +false+ if any of them are overriden. When no +attrs+
67
+ # are given, returns +true+ if *all* attributes are inherited, or
68
+ # +false+ if any attribute is overriden.
69
+ def inherited?(*attrs)
70
+ attrs = data.members if attrs.empty?
71
+ attrs.all? { data[_1] == INHERITED }
72
+ end
60
73
 
61
74
  # :call-seq:
62
75
  # reset -> self
@@ -24,7 +24,7 @@ module Net
24
24
  VERSIONS = ((0.0r..FUTURE_VERSION) % 0.1r).to_a.freeze
25
25
 
26
26
  # See Config.version_defaults.
27
- singleton_class.attr_reader :version_defaults
27
+ singleton_class.attr_accessor :version_defaults
28
28
 
29
29
  @version_defaults = Hash.new {|h, k|
30
30
  # NOTE: String responds to both so the order is significant.