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 +4 -4
- data/Gemfile +1 -0
- data/lib/net/imap/command_data.rb +22 -277
- data/lib/net/imap/config/attr_inheritance.rb +14 -1
- data/lib/net/imap/config/attr_version_defaults.rb +1 -1
- data/lib/net/imap/config.rb +175 -35
- data/lib/net/imap/connection_state.rb +1 -1
- data/lib/net/imap/data_encoding.rb +77 -28
- data/lib/net/imap/esearch_result.rb +6 -0
- data/lib/net/imap/response_data.rb +5 -23
- data/lib/net/imap/response_parser.rb +20 -33
- data/lib/net/imap/response_reader.rb +5 -24
- data/lib/net/imap/sasl/scram_authenticator.rb +0 -74
- data/lib/net/imap/search_result.rb +6 -0
- data/lib/net/imap/sequence_set.rb +622 -327
- data/lib/net/imap/uidplus_data.rb +2 -63
- data/lib/net/imap.rb +63 -161
- data/net-imap.gemspec +1 -1
- metadata +3 -4
- data/lib/net/imap/data_lite.rb +0 -226
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 561e6151a9540c8eaa6c12fd16d625061843a053fb0b0c8a2f215eb1a518ef70
|
|
4
|
+
data.tar.gz: 5be61c15097d2e0007822fc6c4825f59795d6078d6cf9b5cbe6ed4dd2bd1d22e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f84a8adf89f177e953fe57e390b027232bc4c7325ecb0049221a88db87fccc76a09281aea87c4f48de05bc547db1248b628b9c271134a466aedba953305001cc
|
|
7
|
+
data.tar.gz: 0b82898fb8581bab9a30c5b3edb96c89d81c4bddf197ade29a25ef166db827983a6c2c25d1eda9989a9f985cfcbec52f4bf484dbc1b57cfde32f432dcd0dfbc3
|
data/Gemfile
CHANGED
|
@@ -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
|
-
|
|
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
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
133
|
+
imap.__send__(:put_string, data)
|
|
289
134
|
end
|
|
290
135
|
end
|
|
291
136
|
|
|
292
|
-
class
|
|
137
|
+
class Atom < CommandData # :nodoc:
|
|
293
138
|
def send_data(imap, tag)
|
|
294
|
-
imap.__send__(:put_string,
|
|
139
|
+
imap.__send__(:put_string, data)
|
|
295
140
|
end
|
|
296
141
|
end
|
|
297
142
|
|
|
298
|
-
|
|
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__(:
|
|
145
|
+
imap.__send__(:send_quoted_string, data)
|
|
332
146
|
end
|
|
333
147
|
end
|
|
334
148
|
|
|
335
|
-
class
|
|
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__(:
|
|
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
|
-
|
|
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.
|
|
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.
|