net-imap 0.5.6 → 0.5.7
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/lib/net/imap/config/attr_type_coercion.rb +20 -23
- data/lib/net/imap/config.rb +101 -19
- data/lib/net/imap/connection_state.rb +48 -0
- data/lib/net/imap/errors.rb +33 -0
- data/lib/net/imap/response_reader.rb +73 -0
- data/lib/net/imap/sequence_set.rb +74 -60
- data/lib/net/imap.rb +218 -47
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee48ac78f129043fd8ccb4f6e0b42535cc0ef3a6acd8ea1067cbd9b512815e49
|
4
|
+
data.tar.gz: 5bfae3bf0ee2e63c5b61c19d30187fe722adfe8018746e64d583124531de87a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f90a4500f3c218dd3a51cca84a76c620fa8f8488f2ca73486b538a43e67e21e0b897d23d957f490afe1590fd755c692f2e0dede562460ed2cfcd7f4d9bec3262
|
7
|
+
data.tar.gz: 22abb3cf5699bb715c33c69b596ead46ff400109925f58d633ecdc82797be9b80fbbd040c02aa7cd9286f3aa9c697e9fc426f1115b9019455b2ac4e6667cc236
|
@@ -18,6 +18,8 @@ module Net
|
|
18
18
|
super(attr)
|
19
19
|
AttrTypeCoercion.attr_accessor(attr, type: type)
|
20
20
|
end
|
21
|
+
|
22
|
+
module_function def Integer? = NilOrInteger
|
21
23
|
end
|
22
24
|
private_constant :Macros
|
23
25
|
|
@@ -26,34 +28,29 @@ module Net
|
|
26
28
|
end
|
27
29
|
private_class_method :included
|
28
30
|
|
29
|
-
def self.
|
30
|
-
|
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
|
36
|
-
end
|
31
|
+
def self.safe(...) = Ractor.make_shareable nil.instance_eval(...).freeze
|
32
|
+
private_class_method :safe
|
37
33
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
34
|
+
Types = Hash.new do |h, type| type => Proc | nil; safe{type} end
|
35
|
+
Types[:boolean] = Boolean = safe{-> {!!_1}}
|
36
|
+
Types[Integer] = safe{->{Integer(_1)}}
|
42
37
|
|
43
|
-
def self.
|
44
|
-
|
38
|
+
def self.attr_accessor(attr, type: nil)
|
39
|
+
type = Types[type] or return
|
40
|
+
define_method :"#{attr}=" do |val| super type[val] end
|
41
|
+
define_method :"#{attr}?" do send attr end if type == Boolean
|
45
42
|
end
|
46
43
|
|
47
|
-
|
48
|
-
|
44
|
+
NilOrInteger = safe{->val { Integer val unless val.nil? }}
|
45
|
+
|
46
|
+
Enum = ->(*enum) {
|
47
|
+
enum = safe{enum}
|
49
48
|
expected = -"one of #{enum.map(&:inspect).join(", ")}"
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
56
|
-
end
|
49
|
+
safe{->val {
|
50
|
+
return val if enum.include?(val)
|
51
|
+
raise ArgumentError, "expected %s, got %p" % [expected, val]
|
52
|
+
}}
|
53
|
+
}
|
57
54
|
|
58
55
|
end
|
59
56
|
end
|
data/lib/net/imap/config.rb
CHANGED
@@ -131,8 +131,25 @@ module Net
|
|
131
131
|
def self.global; @global if defined?(@global) end
|
132
132
|
|
133
133
|
# A hash of hard-coded configurations, indexed by version number or name.
|
134
|
+
# Values can be accessed with any object that responds to +to_sym+ or
|
135
|
+
# +to_r+/+to_f+ with a non-zero number.
|
136
|
+
#
|
137
|
+
# Config::[] gets named or numbered versions from this hash.
|
138
|
+
#
|
139
|
+
# For example:
|
140
|
+
# Net::IMAP::Config.version_defaults[0.5] == Net::IMAP::Config[0.5]
|
141
|
+
# Net::IMAP::Config[0.5] == Net::IMAP::Config[0.5r] # => true
|
142
|
+
# Net::IMAP::Config["current"] == Net::IMAP::Config[:current] # => true
|
143
|
+
# Net::IMAP::Config["0.5.6"] == Net::IMAP::Config[0.5r] # => true
|
134
144
|
def self.version_defaults; @version_defaults end
|
135
|
-
@version_defaults = {
|
145
|
+
@version_defaults = Hash.new {|h, k|
|
146
|
+
# NOTE: String responds to both so the order is significant.
|
147
|
+
# And ignore non-numeric conversion to zero, because: "wat!?".to_r == 0
|
148
|
+
(h.fetch(k.to_r, nil) || h.fetch(k.to_f, nil) if k.is_a?(Numeric)) ||
|
149
|
+
(h.fetch(k.to_sym, nil) if k.respond_to?(:to_sym)) ||
|
150
|
+
(h.fetch(k.to_r, nil) if k.respond_to?(:to_r) && k.to_r != 0r) ||
|
151
|
+
(h.fetch(k.to_f, nil) if k.respond_to?(:to_f) && k.to_f != 0.0)
|
152
|
+
}
|
136
153
|
|
137
154
|
# :call-seq:
|
138
155
|
# Net::IMAP::Config[number] -> versioned config
|
@@ -155,18 +172,17 @@ module Net
|
|
155
172
|
elsif config.nil? && global.nil? then nil
|
156
173
|
elsif config.respond_to?(:to_hash) then new(global, **config).freeze
|
157
174
|
else
|
158
|
-
version_defaults
|
175
|
+
version_defaults[config] or
|
159
176
|
case config
|
160
177
|
when Numeric
|
161
178
|
raise RangeError, "unknown config version: %p" % [config]
|
162
|
-
when Symbol
|
179
|
+
when String, Symbol
|
163
180
|
raise KeyError, "unknown config name: %p" % [config]
|
164
181
|
else
|
165
182
|
raise TypeError, "no implicit conversion of %s to %s" % [
|
166
183
|
config.class, Config
|
167
184
|
]
|
168
185
|
end
|
169
|
-
end
|
170
186
|
end
|
171
187
|
end
|
172
188
|
|
@@ -193,10 +209,13 @@ module Net
|
|
193
209
|
|
194
210
|
# Seconds to wait until a connection is opened.
|
195
211
|
#
|
212
|
+
# Applied separately for establishing TCP connection and starting a TLS
|
213
|
+
# connection.
|
214
|
+
#
|
196
215
|
# If the IMAP object cannot open a connection within this time,
|
197
216
|
# it raises a Net::OpenTimeout exception.
|
198
217
|
#
|
199
|
-
# See Net::IMAP.new.
|
218
|
+
# See Net::IMAP.new and Net::IMAP#starttls.
|
200
219
|
#
|
201
220
|
# The default value is +30+ seconds.
|
202
221
|
attr_accessor :open_timeout, type: Integer
|
@@ -245,10 +264,44 @@ module Net
|
|
245
264
|
# present. When capabilities are unknown, Net::IMAP will automatically
|
246
265
|
# send a +CAPABILITY+ command first before sending +LOGIN+.
|
247
266
|
#
|
248
|
-
attr_accessor :enforce_logindisabled, type: [
|
267
|
+
attr_accessor :enforce_logindisabled, type: Enum[
|
249
268
|
false, :when_capabilities_cached, true
|
250
269
|
]
|
251
270
|
|
271
|
+
# The maximum allowed server response size. When +nil+, there is no limit
|
272
|
+
# on response size.
|
273
|
+
#
|
274
|
+
# The default value (512 MiB, since +v0.5.7+) is <em>very high</em> and
|
275
|
+
# unlikely to be reached. A _much_ lower value should be used with
|
276
|
+
# untrusted servers (for example, when connecting to a user-provided
|
277
|
+
# hostname). When using a lower limit, message bodies should be fetched
|
278
|
+
# in chunks rather than all at once.
|
279
|
+
#
|
280
|
+
# <em>Please Note:</em> this only limits the size per response. It does
|
281
|
+
# not prevent a flood of individual responses and it does not limit how
|
282
|
+
# many unhandled responses may be stored on the responses hash. See
|
283
|
+
# Net::IMAP@Unbounded+memory+use.
|
284
|
+
#
|
285
|
+
# Socket reads are limited to the maximum remaining bytes for the current
|
286
|
+
# response: max_response_size minus the bytes that have already been read.
|
287
|
+
# When the limit is reached, or reading a +literal+ _would_ go over the
|
288
|
+
# limit, ResponseTooLargeError is raised and the connection is closed.
|
289
|
+
#
|
290
|
+
# Note that changes will not take effect immediately, because the receiver
|
291
|
+
# thread may already be waiting for the next response using the previous
|
292
|
+
# value. Net::IMAP#noop can force a response and enforce the new setting
|
293
|
+
# immediately.
|
294
|
+
#
|
295
|
+
# ==== Versioned Defaults
|
296
|
+
#
|
297
|
+
# Net::IMAP#max_response_size <em>was added in +v0.2.5+ and +v0.3.9+ as an
|
298
|
+
# attr_accessor, and in +v0.4.20+ and +v0.5.7+ as a delegator to this
|
299
|
+
# config attribute.</em>
|
300
|
+
#
|
301
|
+
# * original: +nil+ <em>(no limit)</em>
|
302
|
+
# * +0.5+: 512 MiB
|
303
|
+
attr_accessor :max_response_size, type: Integer?
|
304
|
+
|
252
305
|
# Controls the behavior of Net::IMAP#responses when called without any
|
253
306
|
# arguments (+type+ or +block+).
|
254
307
|
#
|
@@ -275,7 +328,7 @@ module Net
|
|
275
328
|
# Raise an ArgumentError with the deprecation warning.
|
276
329
|
#
|
277
330
|
# Note: #responses_without_args is an alias for #responses_without_block.
|
278
|
-
attr_accessor :responses_without_block, type: [
|
331
|
+
attr_accessor :responses_without_block, type: Enum[
|
279
332
|
:silence_deprecation_warning, :warn, :frozen_dup, :raise,
|
280
333
|
]
|
281
334
|
|
@@ -320,7 +373,7 @@ module Net
|
|
320
373
|
#
|
321
374
|
# [+false+ <em>(planned default for +v0.6+)</em>]
|
322
375
|
# ResponseParser _only_ uses AppendUIDData and CopyUIDData.
|
323
|
-
attr_accessor :parser_use_deprecated_uidplus_data, type: [
|
376
|
+
attr_accessor :parser_use_deprecated_uidplus_data, type: Enum[
|
324
377
|
true, :up_to_max_size, false
|
325
378
|
]
|
326
379
|
|
@@ -427,6 +480,7 @@ module Net
|
|
427
480
|
idle_response_timeout: 5,
|
428
481
|
sasl_ir: true,
|
429
482
|
enforce_logindisabled: true,
|
483
|
+
max_response_size: 512 << 20, # 512 MiB
|
430
484
|
responses_without_block: :warn,
|
431
485
|
parser_use_deprecated_uidplus_data: :up_to_max_size,
|
432
486
|
parser_max_deprecated_uidplus_data_size: 100,
|
@@ -435,36 +489,64 @@ module Net
|
|
435
489
|
@global = default.new
|
436
490
|
|
437
491
|
version_defaults[:default] = Config[default.send(:defaults_hash)]
|
438
|
-
version_defaults[:current] = Config[:default]
|
439
492
|
|
440
|
-
version_defaults[
|
493
|
+
version_defaults[0r] = Config[:default].dup.update(
|
441
494
|
sasl_ir: false,
|
442
495
|
responses_without_block: :silence_deprecation_warning,
|
443
496
|
enforce_logindisabled: false,
|
497
|
+
max_response_size: nil,
|
444
498
|
parser_use_deprecated_uidplus_data: true,
|
445
499
|
parser_max_deprecated_uidplus_data_size: 10_000,
|
446
500
|
).freeze
|
447
|
-
version_defaults[0.
|
448
|
-
version_defaults[0.
|
449
|
-
version_defaults[0.
|
450
|
-
version_defaults[0.
|
501
|
+
version_defaults[0.0r] = Config[0r]
|
502
|
+
version_defaults[0.1r] = Config[0r]
|
503
|
+
version_defaults[0.2r] = Config[0r]
|
504
|
+
version_defaults[0.3r] = Config[0r]
|
451
505
|
|
452
|
-
version_defaults[0.
|
506
|
+
version_defaults[0.4r] = Config[0.3r].dup.update(
|
453
507
|
sasl_ir: true,
|
454
508
|
parser_max_deprecated_uidplus_data_size: 1000,
|
455
509
|
).freeze
|
456
510
|
|
457
|
-
version_defaults[0.
|
511
|
+
version_defaults[0.5r] = Config[0.4r].dup.update(
|
512
|
+
enforce_logindisabled: true,
|
513
|
+
max_response_size: 512 << 20, # 512 MiB
|
514
|
+
responses_without_block: :warn,
|
515
|
+
parser_use_deprecated_uidplus_data: :up_to_max_size,
|
516
|
+
parser_max_deprecated_uidplus_data_size: 100,
|
517
|
+
).freeze
|
458
518
|
|
459
|
-
version_defaults[0.
|
519
|
+
version_defaults[0.6r] = Config[0.5r].dup.update(
|
460
520
|
responses_without_block: :frozen_dup,
|
461
521
|
parser_use_deprecated_uidplus_data: false,
|
462
522
|
parser_max_deprecated_uidplus_data_size: 0,
|
463
523
|
).freeze
|
464
|
-
|
465
|
-
version_defaults[
|
524
|
+
|
525
|
+
version_defaults[0.7r] = Config[0.6r].dup.update(
|
526
|
+
).freeze
|
527
|
+
|
528
|
+
# Safe conversions one way only:
|
529
|
+
# 0.6r.to_f == 0.6 # => true
|
530
|
+
# 0.6 .to_r == 0.6r # => false
|
531
|
+
version_defaults.to_a.each do |k, v|
|
532
|
+
next unless k in Rational
|
533
|
+
version_defaults[k.to_f] = v
|
534
|
+
end
|
535
|
+
|
536
|
+
current = VERSION.to_r
|
537
|
+
version_defaults[:original] = Config[0]
|
538
|
+
version_defaults[:current] = Config[current]
|
539
|
+
version_defaults[:next] = Config[current + 0.1r]
|
540
|
+
version_defaults[:future] = Config[current + 0.2r]
|
466
541
|
|
467
542
|
version_defaults.freeze
|
543
|
+
|
544
|
+
if ($VERBOSE || $DEBUG) && self[:current].to_h != self[:default].to_h
|
545
|
+
warn "Misconfigured Net::IMAP::Config[:current] => %p,\n" \
|
546
|
+
" not equal to Net::IMAP::Config[:default] => %p" % [
|
547
|
+
self[:current].to_h, self[:default].to_h
|
548
|
+
]
|
549
|
+
end
|
468
550
|
end
|
469
551
|
end
|
470
552
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
class ConnectionState < Net::IMAP::Data # :nodoc:
|
6
|
+
def self.define(symbol, *attrs)
|
7
|
+
symbol => Symbol
|
8
|
+
state = super(*attrs)
|
9
|
+
state.const_set :NAME, symbol
|
10
|
+
state
|
11
|
+
end
|
12
|
+
|
13
|
+
def symbol; self.class::NAME end
|
14
|
+
def name; self.class::NAME.name end
|
15
|
+
alias to_sym symbol
|
16
|
+
|
17
|
+
def deconstruct; [symbol, *super] end
|
18
|
+
|
19
|
+
def deconstruct_keys(names)
|
20
|
+
hash = super
|
21
|
+
hash[:symbol] = symbol if names.nil? || names.include?(:symbol)
|
22
|
+
hash[:name] = name if names.nil? || names.include?(:name)
|
23
|
+
hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_h(&block)
|
27
|
+
hash = deconstruct_keys(nil)
|
28
|
+
block ? hash.to_h(&block) : hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def not_authenticated?; to_sym == :not_authenticated end
|
32
|
+
def authenticated?; to_sym == :authenticated end
|
33
|
+
def selected?; to_sym == :selected end
|
34
|
+
def logout?; to_sym == :logout end
|
35
|
+
|
36
|
+
NotAuthenticated = define(:not_authenticated)
|
37
|
+
Authenticated = define(:authenticated)
|
38
|
+
Selected = define(:selected)
|
39
|
+
Logout = define(:logout)
|
40
|
+
|
41
|
+
class << self
|
42
|
+
undef :define
|
43
|
+
end
|
44
|
+
freeze
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
data/lib/net/imap/errors.rb
CHANGED
@@ -17,6 +17,39 @@ module Net
|
|
17
17
|
class DataFormatError < Error
|
18
18
|
end
|
19
19
|
|
20
|
+
# Error raised when the socket cannot be read, due to a Config limit.
|
21
|
+
class ResponseReadError < Error
|
22
|
+
end
|
23
|
+
|
24
|
+
# Error raised when a response is larger than IMAP#max_response_size.
|
25
|
+
class ResponseTooLargeError < ResponseReadError
|
26
|
+
attr_reader :bytes_read, :literal_size
|
27
|
+
attr_reader :max_response_size
|
28
|
+
|
29
|
+
def initialize(msg = nil, *args,
|
30
|
+
bytes_read: nil,
|
31
|
+
literal_size: nil,
|
32
|
+
max_response_size: nil,
|
33
|
+
**kwargs)
|
34
|
+
@bytes_read = bytes_read
|
35
|
+
@literal_size = literal_size
|
36
|
+
@max_response_size = max_response_size
|
37
|
+
msg ||= [
|
38
|
+
"Response size", response_size_msg, "exceeds max_response_size",
|
39
|
+
max_response_size && "(#{max_response_size}B)",
|
40
|
+
].compact.join(" ")
|
41
|
+
super(msg, *args, **kwargs)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def response_size_msg
|
47
|
+
if bytes_read && literal_size
|
48
|
+
"(#{bytes_read}B read + #{literal_size}B literal)"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
20
53
|
# Error raised when a response from the server is non-parsable.
|
21
54
|
class ResponseParseError < Error
|
22
55
|
end
|
@@ -0,0 +1,73 @@
|
|
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
|
+
end
|
12
|
+
|
13
|
+
def read_response_buffer
|
14
|
+
@buff = String.new
|
15
|
+
catch :eof do
|
16
|
+
while true
|
17
|
+
read_line
|
18
|
+
break unless (@literal_size = get_literal_size)
|
19
|
+
read_literal
|
20
|
+
end
|
21
|
+
end
|
22
|
+
buff
|
23
|
+
ensure
|
24
|
+
@buff = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :buff, :literal_size
|
30
|
+
|
31
|
+
def bytes_read = buff.bytesize
|
32
|
+
def empty? = buff.empty?
|
33
|
+
def done? = line_done? && !get_literal_size
|
34
|
+
def line_done? = buff.end_with?(CRLF)
|
35
|
+
def get_literal_size = /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i
|
36
|
+
|
37
|
+
def read_line
|
38
|
+
buff << (@sock.gets(CRLF, read_limit) or throw :eof)
|
39
|
+
max_response_remaining! unless line_done?
|
40
|
+
end
|
41
|
+
|
42
|
+
def read_literal
|
43
|
+
# check before allocating memory for literal
|
44
|
+
max_response_remaining!
|
45
|
+
literal = String.new(capacity: literal_size)
|
46
|
+
buff << (@sock.read(read_limit(literal_size), literal) or throw :eof)
|
47
|
+
ensure
|
48
|
+
@literal_size = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def read_limit(limit = nil)
|
52
|
+
[limit, max_response_remaining!].compact.min
|
53
|
+
end
|
54
|
+
|
55
|
+
def max_response_size = client.max_response_size
|
56
|
+
def max_response_remaining = max_response_size &.- bytes_read
|
57
|
+
def response_too_large? = max_response_size &.< min_response_size
|
58
|
+
def min_response_size = bytes_read + min_response_remaining
|
59
|
+
|
60
|
+
def min_response_remaining
|
61
|
+
empty? ? 3 : done? ? 0 : (literal_size || 0) + 2
|
62
|
+
end
|
63
|
+
|
64
|
+
def max_response_remaining!
|
65
|
+
return max_response_remaining unless response_too_large?
|
66
|
+
raise ResponseTooLargeError.new(
|
67
|
+
max_response_size:, bytes_read:, literal_size:,
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -174,7 +174,7 @@ module Net
|
|
174
174
|
#
|
175
175
|
# <i>Set membership:</i>
|
176
176
|
# - #include? (aliased as #member?):
|
177
|
-
# Returns whether a given
|
177
|
+
# Returns whether a given element (nz-number, range, or <tt>*</tt>) is
|
178
178
|
# contained by the set.
|
179
179
|
# - #include_star?: Returns whether the set contains <tt>*</tt>.
|
180
180
|
#
|
@@ -239,13 +239,13 @@ module Net
|
|
239
239
|
# These methods do not modify +self+.
|
240
240
|
#
|
241
241
|
# - #| (aliased as #union and #+): Returns a new set combining all members
|
242
|
-
# from +self+ with all members from the other
|
242
|
+
# from +self+ with all members from the other set.
|
243
243
|
# - #& (aliased as #intersection): Returns a new set containing all members
|
244
|
-
# common to +self+ and the other
|
244
|
+
# common to +self+ and the other set.
|
245
245
|
# - #- (aliased as #difference): Returns a copy of +self+ with all members
|
246
|
-
# in the other
|
246
|
+
# in the other set removed.
|
247
247
|
# - #^ (aliased as #xor): Returns a new set containing all members from
|
248
|
-
# +self+ and the other
|
248
|
+
# +self+ and the other set except those common to both.
|
249
249
|
# - #~ (aliased as #complement): Returns a new set containing all members
|
250
250
|
# that are not in +self+
|
251
251
|
# - #limit: Returns a copy of +self+ which has replaced <tt>*</tt> with a
|
@@ -258,17 +258,17 @@ module Net
|
|
258
258
|
#
|
259
259
|
# These methods always update #string to be fully sorted and coalesced.
|
260
260
|
#
|
261
|
-
# - #add (aliased as #<<): Adds a given
|
262
|
-
# - #add?: If the given
|
261
|
+
# - #add (aliased as #<<): Adds a given element to the set; returns +self+.
|
262
|
+
# - #add?: If the given element is not fully included the set, adds it and
|
263
263
|
# returns +self+; otherwise, returns +nil+.
|
264
|
-
# - #merge:
|
264
|
+
# - #merge: Adds all members of the given sets into this set; returns +self+.
|
265
265
|
# - #complement!: Replaces the contents of the set with its own #complement.
|
266
266
|
#
|
267
267
|
# <i>Order preserving:</i>
|
268
268
|
#
|
269
269
|
# These methods _may_ cause #string to not be sorted or coalesced.
|
270
270
|
#
|
271
|
-
# - #append: Adds
|
271
|
+
# - #append: Adds the given entry to the set, appending it to the existing
|
272
272
|
# string, and returns +self+.
|
273
273
|
# - #string=: Assigns a new #string value and replaces #elements to match.
|
274
274
|
# - #replace: Replaces the contents of the set with the contents
|
@@ -279,13 +279,14 @@ module Net
|
|
279
279
|
# sorted and coalesced.
|
280
280
|
#
|
281
281
|
# - #clear: Removes all elements in the set; returns +self+.
|
282
|
-
# - #delete: Removes a given
|
283
|
-
# - #delete?: If the given
|
282
|
+
# - #delete: Removes a given element from the set; returns +self+.
|
283
|
+
# - #delete?: If the given element is included in the set, removes it and
|
284
284
|
# returns it; otherwise, returns +nil+.
|
285
285
|
# - #delete_at: Removes the number at a given offset.
|
286
286
|
# - #slice!: Removes the number or consecutive numbers at a given offset or
|
287
287
|
# range of offsets.
|
288
|
-
# - #subtract: Removes
|
288
|
+
# - #subtract: Removes all members of the given sets from this set; returns
|
289
|
+
# +self+.
|
289
290
|
# - #limit!: Replaces <tt>*</tt> with a given maximum value and removes all
|
290
291
|
# members over that maximum; returns +self+.
|
291
292
|
#
|
@@ -318,9 +319,12 @@ module Net
|
|
318
319
|
class << self
|
319
320
|
|
320
321
|
# :call-seq:
|
321
|
-
# SequenceSet[*
|
322
|
+
# SequenceSet[*inputs] -> valid frozen sequence set
|
322
323
|
#
|
323
|
-
# Returns a frozen SequenceSet, constructed from +
|
324
|
+
# Returns a frozen SequenceSet, constructed from +inputs+.
|
325
|
+
#
|
326
|
+
# When only a single valid frozen SequenceSet is given, that same set is
|
327
|
+
# returned.
|
324
328
|
#
|
325
329
|
# An empty SequenceSet is invalid and will raise a DataFormatError.
|
326
330
|
#
|
@@ -690,7 +694,7 @@ module Net
|
|
690
694
|
alias complement :~
|
691
695
|
|
692
696
|
# :call-seq:
|
693
|
-
# add(
|
697
|
+
# add(element) -> self
|
694
698
|
# self << other -> self
|
695
699
|
#
|
696
700
|
# Adds a range or number to the set and returns +self+.
|
@@ -698,8 +702,8 @@ module Net
|
|
698
702
|
# #string will be regenerated. Use #merge to add many elements at once.
|
699
703
|
#
|
700
704
|
# Related: #add?, #merge, #union
|
701
|
-
def add(
|
702
|
-
tuple_add input_to_tuple
|
705
|
+
def add(element)
|
706
|
+
tuple_add input_to_tuple element
|
703
707
|
normalize!
|
704
708
|
end
|
705
709
|
alias << add
|
@@ -708,9 +712,9 @@ module Net
|
|
708
712
|
#
|
709
713
|
# Unlike #add, #merge, or #union, the new value is appended to #string.
|
710
714
|
# This may result in a #string which has duplicates or is out-of-order.
|
711
|
-
def append(
|
715
|
+
def append(entry)
|
712
716
|
modifying!
|
713
|
-
tuple = input_to_tuple
|
717
|
+
tuple = input_to_tuple entry
|
714
718
|
entry = tuple_to_str tuple
|
715
719
|
string unless empty? # write @string before tuple_add
|
716
720
|
tuple_add tuple
|
@@ -718,19 +722,19 @@ module Net
|
|
718
722
|
self
|
719
723
|
end
|
720
724
|
|
721
|
-
# :call-seq: add?(
|
725
|
+
# :call-seq: add?(element) -> self or nil
|
722
726
|
#
|
723
727
|
# Adds a range or number to the set and returns +self+. Returns +nil+
|
724
|
-
# when the
|
728
|
+
# when the element is already included in the set.
|
725
729
|
#
|
726
730
|
# #string will be regenerated. Use #merge to add many elements at once.
|
727
731
|
#
|
728
732
|
# Related: #add, #merge, #union, #include?
|
729
|
-
def add?(
|
730
|
-
add
|
733
|
+
def add?(element)
|
734
|
+
add element unless include? element
|
731
735
|
end
|
732
736
|
|
733
|
-
# :call-seq: delete(
|
737
|
+
# :call-seq: delete(element) -> self
|
734
738
|
#
|
735
739
|
# Deletes the given range or number from the set and returns +self+.
|
736
740
|
#
|
@@ -738,8 +742,8 @@ module Net
|
|
738
742
|
# many elements at once.
|
739
743
|
#
|
740
744
|
# Related: #delete?, #delete_at, #subtract, #difference
|
741
|
-
def delete(
|
742
|
-
tuple_subtract input_to_tuple
|
745
|
+
def delete(element)
|
746
|
+
tuple_subtract input_to_tuple element
|
743
747
|
normalize!
|
744
748
|
end
|
745
749
|
|
@@ -775,8 +779,8 @@ module Net
|
|
775
779
|
# #string will be regenerated after deletion.
|
776
780
|
#
|
777
781
|
# Related: #delete, #delete_at, #subtract, #difference, #disjoint?
|
778
|
-
def delete?(
|
779
|
-
tuple = input_to_tuple
|
782
|
+
def delete?(element)
|
783
|
+
tuple = input_to_tuple element
|
780
784
|
if tuple.first == tuple.last
|
781
785
|
return unless include_tuple? tuple
|
782
786
|
tuple_subtract tuple
|
@@ -820,33 +824,31 @@ module Net
|
|
820
824
|
deleted
|
821
825
|
end
|
822
826
|
|
823
|
-
# Merges all of the elements that appear in any of the +
|
827
|
+
# Merges all of the elements that appear in any of the +sets+ into the
|
824
828
|
# set, and returns +self+.
|
825
829
|
#
|
826
|
-
# The +
|
827
|
-
#
|
828
|
-
#
|
829
|
-
# these.
|
830
|
+
# The +sets+ may be any objects that would be accepted by ::new: non-zero
|
831
|
+
# 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
|
832
|
+
# strings, other sequence sets, or enumerables containing any of these.
|
830
833
|
#
|
831
|
-
# #string will be regenerated after all
|
834
|
+
# #string will be regenerated after all sets have been merged.
|
832
835
|
#
|
833
836
|
# Related: #add, #add?, #union
|
834
|
-
def merge(*
|
835
|
-
tuples_add input_to_tuples
|
837
|
+
def merge(*sets)
|
838
|
+
tuples_add input_to_tuples sets
|
836
839
|
normalize!
|
837
840
|
end
|
838
841
|
|
839
|
-
# Removes all of the elements that appear in any of the given +
|
840
|
-
#
|
842
|
+
# Removes all of the elements that appear in any of the given +sets+ from
|
843
|
+
# the set, and returns +self+.
|
841
844
|
#
|
842
|
-
# The +
|
843
|
-
#
|
844
|
-
#
|
845
|
-
# these.
|
845
|
+
# The +sets+ may be any objects that would be accepted by ::new: non-zero
|
846
|
+
# 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
|
847
|
+
# strings, other sequence sets, or enumerables containing any of these.
|
846
848
|
#
|
847
849
|
# Related: #difference
|
848
|
-
def subtract(*
|
849
|
-
tuples_subtract input_to_tuples
|
850
|
+
def subtract(*sets)
|
851
|
+
tuples_subtract input_to_tuples sets
|
850
852
|
normalize!
|
851
853
|
end
|
852
854
|
|
@@ -1367,6 +1369,18 @@ module Net
|
|
1367
1369
|
imap.__send__(:put_string, valid_string)
|
1368
1370
|
end
|
1369
1371
|
|
1372
|
+
# For YAML serialization
|
1373
|
+
def encode_with(coder) # :nodoc:
|
1374
|
+
# we can perfectly reconstruct from the string
|
1375
|
+
coder['string'] = to_s
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
# For YAML deserialization
|
1379
|
+
def init_with(coder) # :nodoc:
|
1380
|
+
@tuples = []
|
1381
|
+
self.string = coder['string']
|
1382
|
+
end
|
1383
|
+
|
1370
1384
|
protected
|
1371
1385
|
|
1372
1386
|
attr_reader :tuples # :nodoc:
|
@@ -1386,30 +1400,30 @@ module Net
|
|
1386
1400
|
super
|
1387
1401
|
end
|
1388
1402
|
|
1389
|
-
def input_to_tuple(
|
1390
|
-
|
1391
|
-
case
|
1392
|
-
when *STARS, Integer then [int = to_tuple_int(
|
1393
|
-
when Range then range_to_tuple(
|
1394
|
-
when String then str_to_tuple(
|
1403
|
+
def input_to_tuple(entry)
|
1404
|
+
entry = input_try_convert entry
|
1405
|
+
case entry
|
1406
|
+
when *STARS, Integer then [int = to_tuple_int(entry), int]
|
1407
|
+
when Range then range_to_tuple(entry)
|
1408
|
+
when String then str_to_tuple(entry)
|
1395
1409
|
else
|
1396
|
-
raise DataFormatError, "expected number or range, got %p" % [
|
1410
|
+
raise DataFormatError, "expected number or range, got %p" % [entry]
|
1397
1411
|
end
|
1398
1412
|
end
|
1399
1413
|
|
1400
|
-
def input_to_tuples(
|
1401
|
-
|
1402
|
-
case
|
1403
|
-
when *STARS, Integer, Range then [input_to_tuple(
|
1404
|
-
when String then str_to_tuples
|
1405
|
-
when SequenceSet then
|
1406
|
-
when Set then
|
1407
|
-
when Array then
|
1414
|
+
def input_to_tuples(set)
|
1415
|
+
set = input_try_convert set
|
1416
|
+
case set
|
1417
|
+
when *STARS, Integer, Range then [input_to_tuple(set)]
|
1418
|
+
when String then str_to_tuples set
|
1419
|
+
when SequenceSet then set.tuples
|
1420
|
+
when Set then set.map { [to_tuple_int(_1)] * 2 }
|
1421
|
+
when Array then set.flat_map { input_to_tuples _1 }
|
1408
1422
|
when nil then []
|
1409
1423
|
else
|
1410
1424
|
raise DataFormatError,
|
1411
1425
|
"expected nz-number, range, string, or enumerable; " \
|
1412
|
-
"got %p" % [
|
1426
|
+
"got %p" % [set]
|
1413
1427
|
end
|
1414
1428
|
end
|
1415
1429
|
|
data/lib/net/imap.rb
CHANGED
@@ -43,10 +43,18 @@ module Net
|
|
43
43
|
# To work on the messages within a mailbox, the client must
|
44
44
|
# first select that mailbox, using either #select or #examine
|
45
45
|
# (for read-only access). Once the client has successfully
|
46
|
-
# selected a mailbox, they enter the
|
46
|
+
# selected a mailbox, they enter the +selected+ state, and that
|
47
47
|
# mailbox becomes the _current_ mailbox, on which mail-item
|
48
48
|
# related commands implicitly operate.
|
49
49
|
#
|
50
|
+
# === Connection state
|
51
|
+
#
|
52
|
+
# Once an IMAP connection is established, the connection is in one of four
|
53
|
+
# states: <tt>not authenticated</tt>, +authenticated+, +selected+, and
|
54
|
+
# +logout+. Most commands are valid only in certain states.
|
55
|
+
#
|
56
|
+
# See #connection_state.
|
57
|
+
#
|
50
58
|
# === Sequence numbers and UIDs
|
51
59
|
#
|
52
60
|
# Messages have two sorts of identifiers: message sequence
|
@@ -199,6 +207,42 @@ module Net
|
|
199
207
|
#
|
200
208
|
# This script invokes the FETCH command and the SEARCH command concurrently.
|
201
209
|
#
|
210
|
+
# When running multiple commands, care must be taken to avoid ambiguity. For
|
211
|
+
# example, SEARCH responses are ambiguous about which command they are
|
212
|
+
# responding to, so search commands should not run simultaneously, unless the
|
213
|
+
# server supports +ESEARCH+ {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731] or
|
214
|
+
# IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]. See {RFC9051
|
215
|
+
# §5.5}[https://www.rfc-editor.org/rfc/rfc9051.html#section-5.5] for
|
216
|
+
# other examples of command sequences which should not be pipelined.
|
217
|
+
#
|
218
|
+
# == Unbounded memory use
|
219
|
+
#
|
220
|
+
# Net::IMAP reads server responses in a separate receiver thread per client.
|
221
|
+
# Unhandled response data is saved to #responses, and response_handlers run
|
222
|
+
# inside the receiver thread. See the list of methods for {handling server
|
223
|
+
# responses}[rdoc-ref:Net::IMAP@Handling+server+responses], below.
|
224
|
+
#
|
225
|
+
# Because the receiver thread continuously reads and saves new responses, some
|
226
|
+
# scenarios must be careful to avoid unbounded memory use:
|
227
|
+
#
|
228
|
+
# * Commands such as #list or #fetch can have an enormous number of responses.
|
229
|
+
# * Commands such as #fetch can result in an enormous size per response.
|
230
|
+
# * Long-lived connections will gradually accumulate unsolicited server
|
231
|
+
# responses, especially +EXISTS+, +FETCH+, and +EXPUNGE+ responses.
|
232
|
+
# * A buggy or untrusted server could send inappropriate responses, which
|
233
|
+
# could be very numerous, very large, and very rapid.
|
234
|
+
#
|
235
|
+
# Use paginated or limited versions of commands whenever possible.
|
236
|
+
#
|
237
|
+
# Use Config#max_response_size to impose a limit on incoming server responses
|
238
|
+
# as they are being read. <em>This is especially important for untrusted
|
239
|
+
# servers.</em>
|
240
|
+
#
|
241
|
+
# Use #add_response_handler to handle responses after each one is received.
|
242
|
+
# Use the +response_handlers+ argument to ::new to assign response handlers
|
243
|
+
# before the receiver thread is started. Use #extract_responses,
|
244
|
+
# #clear_responses, or #responses (with a block) to prune responses.
|
245
|
+
#
|
202
246
|
# == Errors
|
203
247
|
#
|
204
248
|
# An \IMAP server can send three different types of responses to indicate
|
@@ -260,8 +304,9 @@ module Net
|
|
260
304
|
#
|
261
305
|
# - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
|
262
306
|
# waits for a successful server greeting before the method returns.
|
307
|
+
# - #connection_state: Returns the connection state.
|
263
308
|
# - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
|
264
|
-
# - #logout: Tells the server to end the session.
|
309
|
+
# - #logout: Tells the server to end the session. Enters the +logout+ state.
|
265
310
|
# - #disconnect: Disconnects the connection (without sending #logout first).
|
266
311
|
# - #disconnected?: True if the connection has been closed.
|
267
312
|
#
|
@@ -317,37 +362,36 @@ module Net
|
|
317
362
|
# <em>In general, #capable? should be used rather than explicitly sending a
|
318
363
|
# +CAPABILITY+ command to the server.</em>
|
319
364
|
# - #noop: Allows the server to send unsolicited untagged #responses.
|
320
|
-
# - #logout: Tells the server to end the session. Enters the
|
365
|
+
# - #logout: Tells the server to end the session. Enters the +logout+ state.
|
321
366
|
#
|
322
367
|
# ==== Not Authenticated state
|
323
368
|
#
|
324
369
|
# In addition to the commands for any state, the following commands are valid
|
325
|
-
# in the
|
370
|
+
# in the +not_authenticated+ state:
|
326
371
|
#
|
327
372
|
# - #starttls: Upgrades a clear-text connection to use TLS.
|
328
373
|
#
|
329
374
|
# <em>Requires the +STARTTLS+ capability.</em>
|
330
375
|
# - #authenticate: Identifies the client to the server using the given
|
331
376
|
# {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
332
|
-
# and credentials. Enters the
|
377
|
+
# and credentials. Enters the +authenticated+ state.
|
333
378
|
#
|
334
379
|
# <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
|
335
380
|
# supported mechanisms.</em>
|
336
381
|
# - #login: Identifies the client to the server using a plain text password.
|
337
|
-
# Using #authenticate is
|
338
|
-
# state.
|
382
|
+
# Using #authenticate is preferred. Enters the +authenticated+ state.
|
339
383
|
#
|
340
384
|
# <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
|
341
385
|
#
|
342
386
|
# ==== Authenticated state
|
343
387
|
#
|
344
388
|
# In addition to the commands for any state, the following commands are valid
|
345
|
-
# in the
|
389
|
+
# in the +authenticated+ state:
|
346
390
|
#
|
347
391
|
# - #enable: Enables backwards incompatible server extensions.
|
348
392
|
# <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
|
349
|
-
# - #select: Open a mailbox and enter the
|
350
|
-
# - #examine: Open a mailbox read-only, and enter the
|
393
|
+
# - #select: Open a mailbox and enter the +selected+ state.
|
394
|
+
# - #examine: Open a mailbox read-only, and enter the +selected+ state.
|
351
395
|
# - #create: Creates a new mailbox.
|
352
396
|
# - #delete: Permanently remove a mailbox.
|
353
397
|
# - #rename: Change the name of a mailbox.
|
@@ -369,12 +413,12 @@ module Net
|
|
369
413
|
#
|
370
414
|
# ==== Selected state
|
371
415
|
#
|
372
|
-
# In addition to the commands for any state and the
|
373
|
-
# commands, the following commands are valid in the
|
416
|
+
# In addition to the commands for any state and the +authenticated+
|
417
|
+
# commands, the following commands are valid in the +selected+ state:
|
374
418
|
#
|
375
|
-
# - #close: Closes the mailbox and returns to the
|
419
|
+
# - #close: Closes the mailbox and returns to the +authenticated+ state,
|
376
420
|
# expunging deleted messages, unless the mailbox was opened as read-only.
|
377
|
-
# - #unselect: Closes the mailbox and returns to the
|
421
|
+
# - #unselect: Closes the mailbox and returns to the +authenticated+ state,
|
378
422
|
# without expunging any messages.
|
379
423
|
# <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
|
380
424
|
# - #expunge: Permanently removes messages which have the Deleted flag set.
|
@@ -395,7 +439,7 @@ module Net
|
|
395
439
|
#
|
396
440
|
# ==== Logout state
|
397
441
|
#
|
398
|
-
# No \IMAP commands are valid in the
|
442
|
+
# No \IMAP commands are valid in the +logout+ state. If the socket is still
|
399
443
|
# open, Net::IMAP will close it after receiving server confirmation.
|
400
444
|
# Exceptions will be raised by \IMAP commands that have already started and
|
401
445
|
# are waiting for a response, as well as any that are called after logout.
|
@@ -449,7 +493,7 @@ module Net
|
|
449
493
|
# ==== RFC3691: +UNSELECT+
|
450
494
|
# Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
|
451
495
|
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
452
|
-
# - #unselect: Closes the mailbox and returns to the
|
496
|
+
# - #unselect: Closes the mailbox and returns to the +authenticated+ state,
|
453
497
|
# without expunging any messages.
|
454
498
|
#
|
455
499
|
# ==== RFC4314: +ACL+
|
@@ -744,7 +788,7 @@ module Net
|
|
744
788
|
# * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
|
745
789
|
#
|
746
790
|
class IMAP < Protocol
|
747
|
-
VERSION = "0.5.
|
791
|
+
VERSION = "0.5.7"
|
748
792
|
|
749
793
|
# Aliases for supported capabilities, to be used with the #enable command.
|
750
794
|
ENABLE_ALIASES = {
|
@@ -752,9 +796,12 @@ module Net
|
|
752
796
|
"UTF8=ONLY" => "UTF8=ACCEPT",
|
753
797
|
}.freeze
|
754
798
|
|
755
|
-
|
756
|
-
autoload :
|
757
|
-
autoload :
|
799
|
+
dir = File.expand_path("imap", __dir__)
|
800
|
+
autoload :ConnectionState, "#{dir}/connection_state"
|
801
|
+
autoload :ResponseReader, "#{dir}/response_reader"
|
802
|
+
autoload :SASL, "#{dir}/sasl"
|
803
|
+
autoload :SASLAdapter, "#{dir}/sasl_adapter"
|
804
|
+
autoload :StringPrep, "#{dir}/stringprep"
|
758
805
|
|
759
806
|
include MonitorMixin
|
760
807
|
if defined?(OpenSSL::SSL)
|
@@ -766,9 +813,11 @@ module Net
|
|
766
813
|
def self.config; Config.global end
|
767
814
|
|
768
815
|
# Returns the global debug mode.
|
816
|
+
# Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
|
769
817
|
def self.debug; config.debug end
|
770
818
|
|
771
819
|
# Sets the global debug mode.
|
820
|
+
# Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
|
772
821
|
def self.debug=(val)
|
773
822
|
config.debug = val
|
774
823
|
end
|
@@ -789,7 +838,7 @@ module Net
|
|
789
838
|
alias default_ssl_port default_tls_port
|
790
839
|
end
|
791
840
|
|
792
|
-
# Returns the initial greeting the server, an UntaggedResponse.
|
841
|
+
# Returns the initial greeting sent by the server, an UntaggedResponse.
|
793
842
|
attr_reader :greeting
|
794
843
|
|
795
844
|
# The client configuration. See Net::IMAP::Config.
|
@@ -798,13 +847,28 @@ module Net
|
|
798
847
|
# Net::IMAP.config.
|
799
848
|
attr_reader :config
|
800
849
|
|
801
|
-
|
802
|
-
#
|
803
|
-
#
|
804
|
-
|
850
|
+
##
|
851
|
+
# :attr_reader: open_timeout
|
852
|
+
# Seconds to wait until a connection is opened. Also used by #starttls.
|
853
|
+
# Delegates to {config.open_timeout}[rdoc-ref:Config#open_timeout].
|
805
854
|
|
855
|
+
##
|
856
|
+
# :attr_reader: idle_response_timeout
|
806
857
|
# Seconds to wait until an IDLE response is received.
|
807
|
-
|
858
|
+
# Delegates to {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout].
|
859
|
+
|
860
|
+
##
|
861
|
+
# :attr_accessor: max_response_size
|
862
|
+
#
|
863
|
+
# The maximum allowed server response size, in bytes.
|
864
|
+
# Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size].
|
865
|
+
|
866
|
+
# :stopdoc:
|
867
|
+
def open_timeout; config.open_timeout end
|
868
|
+
def idle_response_timeout; config.idle_response_timeout end
|
869
|
+
def max_response_size; config.max_response_size end
|
870
|
+
def max_response_size=(val) config.max_response_size = val end
|
871
|
+
# :startdoc:
|
808
872
|
|
809
873
|
# The hostname this client connected to
|
810
874
|
attr_reader :host
|
@@ -827,6 +891,67 @@ module Net
|
|
827
891
|
# Returns +false+ for a plaintext connection.
|
828
892
|
attr_reader :ssl_ctx_params
|
829
893
|
|
894
|
+
# Returns the current connection state.
|
895
|
+
#
|
896
|
+
# Once an IMAP connection is established, the connection is in one of four
|
897
|
+
# states: +not_authenticated+, +authenticated+, +selected+, and +logout+.
|
898
|
+
# Most commands are valid only in certain states.
|
899
|
+
#
|
900
|
+
# The connection state object responds to +to_sym+ and +name+ with the name
|
901
|
+
# of the current connection state, as a Symbol or String. Future versions
|
902
|
+
# of +net-imap+ may store additional information on the state object.
|
903
|
+
#
|
904
|
+
# From {RFC9051}[https://www.rfc-editor.org/rfc/rfc9051#section-3]:
|
905
|
+
# +----------------------+
|
906
|
+
# |connection established|
|
907
|
+
# +----------------------+
|
908
|
+
# ||
|
909
|
+
# \/
|
910
|
+
# +--------------------------------------+
|
911
|
+
# | server greeting |
|
912
|
+
# +--------------------------------------+
|
913
|
+
# || (1) || (2) || (3)
|
914
|
+
# \/ || ||
|
915
|
+
# +-----------------+ || ||
|
916
|
+
# |Not Authenticated| || ||
|
917
|
+
# +-----------------+ || ||
|
918
|
+
# || (7) || (4) || ||
|
919
|
+
# || \/ \/ ||
|
920
|
+
# || +----------------+ ||
|
921
|
+
# || | Authenticated |<=++ ||
|
922
|
+
# || +----------------+ || ||
|
923
|
+
# || || (7) || (5) || (6) ||
|
924
|
+
# || || \/ || ||
|
925
|
+
# || || +--------+ || ||
|
926
|
+
# || || |Selected|==++ ||
|
927
|
+
# || || +--------+ ||
|
928
|
+
# || || || (7) ||
|
929
|
+
# \/ \/ \/ \/
|
930
|
+
# +--------------------------------------+
|
931
|
+
# | Logout |
|
932
|
+
# +--------------------------------------+
|
933
|
+
# ||
|
934
|
+
# \/
|
935
|
+
# +-------------------------------+
|
936
|
+
# |both sides close the connection|
|
937
|
+
# +-------------------------------+
|
938
|
+
#
|
939
|
+
# >>>
|
940
|
+
# Legend for the above diagram:
|
941
|
+
#
|
942
|
+
# 1. connection without pre-authentication (+OK+ #greeting)
|
943
|
+
# 2. pre-authenticated connection (+PREAUTH+ #greeting)
|
944
|
+
# 3. rejected connection (+BYE+ #greeting)
|
945
|
+
# 4. successful #login or #authenticate command
|
946
|
+
# 5. successful #select or #examine command
|
947
|
+
# 6. #close or #unselect command, unsolicited +CLOSED+ response code, or
|
948
|
+
# failed #select or #examine command
|
949
|
+
# 7. #logout command, server shutdown, or connection closed
|
950
|
+
#
|
951
|
+
# Before the server greeting, the state is +not_authenticated+.
|
952
|
+
# After the connection closes, the state remains +logout+.
|
953
|
+
attr_reader :connection_state
|
954
|
+
|
830
955
|
# Creates a new Net::IMAP object and connects it to the specified
|
831
956
|
# +host+.
|
832
957
|
#
|
@@ -860,6 +985,12 @@ module Net
|
|
860
985
|
#
|
861
986
|
# See DeprecatedClientOptions.new for deprecated SSL arguments.
|
862
987
|
#
|
988
|
+
# [response_handlers]
|
989
|
+
# A list of response handlers to be added before the receiver thread is
|
990
|
+
# started. This ensures every server response is handled, including the
|
991
|
+
# #greeting. Note that the greeting is handled in the current thread, but
|
992
|
+
# all other responses are handled in the receiver thread.
|
993
|
+
#
|
863
994
|
# [config]
|
864
995
|
# A Net::IMAP::Config object to use as the basis for #config. By default,
|
865
996
|
# the global Net::IMAP.config is used.
|
@@ -931,7 +1062,7 @@ module Net
|
|
931
1062
|
# [Net::IMAP::ByeResponseError]
|
932
1063
|
# Connected to the host successfully, but it immediately said goodbye.
|
933
1064
|
#
|
934
|
-
def initialize(host, port: nil, ssl:
|
1065
|
+
def initialize(host, port: nil, ssl: nil, response_handlers: nil,
|
935
1066
|
config: Config.global, **config_options)
|
936
1067
|
super()
|
937
1068
|
# Config options
|
@@ -946,6 +1077,8 @@ module Net
|
|
946
1077
|
@exception = nil
|
947
1078
|
@greeting = nil
|
948
1079
|
@capabilities = nil
|
1080
|
+
@tls_verified = false
|
1081
|
+
@connection_state = ConnectionState::NotAuthenticated.new
|
949
1082
|
|
950
1083
|
# Client Protocol Receiver
|
951
1084
|
@parser = ResponseParser.new(config: @config)
|
@@ -954,6 +1087,7 @@ module Net
|
|
954
1087
|
@receiver_thread = nil
|
955
1088
|
@receiver_thread_exception = nil
|
956
1089
|
@receiver_thread_terminating = false
|
1090
|
+
response_handlers&.each do add_response_handler(_1) end
|
957
1091
|
|
958
1092
|
# Client Protocol Sender (including state for currently running commands)
|
959
1093
|
@tag_prefix = "RUBY"
|
@@ -967,8 +1101,8 @@ module Net
|
|
967
1101
|
@logout_command_tag = nil
|
968
1102
|
|
969
1103
|
# Connection
|
970
|
-
@tls_verified = false
|
971
1104
|
@sock = tcp_socket(@host, @port)
|
1105
|
+
@reader = ResponseReader.new(self, @sock)
|
972
1106
|
start_tls_session if ssl_ctx
|
973
1107
|
start_imap_connection
|
974
1108
|
end
|
@@ -983,6 +1117,7 @@ module Net
|
|
983
1117
|
# Related: #logout, #logout!
|
984
1118
|
def disconnect
|
985
1119
|
return if disconnected?
|
1120
|
+
state_logout!
|
986
1121
|
begin
|
987
1122
|
begin
|
988
1123
|
# try to call SSL::SSLSocket#io.
|
@@ -1221,6 +1356,10 @@ module Net
|
|
1221
1356
|
# both successful. Any error indicates that the connection has not been
|
1222
1357
|
# secured.
|
1223
1358
|
#
|
1359
|
+
# After the server agrees to start a TLS connection, this method waits up to
|
1360
|
+
# {config.open_timeout}[rdoc-ref:Config#open_timeout] before raising
|
1361
|
+
# +Net::OpenTimeout+.
|
1362
|
+
#
|
1224
1363
|
# *Note:*
|
1225
1364
|
# >>>
|
1226
1365
|
# Any #response_handlers added before STARTTLS should be aware that the
|
@@ -1368,7 +1507,7 @@ module Net
|
|
1368
1507
|
# capabilities, they will be cached.
|
1369
1508
|
def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback)
|
1370
1509
|
sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback)
|
1371
|
-
.tap
|
1510
|
+
.tap do state_authenticated! _1 end
|
1372
1511
|
end
|
1373
1512
|
|
1374
1513
|
# Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
|
@@ -1402,7 +1541,7 @@ module Net
|
|
1402
1541
|
raise LoginDisabledError
|
1403
1542
|
end
|
1404
1543
|
send_command("LOGIN", user, password)
|
1405
|
-
.tap
|
1544
|
+
.tap do state_authenticated! _1 end
|
1406
1545
|
end
|
1407
1546
|
|
1408
1547
|
# Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
|
@@ -1442,8 +1581,10 @@ module Net
|
|
1442
1581
|
args = ["SELECT", mailbox]
|
1443
1582
|
args << ["CONDSTORE"] if condstore
|
1444
1583
|
synchronize do
|
1584
|
+
state_unselected! # implicitly closes current mailbox
|
1445
1585
|
@responses.clear
|
1446
1586
|
send_command(*args)
|
1587
|
+
.tap do state_selected! end
|
1447
1588
|
end
|
1448
1589
|
end
|
1449
1590
|
|
@@ -1460,8 +1601,10 @@ module Net
|
|
1460
1601
|
args = ["EXAMINE", mailbox]
|
1461
1602
|
args << ["CONDSTORE"] if condstore
|
1462
1603
|
synchronize do
|
1604
|
+
state_unselected! # implicitly closes current mailbox
|
1463
1605
|
@responses.clear
|
1464
1606
|
send_command(*args)
|
1607
|
+
.tap do state_selected! end
|
1465
1608
|
end
|
1466
1609
|
end
|
1467
1610
|
|
@@ -1900,6 +2043,7 @@ module Net
|
|
1900
2043
|
# Related: #unselect
|
1901
2044
|
def close
|
1902
2045
|
send_command("CLOSE")
|
2046
|
+
.tap do state_authenticated! end
|
1903
2047
|
end
|
1904
2048
|
|
1905
2049
|
# Sends an {UNSELECT command [RFC3691 §2]}[https://www.rfc-editor.org/rfc/rfc3691#section-3]
|
@@ -1916,6 +2060,7 @@ module Net
|
|
1916
2060
|
# [RFC3691[https://www.rfc-editor.org/rfc/rfc3691]].
|
1917
2061
|
def unselect
|
1918
2062
|
send_command("UNSELECT")
|
2063
|
+
.tap do state_authenticated! end
|
1919
2064
|
end
|
1920
2065
|
|
1921
2066
|
# call-seq:
|
@@ -3146,6 +3291,10 @@ module Net
|
|
3146
3291
|
# end
|
3147
3292
|
# }
|
3148
3293
|
#
|
3294
|
+
# Response handlers can also be added when the client is created before the
|
3295
|
+
# receiver thread is started, by the +response_handlers+ argument to ::new.
|
3296
|
+
# This ensures every server response is handled, including the #greeting.
|
3297
|
+
#
|
3149
3298
|
# Related: #remove_response_handler, #response_handlers
|
3150
3299
|
def add_response_handler(handler = nil, &block)
|
3151
3300
|
raise ArgumentError, "two Procs are passed" if handler && block
|
@@ -3172,8 +3321,10 @@ module Net
|
|
3172
3321
|
def start_imap_connection
|
3173
3322
|
@greeting = get_server_greeting
|
3174
3323
|
@capabilities = capabilities_from_resp_code @greeting
|
3324
|
+
@response_handlers.each do |handler| handler.call(@greeting) end
|
3175
3325
|
@receiver_thread = start_receiver_thread
|
3176
3326
|
rescue Exception
|
3327
|
+
state_logout!
|
3177
3328
|
@sock.close
|
3178
3329
|
raise
|
3179
3330
|
end
|
@@ -3182,7 +3333,10 @@ module Net
|
|
3182
3333
|
greeting = get_response
|
3183
3334
|
raise Error, "No server greeting - connection closed" unless greeting
|
3184
3335
|
record_untagged_response_code greeting
|
3185
|
-
|
3336
|
+
case greeting.name
|
3337
|
+
when "PREAUTH" then state_authenticated!
|
3338
|
+
when "BYE" then state_logout!; raise ByeResponseError, greeting
|
3339
|
+
end
|
3186
3340
|
greeting
|
3187
3341
|
end
|
3188
3342
|
|
@@ -3192,6 +3346,8 @@ module Net
|
|
3192
3346
|
rescue Exception => ex
|
3193
3347
|
@receiver_thread_exception = ex
|
3194
3348
|
# don't exit the thread with an exception
|
3349
|
+
ensure
|
3350
|
+
state_logout!
|
3195
3351
|
end
|
3196
3352
|
end
|
3197
3353
|
|
@@ -3214,6 +3370,7 @@ module Net
|
|
3214
3370
|
resp = get_response
|
3215
3371
|
rescue Exception => e
|
3216
3372
|
synchronize do
|
3373
|
+
state_logout!
|
3217
3374
|
@sock.close
|
3218
3375
|
@exception = e
|
3219
3376
|
end
|
@@ -3233,6 +3390,7 @@ module Net
|
|
3233
3390
|
@tagged_response_arrival.broadcast
|
3234
3391
|
case resp.tag
|
3235
3392
|
when @logout_command_tag
|
3393
|
+
state_logout!
|
3236
3394
|
return
|
3237
3395
|
when @continued_command_tag
|
3238
3396
|
@continuation_request_exception =
|
@@ -3242,6 +3400,7 @@ module Net
|
|
3242
3400
|
when UntaggedResponse
|
3243
3401
|
record_untagged_response(resp)
|
3244
3402
|
if resp.name == "BYE" && @logout_command_tag.nil?
|
3403
|
+
state_logout!
|
3245
3404
|
@sock.close
|
3246
3405
|
@exception = ByeResponseError.new(resp)
|
3247
3406
|
connection_closed = true
|
@@ -3249,6 +3408,7 @@ module Net
|
|
3249
3408
|
when ContinuationRequest
|
3250
3409
|
@continuation_request_arrival.signal
|
3251
3410
|
end
|
3411
|
+
state_unselected! if resp in {data: {code: {name: "CLOSED"}}}
|
3252
3412
|
@response_handlers.each do |handler|
|
3253
3413
|
handler.call(resp)
|
3254
3414
|
end
|
@@ -3300,23 +3460,10 @@ module Net
|
|
3300
3460
|
end
|
3301
3461
|
|
3302
3462
|
def get_response
|
3303
|
-
buff =
|
3304
|
-
while true
|
3305
|
-
s = @sock.gets(CRLF)
|
3306
|
-
break unless s
|
3307
|
-
buff.concat(s)
|
3308
|
-
if /\{(\d+)\}\r\n/n =~ s
|
3309
|
-
s = @sock.read($1.to_i)
|
3310
|
-
buff.concat(s)
|
3311
|
-
else
|
3312
|
-
break
|
3313
|
-
end
|
3314
|
-
end
|
3463
|
+
buff = @reader.read_response_buffer
|
3315
3464
|
return nil if buff.length == 0
|
3316
|
-
if config.debug?
|
3317
|
-
|
3318
|
-
end
|
3319
|
-
return @parser.parse(buff)
|
3465
|
+
$stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
|
3466
|
+
@parser.parse(buff)
|
3320
3467
|
end
|
3321
3468
|
|
3322
3469
|
#############################
|
@@ -3620,6 +3767,7 @@ module Net
|
|
3620
3767
|
raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
|
3621
3768
|
raise "cannot start TLS without SSLContext" unless ssl_ctx
|
3622
3769
|
@sock = SSLSocket.new(@sock, ssl_ctx)
|
3770
|
+
@reader = ResponseReader.new(self, @sock)
|
3623
3771
|
@sock.sync_close = true
|
3624
3772
|
@sock.hostname = @host if @sock.respond_to? :hostname=
|
3625
3773
|
ssl_socket_connect(@sock, open_timeout)
|
@@ -3629,6 +3777,29 @@ module Net
|
|
3629
3777
|
end
|
3630
3778
|
end
|
3631
3779
|
|
3780
|
+
def state_authenticated!(resp = nil)
|
3781
|
+
synchronize do
|
3782
|
+
@capabilities = capabilities_from_resp_code resp if resp
|
3783
|
+
@connection_state = ConnectionState::Authenticated.new
|
3784
|
+
end
|
3785
|
+
end
|
3786
|
+
|
3787
|
+
def state_selected!
|
3788
|
+
synchronize do
|
3789
|
+
@connection_state = ConnectionState::Selected.new
|
3790
|
+
end
|
3791
|
+
end
|
3792
|
+
|
3793
|
+
def state_unselected!
|
3794
|
+
state_authenticated! if connection_state.to_sym == :selected
|
3795
|
+
end
|
3796
|
+
|
3797
|
+
def state_logout!
|
3798
|
+
synchronize do
|
3799
|
+
@connection_state = ConnectionState::Logout.new
|
3800
|
+
end
|
3801
|
+
end
|
3802
|
+
|
3632
3803
|
def sasl_adapter
|
3633
3804
|
SASLAdapter.new(self, &method(:send_command_with_continuations))
|
3634
3805
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: net-imap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shugo Maeda
|
8
8
|
- nicholas a. evans
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-protocol
|
@@ -60,6 +60,7 @@ files:
|
|
60
60
|
- lib/net/imap/config/attr_accessors.rb
|
61
61
|
- lib/net/imap/config/attr_inheritance.rb
|
62
62
|
- lib/net/imap/config/attr_type_coercion.rb
|
63
|
+
- lib/net/imap/connection_state.rb
|
63
64
|
- lib/net/imap/data_encoding.rb
|
64
65
|
- lib/net/imap/data_lite.rb
|
65
66
|
- lib/net/imap/deprecated_client_options.rb
|
@@ -70,6 +71,7 @@ files:
|
|
70
71
|
- lib/net/imap/response_data.rb
|
71
72
|
- lib/net/imap/response_parser.rb
|
72
73
|
- lib/net/imap/response_parser/parser_utils.rb
|
74
|
+
- lib/net/imap/response_reader.rb
|
73
75
|
- lib/net/imap/sasl.rb
|
74
76
|
- lib/net/imap/sasl/anonymous_authenticator.rb
|
75
77
|
- lib/net/imap/sasl/authentication_exchange.rb
|
@@ -127,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
129
|
- !ruby/object:Gem::Version
|
128
130
|
version: '0'
|
129
131
|
requirements: []
|
130
|
-
rubygems_version: 3.6.
|
132
|
+
rubygems_version: 3.6.7
|
131
133
|
specification_version: 4
|
132
134
|
summary: Ruby client api for Internet Message Access Protocol
|
133
135
|
test_files: []
|