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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/COPYING +56 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +3 -22
  6. data/README.md +25 -8
  7. data/Rakefile +0 -7
  8. data/docs/styles.css +72 -23
  9. data/lib/net/imap/authenticators.rb +26 -57
  10. data/lib/net/imap/command_data.rb +74 -54
  11. data/lib/net/imap/config/attr_accessors.rb +75 -0
  12. data/lib/net/imap/config/attr_inheritance.rb +90 -0
  13. data/lib/net/imap/config/attr_type_coercion.rb +61 -0
  14. data/lib/net/imap/config.rb +470 -0
  15. data/lib/net/imap/data_encoding.rb +18 -6
  16. data/lib/net/imap/data_lite.rb +226 -0
  17. data/lib/net/imap/deprecated_client_options.rb +142 -0
  18. data/lib/net/imap/errors.rb +27 -1
  19. data/lib/net/imap/esearch_result.rb +180 -0
  20. data/lib/net/imap/fetch_data.rb +597 -0
  21. data/lib/net/imap/flags.rb +1 -1
  22. data/lib/net/imap/response_data.rb +250 -440
  23. data/lib/net/imap/response_parser/parser_utils.rb +245 -0
  24. data/lib/net/imap/response_parser.rb +1867 -1184
  25. data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
  26. data/lib/net/imap/sasl/authentication_exchange.rb +139 -0
  27. data/lib/net/imap/sasl/authenticators.rb +122 -0
  28. data/lib/net/imap/sasl/client_adapter.rb +123 -0
  29. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +24 -14
  30. data/lib/net/imap/sasl/digest_md5_authenticator.rb +342 -0
  31. data/lib/net/imap/sasl/external_authenticator.rb +83 -0
  32. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  33. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +28 -18
  34. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
  35. data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
  36. data/lib/net/imap/sasl/protocol_adapters.rb +101 -0
  37. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  38. data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
  39. data/lib/net/imap/sasl/stringprep.rb +6 -66
  40. data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
  41. data/lib/net/imap/sasl.rb +148 -44
  42. data/lib/net/imap/sasl_adapter.rb +20 -0
  43. data/lib/net/imap/search_result.rb +146 -0
  44. data/lib/net/imap/sequence_set.rb +1565 -0
  45. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  46. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  47. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  48. data/lib/net/imap/stringprep/tables.rb +146 -0
  49. data/lib/net/imap/stringprep/trace.rb +85 -0
  50. data/lib/net/imap/stringprep.rb +159 -0
  51. data/lib/net/imap/uidplus_data.rb +244 -0
  52. data/lib/net/imap/vanished_data.rb +56 -0
  53. data/lib/net/imap.rb +2090 -823
  54. data/net-imap.gemspec +7 -8
  55. data/rakelib/benchmarks.rake +91 -0
  56. data/rakelib/rfcs.rake +2 -0
  57. data/rakelib/saslprep.rake +4 -4
  58. data/rakelib/string_prep_tables_generator.rb +84 -60
  59. data/sample/net-imap.rb +167 -0
  60. metadata +45 -49
  61. data/.github/dependabot.yml +0 -6
  62. data/.github/workflows/test.yml +0 -38
  63. data/.gitignore +0 -10
  64. data/benchmarks/stringprep.yml +0 -65
  65. data/benchmarks/table-regexps.yml +0 -39
  66. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  67. data/lib/net/imap/authenticators/plain.rb +0 -41
  68. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  69. data/lib/net/imap/sasl/saslprep.rb +0 -55
  70. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  71. 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