net-imap 0.4.10 → 0.4.20
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/BSDL +22 -0
- data/COPYING +56 -0
- data/LICENSE.txt +3 -22
- data/lib/net/imap/command_data.rb +2 -2
- data/lib/net/imap/config/attr_accessors.rb +75 -0
- data/lib/net/imap/config/attr_inheritance.rb +90 -0
- data/lib/net/imap/config/attr_type_coercion.rb +65 -0
- data/lib/net/imap/config.rb +524 -0
- data/lib/net/imap/data_encoding.rb +1 -1
- data/lib/net/imap/deprecated_client_options.rb +3 -3
- data/lib/net/imap/errors.rb +34 -1
- data/lib/net/imap/flags.rb +1 -1
- data/lib/net/imap/response_data.rb +8 -59
- data/lib/net/imap/response_parser/parser_utils.rb +6 -6
- data/lib/net/imap/response_parser.rb +35 -15
- data/lib/net/imap/response_reader.rb +75 -0
- data/lib/net/imap/sasl/external_authenticator.rb +1 -1
- data/lib/net/imap/sasl.rb +1 -1
- data/lib/net/imap/sequence_set.rb +267 -121
- data/lib/net/imap/uidplus_data.rb +326 -0
- data/lib/net/imap.rb +299 -88
- data/net-imap.gemspec +3 -3
- data/rakelib/benchmarks.rake +1 -1
- metadata +12 -11
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/pages.yml +0 -46
- data/.github/workflows/test.yml +0 -31
- data/.gitignore +0 -12
@@ -5,6 +5,9 @@ module Net
|
|
5
5
|
autoload :FetchData, "#{__dir__}/fetch_data"
|
6
6
|
autoload :SearchResult, "#{__dir__}/search_result"
|
7
7
|
autoload :SequenceSet, "#{__dir__}/sequence_set"
|
8
|
+
autoload :UIDPlusData, "#{__dir__}/uidplus_data"
|
9
|
+
autoload :AppendUIDData, "#{__dir__}/uidplus_data"
|
10
|
+
autoload :CopyUIDData, "#{__dir__}/uidplus_data"
|
8
11
|
|
9
12
|
# Net::IMAP::ContinuationRequest represents command continuation requests.
|
10
13
|
#
|
@@ -58,7 +61,7 @@ module Net
|
|
58
61
|
|
59
62
|
# Net::IMAP::IgnoredResponse represents intentionally ignored responses.
|
60
63
|
#
|
61
|
-
# This includes untagged response "NOOP" sent by
|
64
|
+
# This includes untagged response "NOOP" sent by e.g. Zimbra to avoid
|
62
65
|
# some clients to close the connection.
|
63
66
|
#
|
64
67
|
# It matches no IMAP standard.
|
@@ -280,7 +283,7 @@ module Net
|
|
280
283
|
# ==== +QRESYNC+ extension
|
281
284
|
# See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
|
282
285
|
# * +CLOSED+, returned when the currently selected mailbox is closed
|
283
|
-
#
|
286
|
+
# implicitly by selecting or examining another mailbox. #data is +nil+.
|
284
287
|
#
|
285
288
|
# ==== +IMAP4rev2+ Response Codes
|
286
289
|
# See {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051] {§7.1, "Server
|
@@ -324,60 +327,6 @@ module Net
|
|
324
327
|
# code data can take.
|
325
328
|
end
|
326
329
|
|
327
|
-
# Net::IMAP::UIDPlusData represents the ResponseCode#data that accompanies
|
328
|
-
# the +APPENDUID+ and +COPYUID+ response codes.
|
329
|
-
#
|
330
|
-
# See [[UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]].
|
331
|
-
#
|
332
|
-
# ==== Capability requirement
|
333
|
-
#
|
334
|
-
# The +UIDPLUS+ capability[rdoc-ref:Net::IMAP#capability] must be supported.
|
335
|
-
# A server that supports +UIDPLUS+ should send a UIDPlusData object inside
|
336
|
-
# every TaggedResponse returned by the append[rdoc-ref:Net::IMAP#append],
|
337
|
-
# copy[rdoc-ref:Net::IMAP#copy], move[rdoc-ref:Net::IMAP#move], {uid
|
338
|
-
# copy}[rdoc-ref:Net::IMAP#uid_copy], and {uid
|
339
|
-
# move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the destination
|
340
|
-
# mailbox reports +UIDNOTSTICKY+.
|
341
|
-
#
|
342
|
-
#--
|
343
|
-
# TODO: support MULTIAPPEND
|
344
|
-
#++
|
345
|
-
#
|
346
|
-
class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
|
347
|
-
##
|
348
|
-
# method: uidvalidity
|
349
|
-
# :call-seq: uidvalidity -> nonzero uint32
|
350
|
-
#
|
351
|
-
# The UIDVALIDITY of the destination mailbox.
|
352
|
-
|
353
|
-
##
|
354
|
-
# method: source_uids
|
355
|
-
# :call-seq: source_uids -> nil or an array of nonzero uint32
|
356
|
-
#
|
357
|
-
# The UIDs of the copied or moved messages.
|
358
|
-
#
|
359
|
-
# Note:: Returns +nil+ for Net::IMAP#append.
|
360
|
-
|
361
|
-
##
|
362
|
-
# method: assigned_uids
|
363
|
-
# :call-seq: assigned_uids -> an array of nonzero uint32
|
364
|
-
#
|
365
|
-
# The newly assigned UIDs of the copied, moved, or appended messages.
|
366
|
-
#
|
367
|
-
# Note:: This always returns an array, even when it contains only one UID.
|
368
|
-
|
369
|
-
##
|
370
|
-
# :call-seq: uid_mapping -> nil or a hash
|
371
|
-
#
|
372
|
-
# Returns a hash mapping each source UID to the newly assigned destination
|
373
|
-
# UID.
|
374
|
-
#
|
375
|
-
# Note:: Returns +nil+ for Net::IMAP#append.
|
376
|
-
def uid_mapping
|
377
|
-
source_uids&.zip(assigned_uids)&.to_h
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
330
|
# Net::IMAP::MailboxList represents contents of the LIST response,
|
382
331
|
# representing a single mailbox path.
|
383
332
|
#
|
@@ -1045,7 +994,7 @@ module Net
|
|
1045
994
|
# === Bug Analysis
|
1046
995
|
#
|
1047
996
|
# \IMAP body structures are parenthesized lists and assign their fields
|
1048
|
-
# positionally, so missing fields change the
|
997
|
+
# positionally, so missing fields change the interpretation of all
|
1049
998
|
# following fields. Additionally, different body types have a different
|
1050
999
|
# number of required fields, followed by optional "extension" fields.
|
1051
1000
|
#
|
@@ -1060,7 +1009,7 @@ module Net
|
|
1060
1009
|
# Normally, +envelope+ and +md5+ are incompatible, but Net::IMAP leniently
|
1061
1010
|
# allowed buggy servers to send +NIL+ for +envelope+. As a result, when a
|
1062
1011
|
# server sent a <tt>message/rfc822</tt> part with +NIL+ for +md5+ and a
|
1063
|
-
# non-<tt>NIL</tt> +dsp+, Net::IMAP
|
1012
|
+
# non-<tt>NIL</tt> +dsp+, Net::IMAP misinterpreted the
|
1064
1013
|
# <tt>Content-Disposition</tt> as if it were a strange body type. In all
|
1065
1014
|
# reported cases, the <tt>Content-Disposition</tt> was "attachment", so
|
1066
1015
|
# BodyTypeAttachment was created as the workaround.
|
@@ -1068,7 +1017,7 @@ module Net
|
|
1068
1017
|
# === Current behavior
|
1069
1018
|
#
|
1070
1019
|
# When interpreted strictly, +envelope+ and +md5+ are incompatible. So the
|
1071
|
-
# current parsing algorithm peeks ahead after it has
|
1020
|
+
# current parsing algorithm peeks ahead after it has received the seventh
|
1072
1021
|
# body field. If the next token is not the start of an +envelope+, we assume
|
1073
1022
|
# the server has incorrectly sent us a <tt>body-type-basic</tt> and return
|
1074
1023
|
# BodyTypeBasic. As a result, what was previously BodyTypeMessage#body =>
|
@@ -154,7 +154,7 @@ module Net
|
|
154
154
|
end
|
155
155
|
|
156
156
|
# To be used conditionally:
|
157
|
-
# assert_no_lookahead if
|
157
|
+
# assert_no_lookahead if config.debug?
|
158
158
|
def assert_no_lookahead
|
159
159
|
@token.nil? or
|
160
160
|
parse_error("assertion failed: expected @token.nil?, actual %s: %p",
|
@@ -181,23 +181,23 @@ module Net
|
|
181
181
|
end
|
182
182
|
|
183
183
|
def peek_str?(str)
|
184
|
-
assert_no_lookahead if
|
184
|
+
assert_no_lookahead if config.debug?
|
185
185
|
@str[@pos, str.length] == str
|
186
186
|
end
|
187
187
|
|
188
188
|
def peek_re(re)
|
189
|
-
assert_no_lookahead if
|
189
|
+
assert_no_lookahead if config.debug?
|
190
190
|
re.match(@str, @pos)
|
191
191
|
end
|
192
192
|
|
193
193
|
def accept_re(re)
|
194
|
-
assert_no_lookahead if
|
194
|
+
assert_no_lookahead if config.debug?
|
195
195
|
re.match(@str, @pos) and @pos = $~.end(0)
|
196
196
|
$~
|
197
197
|
end
|
198
198
|
|
199
199
|
def match_re(re, name)
|
200
|
-
assert_no_lookahead if
|
200
|
+
assert_no_lookahead if config.debug?
|
201
201
|
if re.match(@str, @pos)
|
202
202
|
@pos = $~.end(0)
|
203
203
|
$~
|
@@ -212,7 +212,7 @@ module Net
|
|
212
212
|
|
213
213
|
def parse_error(fmt, *args)
|
214
214
|
msg = format(fmt, *args)
|
215
|
-
if
|
215
|
+
if config.debug?
|
216
216
|
local_path = File.dirname(__dir__)
|
217
217
|
tok = @token ? "%s: %p" % [@token.symbol, @token.value] : "nil"
|
218
218
|
warn "%s %s: %s" % [self.class, __method__, msg]
|
@@ -11,12 +11,19 @@ module Net
|
|
11
11
|
include ParserUtils
|
12
12
|
extend ParserUtils::Generator
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
attr_reader :config
|
15
|
+
|
16
|
+
# Creates a new ResponseParser.
|
17
|
+
#
|
18
|
+
# When +config+ is frozen or global, the parser #config inherits from it.
|
19
|
+
# Otherwise, +config+ will be used directly.
|
20
|
+
def initialize(config: Config.global)
|
16
21
|
@str = nil
|
17
22
|
@pos = nil
|
18
23
|
@lex_state = nil
|
19
24
|
@token = nil
|
25
|
+
@config = Config[config]
|
26
|
+
@config = @config.new if @config == Config.global || @config.frozen?
|
20
27
|
end
|
21
28
|
|
22
29
|
# :call-seq:
|
@@ -1155,6 +1162,7 @@ module Net
|
|
1155
1162
|
# RFC3501, RFC9051:
|
1156
1163
|
# body-fld-param = "(" string SP string *(SP string SP string) ")" / nil
|
1157
1164
|
def body_fld_param
|
1165
|
+
quirky_SP? # See comments on test_bodystructure_extra_space
|
1158
1166
|
return if NIL?
|
1159
1167
|
param = {}
|
1160
1168
|
lpar
|
@@ -1335,7 +1343,8 @@ module Net
|
|
1335
1343
|
assert_no_lookahead
|
1336
1344
|
start = @pos
|
1337
1345
|
astring
|
1338
|
-
@
|
1346
|
+
end_pos = @token ? @pos - 1 : @pos
|
1347
|
+
@str[start...end_pos]
|
1339
1348
|
end
|
1340
1349
|
|
1341
1350
|
# mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list /
|
@@ -1858,11 +1867,10 @@ module Net
|
|
1858
1867
|
#
|
1859
1868
|
# n.b, uniqueid ⊂ uid-set. To avoid inconsistent return types, we always
|
1860
1869
|
# match uid_set even if that returns a single-member array.
|
1861
|
-
#
|
1862
1870
|
def resp_code_apnd__data
|
1863
1871
|
validity = number; SP!
|
1864
1872
|
dst_uids = uid_set # uniqueid ⊂ uid-set
|
1865
|
-
|
1873
|
+
AppendUID(validity, dst_uids)
|
1866
1874
|
end
|
1867
1875
|
|
1868
1876
|
# already matched: "COPYUID"
|
@@ -1872,7 +1880,25 @@ module Net
|
|
1872
1880
|
validity = number; SP!
|
1873
1881
|
src_uids = uid_set; SP!
|
1874
1882
|
dst_uids = uid_set
|
1875
|
-
|
1883
|
+
CopyUID(validity, src_uids, dst_uids)
|
1884
|
+
end
|
1885
|
+
|
1886
|
+
def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
|
1887
|
+
def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end
|
1888
|
+
|
1889
|
+
# TODO: remove this code in the v0.6.0 release
|
1890
|
+
def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids)
|
1891
|
+
return unless config.parser_use_deprecated_uidplus_data
|
1892
|
+
compact_uid_sets = [src_uids, dst_uids].compact
|
1893
|
+
count = compact_uid_sets.map { _1.count_with_duplicates }.max
|
1894
|
+
max = config.parser_max_deprecated_uidplus_data_size
|
1895
|
+
if count <= max
|
1896
|
+
src_uids &&= src_uids.each_ordered_number.to_a
|
1897
|
+
dst_uids = dst_uids.each_ordered_number.to_a
|
1898
|
+
UIDPlusData.new(validity, src_uids, dst_uids)
|
1899
|
+
elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size
|
1900
|
+
parse_error("uid-set is too large: %d > %d", count, max)
|
1901
|
+
end
|
1876
1902
|
end
|
1877
1903
|
|
1878
1904
|
ADDRESS_REGEXP = /\G
|
@@ -1998,15 +2024,9 @@ module Net
|
|
1998
2024
|
# uniqueid = nz-number
|
1999
2025
|
# ; Strictly ascending
|
2000
2026
|
def uid_set
|
2001
|
-
|
2002
|
-
|
2003
|
-
|
2004
|
-
when T_ATOM
|
2005
|
-
token.value.split(",").flat_map {|range|
|
2006
|
-
range = range.split(":").map {|uniqueid| Integer(uniqueid) }
|
2007
|
-
range.size == 1 ? range : Range.new(range.min, range.max).to_a
|
2008
|
-
}
|
2009
|
-
end
|
2027
|
+
set = sequence_set
|
2028
|
+
parse_error("uid-set cannot contain '*'") if set.include_star?
|
2029
|
+
set
|
2010
2030
|
end
|
2011
2031
|
|
2012
2032
|
def nil_atom
|
@@ -0,0 +1,75 @@
|
|
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 end
|
32
|
+
def empty?; buff.empty? end
|
33
|
+
def done?; line_done? && !get_literal_size end
|
34
|
+
def line_done?; buff.end_with?(CRLF) end
|
35
|
+
def get_literal_size; /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i end
|
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 end
|
56
|
+
def max_response_remaining; max_response_size &.- bytes_read end
|
57
|
+
def response_too_large?; max_response_size &.< min_response_size end
|
58
|
+
def min_response_size; bytes_read + min_response_remaining end
|
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: max_response_size,
|
68
|
+
bytes_read: bytes_read,
|
69
|
+
literal_size: literal_size,
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -9,7 +9,7 @@ module Net
|
|
9
9
|
# Net::IMAP#authenticate.
|
10
10
|
#
|
11
11
|
# The EXTERNAL mechanism requests that the server use client credentials
|
12
|
-
# established external to SASL, for example by TLS certificate or
|
12
|
+
# established external to SASL, for example by TLS certificate or IPSec.
|
13
13
|
class ExternalAuthenticator
|
14
14
|
|
15
15
|
# Authorization identity: an identity to act as or on behalf of. The
|
data/lib/net/imap/sasl.rb
CHANGED