net-imap 0.5.6 → 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 +7 -4
- data/lib/net/imap/command_data.rb +0 -68
- data/lib/net/imap/config/attr_inheritance.rb +14 -1
- data/lib/net/imap/config/attr_type_coercion.rb +35 -22
- data/lib/net/imap/config/attr_version_defaults.rb +93 -0
- data/lib/net/imap/config.rb +265 -95
- data/lib/net/imap/connection_state.rb +48 -0
- data/lib/net/imap/data_encoding.rb +77 -28
- data/lib/net/imap/errors.rb +33 -0
- data/lib/net/imap/esearch_result.rb +48 -3
- data/lib/net/imap/flags.rb +1 -1
- data/lib/net/imap/response_data.rb +2 -4
- data/lib/net/imap/response_parser.rb +8 -13
- data/lib/net/imap/response_reader.rb +73 -0
- data/lib/net/imap/search_result.rb +8 -3
- data/lib/net/imap/sequence_set.rb +1113 -431
- data/lib/net/imap/uidplus_data.rb +2 -63
- data/lib/net/imap/vanished_data.rb +10 -1
- data/lib/net/imap.rb +291 -81
- data/net-imap.gemspec +1 -1
- data/rakelib/string_prep_tables_generator.rb +4 -2
- metadata +7 -5
- data/lib/net/imap/data_lite.rb +0 -226
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
|
|
@@ -25,6 +25,12 @@ module Net
|
|
|
25
25
|
# Some search extensions may result in the server sending ESearchResult
|
|
26
26
|
# responses after the initiating command has completed. Use
|
|
27
27
|
# IMAP#add_response_handler to handle these responses.
|
|
28
|
+
#
|
|
29
|
+
# ==== Compatibility with SearchResult
|
|
30
|
+
#
|
|
31
|
+
# Note that both SearchResult and ESearchResult implement +each+, +to_a+,
|
|
32
|
+
# and +to_sequence_set+. These methods can be used regardless of whether
|
|
33
|
+
# the server returns +SEARCH+ or +ESEARCH+ data (or no data).
|
|
28
34
|
class ESearchResult < Data.define(:tag, :uid, :data)
|
|
29
35
|
def initialize(tag: nil, uid: nil, data: nil)
|
|
30
36
|
tag => String | nil; tag = -tag if tag
|
|
@@ -39,12 +45,49 @@ module Net
|
|
|
39
45
|
# numbers or UIDs, +to_a+ returns that set as an array of integers.
|
|
40
46
|
#
|
|
41
47
|
# When both #all and #partial are +nil+, either because the server
|
|
42
|
-
# returned no results or because +ALL+
|
|
43
|
-
# the IMAP#search +RETURN+ options, #to_a returns an empty array.
|
|
48
|
+
# returned no results or because neither +ALL+ or +PARTIAL+ were included
|
|
49
|
+
# in the IMAP#search +RETURN+ options, #to_a returns an empty array.
|
|
44
50
|
#
|
|
45
51
|
# Note that SearchResult also implements +to_a+, so it can be used without
|
|
46
52
|
# checking if the server returned +SEARCH+ or +ESEARCH+ data.
|
|
47
|
-
|
|
53
|
+
#
|
|
54
|
+
# Related: #each, #to_sequence_set, #all, #partial
|
|
55
|
+
def to_a; to_sequence_set.numbers end
|
|
56
|
+
|
|
57
|
+
# :call-seq: to_sequence_set -> SequenceSet or nil
|
|
58
|
+
#
|
|
59
|
+
# When either #all or #partial contains a SequenceSet of message sequence
|
|
60
|
+
# numbers or UIDs, +to_sequence_set+ returns that sequence set.
|
|
61
|
+
#
|
|
62
|
+
# When both #all and #partial are +nil+, either because the server
|
|
63
|
+
# returned no results or because neither +ALL+ or +PARTIAL+ were included
|
|
64
|
+
# in the IMAP#search +RETURN+ options, #to_sequence_set returns
|
|
65
|
+
# SequenceSet.empty.
|
|
66
|
+
#
|
|
67
|
+
# Note that SearchResult also implements +to_sequence_set+, so it can be
|
|
68
|
+
# used without checking if the server returned +SEARCH+ or +ESEARCH+ data.
|
|
69
|
+
#
|
|
70
|
+
# Related: #each, #to_a, #all, #partial
|
|
71
|
+
def to_sequence_set
|
|
72
|
+
all || partial&.to_sequence_set || SequenceSet.empty
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# When either #all or #partial contains a SequenceSet of message sequence
|
|
76
|
+
# numbers or UIDs, +each+ yields each integer in the set.
|
|
77
|
+
#
|
|
78
|
+
# When both #all and #partial are +nil+, either because the server
|
|
79
|
+
# returned no results or because +ALL+ and +PARTIAL+ were not included in
|
|
80
|
+
# the IMAP#search +RETURN+ options, #each does not yield.
|
|
81
|
+
#
|
|
82
|
+
# Note that SearchResult also implements +#each+, so it can be used
|
|
83
|
+
# without checking if the server returned +SEARCH+ or +ESEARCH+ data.
|
|
84
|
+
#
|
|
85
|
+
# Related: #to_sequence_set, #to_a, #all, #partial
|
|
86
|
+
def each(&)
|
|
87
|
+
return to_enum(__callee__) unless block_given?
|
|
88
|
+
to_sequence_set.each_number(&)
|
|
89
|
+
self
|
|
90
|
+
end
|
|
48
91
|
|
|
49
92
|
##
|
|
50
93
|
# attr_reader: tag
|
|
@@ -161,6 +204,8 @@ module Net
|
|
|
161
204
|
#
|
|
162
205
|
# See also: ESearchResult#to_a.
|
|
163
206
|
def to_a; results&.numbers || [] end
|
|
207
|
+
|
|
208
|
+
alias to_sequence_set results
|
|
164
209
|
end
|
|
165
210
|
|
|
166
211
|
# :call-seq: partial -> PartialResult or nil
|
data/lib/net/imap/flags.rb
CHANGED
|
@@ -6,8 +6,6 @@ module Net
|
|
|
6
6
|
autoload :FetchData, "#{__dir__}/fetch_data"
|
|
7
7
|
autoload :UIDFetchData, "#{__dir__}/fetch_data"
|
|
8
8
|
autoload :SearchResult, "#{__dir__}/search_result"
|
|
9
|
-
autoload :SequenceSet, "#{__dir__}/sequence_set"
|
|
10
|
-
autoload :UIDPlusData, "#{__dir__}/uidplus_data"
|
|
11
9
|
autoload :AppendUIDData, "#{__dir__}/uidplus_data"
|
|
12
10
|
autoload :CopyUIDData, "#{__dir__}/uidplus_data"
|
|
13
11
|
autoload :VanishedData, "#{__dir__}/vanished_data"
|
|
@@ -261,8 +259,8 @@ module Net
|
|
|
261
259
|
#
|
|
262
260
|
# === +UIDPLUS+ extension
|
|
263
261
|
# See {[RFC4315 §3]}[https://www.rfc-editor.org/rfc/rfc4315#section-3].
|
|
264
|
-
# * +APPENDUID+, #data is
|
|
265
|
-
# * +COPYUID+, #data is
|
|
262
|
+
# * +APPENDUID+, #data is AppendUIDData. See IMAP#append.
|
|
263
|
+
# * +COPYUID+, #data is CopyUIDData. See IMAP#copy.
|
|
266
264
|
# * +UIDNOTSTICKY+, #data is +nil+. See IMAP#select.
|
|
267
265
|
#
|
|
268
266
|
# === +SEARCHRES+ extension
|
|
@@ -2017,24 +2017,19 @@ module Net
|
|
|
2017
2017
|
CopyUID(validity, src_uids, dst_uids)
|
|
2018
2018
|
end
|
|
2019
2019
|
|
|
2020
|
-
def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
|
|
2021
|
-
def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end
|
|
2022
|
-
|
|
2023
2020
|
# TODO: remove this code in the v0.6.0 release
|
|
2024
2021
|
def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids)
|
|
2025
2022
|
return unless config.parser_use_deprecated_uidplus_data
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
dst_uids = dst_uids.each_ordered_number.to_a
|
|
2032
|
-
UIDPlusData.new(validity, src_uids, dst_uids)
|
|
2033
|
-
elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size
|
|
2034
|
-
parse_error("uid-set is too large: %d > %d", count, max)
|
|
2035
|
-
end
|
|
2023
|
+
warn("#{Config}#parser_use_deprecated_uidplus_data is ignored " \
|
|
2024
|
+
"since v0.6.0. Disable this warning by setting " \
|
|
2025
|
+
"config.parser_use_deprecated_uidplus_data = false.",
|
|
2026
|
+
category: :deprecated, uplevel: 9)
|
|
2027
|
+
nil
|
|
2036
2028
|
end
|
|
2037
2029
|
|
|
2030
|
+
def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
|
|
2031
|
+
def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end
|
|
2032
|
+
|
|
2038
2033
|
ADDRESS_REGEXP = /\G
|
|
2039
2034
|
\( (?: NIL | #{Patterns::QUOTED_rev2} ) # 1: NAME
|
|
2040
2035
|
\s (?: NIL | #{Patterns::QUOTED_rev2} ) # 2: ROUTE
|
|
@@ -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
|
|
@@ -7,6 +7,12 @@ module Net
|
|
|
7
7
|
# identifiers returned by Net::IMAP#uid_search.
|
|
8
8
|
#
|
|
9
9
|
# For backward compatibility, SearchResult inherits from Array.
|
|
10
|
+
#
|
|
11
|
+
# ==== Compatibility with ESearchResult
|
|
12
|
+
#
|
|
13
|
+
# Note that both SearchResult and ESearchResult implement +each+, +to_a+,
|
|
14
|
+
# and +to_sequence_set+. These methods can be used regardless of whether
|
|
15
|
+
# the server returns +SEARCH+ or +ESEARCH+ data (or no data).
|
|
10
16
|
class SearchResult < Array
|
|
11
17
|
|
|
12
18
|
# Returns a SearchResult populated with the given +seq_nums+.
|
|
@@ -60,9 +66,8 @@ module Net
|
|
|
60
66
|
# [3, 5, 7] == Net::IMAP::SearchResult[3, 5, 7, modseq: 99] # => true
|
|
61
67
|
#
|
|
62
68
|
def ==(other)
|
|
63
|
-
(
|
|
64
|
-
|
|
65
|
-
other.is_a?(Array)) &&
|
|
69
|
+
other.is_a?(Array) &&
|
|
70
|
+
modseq == (other.modseq if other.respond_to?(:modseq)) &&
|
|
66
71
|
size == other.size &&
|
|
67
72
|
sort == other.sort
|
|
68
73
|
end
|