net-imap 0.3.7 → 0.5.6
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/Gemfile +14 -0
- data/LICENSE.txt +3 -22
- data/README.md +25 -8
- data/Rakefile +0 -7
- data/docs/styles.css +72 -23
- data/lib/net/imap/authenticators.rb +26 -57
- data/lib/net/imap/command_data.rb +74 -54
- 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 +61 -0
- data/lib/net/imap/config.rb +470 -0
- data/lib/net/imap/data_encoding.rb +18 -6
- data/lib/net/imap/data_lite.rb +226 -0
- data/lib/net/imap/deprecated_client_options.rb +142 -0
- data/lib/net/imap/errors.rb +27 -1
- data/lib/net/imap/esearch_result.rb +180 -0
- data/lib/net/imap/fetch_data.rb +597 -0
- data/lib/net/imap/flags.rb +1 -1
- data/lib/net/imap/response_data.rb +250 -440
- data/lib/net/imap/response_parser/parser_utils.rb +245 -0
- data/lib/net/imap/response_parser.rb +1867 -1184
- data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
- data/lib/net/imap/sasl/authentication_exchange.rb +139 -0
- data/lib/net/imap/sasl/authenticators.rb +122 -0
- data/lib/net/imap/sasl/client_adapter.rb +123 -0
- data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +24 -14
- data/lib/net/imap/sasl/digest_md5_authenticator.rb +342 -0
- data/lib/net/imap/sasl/external_authenticator.rb +83 -0
- data/lib/net/imap/sasl/gs2_header.rb +80 -0
- data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +28 -18
- data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
- data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
- data/lib/net/imap/sasl/protocol_adapters.rb +101 -0
- data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
- data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
- data/lib/net/imap/sasl/stringprep.rb +6 -66
- data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
- data/lib/net/imap/sasl.rb +148 -44
- data/lib/net/imap/sasl_adapter.rb +20 -0
- data/lib/net/imap/search_result.rb +146 -0
- data/lib/net/imap/sequence_set.rb +1565 -0
- data/lib/net/imap/stringprep/nameprep.rb +70 -0
- data/lib/net/imap/stringprep/saslprep.rb +69 -0
- data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
- data/lib/net/imap/stringprep/tables.rb +146 -0
- data/lib/net/imap/stringprep/trace.rb +85 -0
- data/lib/net/imap/stringprep.rb +159 -0
- data/lib/net/imap/uidplus_data.rb +244 -0
- data/lib/net/imap/vanished_data.rb +56 -0
- data/lib/net/imap.rb +2090 -823
- data/net-imap.gemspec +7 -8
- data/rakelib/benchmarks.rake +91 -0
- data/rakelib/rfcs.rake +2 -0
- data/rakelib/saslprep.rake +4 -4
- data/rakelib/string_prep_tables_generator.rb +84 -60
- data/sample/net-imap.rb +167 -0
- metadata +45 -49
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/test.yml +0 -38
- data/.gitignore +0 -10
- data/benchmarks/stringprep.yml +0 -65
- data/benchmarks/table-regexps.yml +0 -39
- data/lib/net/imap/authenticators/digest_md5.rb +0 -115
- data/lib/net/imap/authenticators/plain.rb +0 -41
- data/lib/net/imap/authenticators/xoauth2.rb +0 -20
- data/lib/net/imap/sasl/saslprep.rb +0 -55
- data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
- data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
@@ -0,0 +1,245 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP < Protocol
|
5
|
+
class ResponseParser
|
6
|
+
# basic utility methods for parsing.
|
7
|
+
#
|
8
|
+
# (internal API, subject to change)
|
9
|
+
module ParserUtils # :nodoc:
|
10
|
+
|
11
|
+
module Generator # :nodoc:
|
12
|
+
|
13
|
+
LOOKAHEAD = "(@token ||= next_token)"
|
14
|
+
SHIFT_TOKEN = "(@token = nil)"
|
15
|
+
|
16
|
+
# we can skip lexer for single character matches, as a shortcut
|
17
|
+
def def_char_matchers(name, char, token)
|
18
|
+
byte = char.ord
|
19
|
+
match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name
|
20
|
+
char = char.dump
|
21
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
22
|
+
# frozen_string_literal: true
|
23
|
+
|
24
|
+
# force use of #next_token; no string peeking
|
25
|
+
def lookahead_#{name}?
|
26
|
+
#{LOOKAHEAD}&.symbol == #{token}
|
27
|
+
end
|
28
|
+
|
29
|
+
# use token or string peek
|
30
|
+
def peek_#{name}?
|
31
|
+
@token ? @token.symbol == #{token} : @str.getbyte(@pos) == #{byte}
|
32
|
+
end
|
33
|
+
|
34
|
+
# like accept(token_symbols); returns token or nil
|
35
|
+
def #{name}?
|
36
|
+
if @token&.symbol == #{token}
|
37
|
+
#{SHIFT_TOKEN}
|
38
|
+
#{char}
|
39
|
+
elsif !@token && @str.getbyte(@pos) == #{byte}
|
40
|
+
@pos += 1
|
41
|
+
#{char}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# like match(token_symbols); returns token or raises parse_error
|
46
|
+
def #{match_name}
|
47
|
+
if @token&.symbol == #{token}
|
48
|
+
#{SHIFT_TOKEN}
|
49
|
+
#{char}
|
50
|
+
elsif !@token && @str.getbyte(@pos) == #{byte}
|
51
|
+
@pos += 1
|
52
|
+
#{char}
|
53
|
+
else
|
54
|
+
parse_error("unexpected %s (expected %p)",
|
55
|
+
@token&.symbol || @str[@pos].inspect, #{char})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
RUBY
|
59
|
+
end
|
60
|
+
|
61
|
+
# TODO: move coersion to the token.value method?
|
62
|
+
def def_token_matchers(name, *token_symbols, coerce: nil, send: nil)
|
63
|
+
match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name
|
64
|
+
|
65
|
+
if token_symbols.size == 1
|
66
|
+
token = token_symbols.first
|
67
|
+
matcher = "token&.symbol == %p" % [token]
|
68
|
+
desc = token
|
69
|
+
else
|
70
|
+
matcher = "%p.include? token&.symbol" % [token_symbols]
|
71
|
+
desc = token_symbols.join(" or ")
|
72
|
+
end
|
73
|
+
|
74
|
+
value = "(token.value)"
|
75
|
+
value = coerce.to_s + value if coerce
|
76
|
+
value = [value, send].join(".") if send
|
77
|
+
|
78
|
+
raise_parse_error = <<~RUBY
|
79
|
+
parse_error("unexpected %s (expected #{desc})", token&.symbol)
|
80
|
+
RUBY
|
81
|
+
|
82
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
83
|
+
# frozen_string_literal: true
|
84
|
+
|
85
|
+
# lookahead version of match, returning the value
|
86
|
+
def lookahead_#{name}!
|
87
|
+
token = #{LOOKAHEAD}
|
88
|
+
if #{matcher}
|
89
|
+
#{value}
|
90
|
+
else
|
91
|
+
#{raise_parse_error}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def #{name}?
|
96
|
+
token = #{LOOKAHEAD}
|
97
|
+
if #{matcher}
|
98
|
+
#{SHIFT_TOKEN}
|
99
|
+
#{value}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def #{match_name}
|
104
|
+
token = #{LOOKAHEAD}
|
105
|
+
if #{matcher}
|
106
|
+
#{SHIFT_TOKEN}
|
107
|
+
#{value}
|
108
|
+
else
|
109
|
+
#{raise_parse_error}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
RUBY
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# TODO: after checking the lookahead, use a regexp for remaining chars.
|
120
|
+
# That way a loop isn't needed.
|
121
|
+
def combine_adjacent(*tokens)
|
122
|
+
result = "".b
|
123
|
+
while token = accept(*tokens)
|
124
|
+
result << token.value
|
125
|
+
end
|
126
|
+
if result.empty?
|
127
|
+
parse_error('unexpected token %s (expected %s)',
|
128
|
+
lookahead.symbol, tokens.join(" or "))
|
129
|
+
end
|
130
|
+
result
|
131
|
+
end
|
132
|
+
|
133
|
+
def match(*args)
|
134
|
+
token = lookahead
|
135
|
+
unless args.include?(token.symbol)
|
136
|
+
parse_error('unexpected token %s (expected %s)',
|
137
|
+
token.symbol.id2name,
|
138
|
+
args.collect {|i| i.id2name}.join(" or "))
|
139
|
+
end
|
140
|
+
shift_token
|
141
|
+
token
|
142
|
+
end
|
143
|
+
|
144
|
+
# like match, but does not raise error on failure.
|
145
|
+
#
|
146
|
+
# returns and shifts token on successful match
|
147
|
+
# returns nil and leaves @token unshifted on no match
|
148
|
+
def accept(*args)
|
149
|
+
token = lookahead
|
150
|
+
if args.include?(token.symbol)
|
151
|
+
shift_token
|
152
|
+
token
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# To be used conditionally:
|
157
|
+
# assert_no_lookahead if config.debug?
|
158
|
+
def assert_no_lookahead
|
159
|
+
@token.nil? or
|
160
|
+
parse_error("assertion failed: expected @token.nil?, actual %s: %p",
|
161
|
+
@token.symbol, @token.value)
|
162
|
+
end
|
163
|
+
|
164
|
+
# like accept, without consuming the token
|
165
|
+
def lookahead?(*symbols)
|
166
|
+
@token if symbols.include?((@token ||= next_token)&.symbol)
|
167
|
+
end
|
168
|
+
|
169
|
+
def lookahead
|
170
|
+
@token ||= next_token
|
171
|
+
end
|
172
|
+
|
173
|
+
# like match, without consuming the token
|
174
|
+
def lookahead!(*args)
|
175
|
+
if args.include?((@token ||= next_token)&.symbol)
|
176
|
+
@token
|
177
|
+
else
|
178
|
+
parse_error('unexpected token %s (expected %s)',
|
179
|
+
@token&.symbol, args.join(" or "))
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def peek_str?(str)
|
184
|
+
assert_no_lookahead if config.debug?
|
185
|
+
@str[@pos, str.length] == str
|
186
|
+
end
|
187
|
+
|
188
|
+
def peek_re?(re)
|
189
|
+
assert_no_lookahead if Net::IMAP.debug
|
190
|
+
re.match?(@str, @pos)
|
191
|
+
end
|
192
|
+
|
193
|
+
def peek_re(re)
|
194
|
+
assert_no_lookahead if config.debug?
|
195
|
+
re.match(@str, @pos)
|
196
|
+
end
|
197
|
+
|
198
|
+
def accept_re(re)
|
199
|
+
assert_no_lookahead if config.debug?
|
200
|
+
re.match(@str, @pos) and @pos = $~.end(0)
|
201
|
+
$~
|
202
|
+
end
|
203
|
+
|
204
|
+
def match_re(re, name)
|
205
|
+
assert_no_lookahead if config.debug?
|
206
|
+
if re.match(@str, @pos)
|
207
|
+
@pos = $~.end(0)
|
208
|
+
$~
|
209
|
+
else
|
210
|
+
parse_error("invalid #{name}")
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def shift_token
|
215
|
+
@token = nil
|
216
|
+
end
|
217
|
+
|
218
|
+
def parse_error(fmt, *args)
|
219
|
+
msg = format(fmt, *args)
|
220
|
+
if config.debug?
|
221
|
+
local_path = File.dirname(__dir__)
|
222
|
+
tok = @token ? "%s: %p" % [@token.symbol, @token.value] : "nil"
|
223
|
+
warn "%s %s: %s" % [self.class, __method__, msg]
|
224
|
+
warn " tokenized : %s" % [@str[...@pos].dump]
|
225
|
+
warn " remaining : %s" % [@str[@pos..].dump]
|
226
|
+
warn " @lex_state: %s" % [@lex_state]
|
227
|
+
warn " @pos : %d" % [@pos]
|
228
|
+
warn " @token : %s" % [tok]
|
229
|
+
caller_locations(1..20).each_with_index do |cloc, idx|
|
230
|
+
next unless cloc.path&.start_with?(local_path)
|
231
|
+
warn " caller[%2d]: %-30s (%s:%d)" % [
|
232
|
+
idx,
|
233
|
+
cloc.base_label,
|
234
|
+
File.basename(cloc.path, ".rb"),
|
235
|
+
cloc.lineno
|
236
|
+
]
|
237
|
+
end
|
238
|
+
end
|
239
|
+
raise ResponseParseError, msg
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|