net-imap 0.6.3 → 0.6.4
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/.document +3 -0
- data/.rdoc_options +7 -0
- data/Gemfile +1 -1
- data/README.md +1 -1
- data/lib/net/imap/command_data.rb +170 -12
- data/lib/net/imap/config.rb +34 -0
- data/lib/net/imap/data_encoding.rb +50 -0
- data/lib/net/imap/errors.rb +47 -10
- data/lib/net/imap/response_data.rb +108 -11
- data/lib/net/imap/response_parser.rb +13 -0
- data/lib/net/imap/response_reader.rb +25 -16
- data/lib/net/imap/sasl/scram_authenticator.rb +74 -0
- data/lib/net/imap/search_result.rb +5 -1
- data/lib/net/imap.rb +167 -61
- data/rakelib/rdoc.rake +1 -18
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ffcec5e3cc42d7f72c63520110ec442f760c35472401de495001f420cfa059c5
|
|
4
|
+
data.tar.gz: aedd703997fc651cb6c67635a3d20a4159720c6e1bda41c07322f2bd67a627d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1fa95302f29607d152b991535a9d14cda5dae1a1fce06b1d831dd2b09f69b2fd845367e0ebed73dd4e79d04c064a801e59865e76947060a4811304728458f3c7
|
|
7
|
+
data.tar.gz: 2dc9a56758d6f370affc9540a8ca26b8ad866ca5ef0e5b9bc2c30ff6257e3461e5ed23173e1c5e98a2e95c1e0f0a844c96f9e34d41151e9196ab513ac6987d33
|
data/.document
ADDED
data/.rdoc_options
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -61,9 +61,9 @@ imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_i
|
|
|
61
61
|
else
|
|
62
62
|
imap.copy(message_id, "Mail/sent-apr03")
|
|
63
63
|
imap.store(message_id, "+FLAGS", [:Deleted])
|
|
64
|
+
imap.expunge
|
|
64
65
|
end
|
|
65
66
|
end
|
|
66
|
-
imap.expunge
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
## Development
|
|
@@ -4,6 +4,8 @@ require "date"
|
|
|
4
4
|
|
|
5
5
|
require_relative "errors"
|
|
6
6
|
|
|
7
|
+
# :enddoc:
|
|
8
|
+
|
|
7
9
|
module Net
|
|
8
10
|
class IMAP < Protocol
|
|
9
11
|
|
|
@@ -25,6 +27,7 @@ module Net
|
|
|
25
27
|
end
|
|
26
28
|
when Time, Date, DateTime
|
|
27
29
|
when Symbol
|
|
30
|
+
Flag.validate(data)
|
|
28
31
|
else
|
|
29
32
|
data.validate
|
|
30
33
|
end
|
|
@@ -45,7 +48,7 @@ module Net
|
|
|
45
48
|
when Date
|
|
46
49
|
send_date_data(data)
|
|
47
50
|
when Symbol
|
|
48
|
-
|
|
51
|
+
Flag[data].send_data(self, tag)
|
|
49
52
|
else
|
|
50
53
|
data.send_data(self, tag)
|
|
51
54
|
end
|
|
@@ -77,9 +80,23 @@ module Net
|
|
|
77
80
|
put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"')
|
|
78
81
|
end
|
|
79
82
|
|
|
80
|
-
def
|
|
83
|
+
def send_binary_literal(*, **) = send_literal(*, **, binary: true)
|
|
84
|
+
|
|
85
|
+
# `non_sync` is an optional tri-state flag:
|
|
86
|
+
# * `true` -> Force non-synchronizing `LITERAL+`/`LITERAL-` behavior.
|
|
87
|
+
# TODO: raise or warn when capabilities don't allow non_sync.
|
|
88
|
+
# * `false` -> Force normal synchronizing literal behavior.
|
|
89
|
+
# * `nil` -> (default) Currently behaves like `false` (will be dynamic).
|
|
90
|
+
def send_literal(str, tag = nil, binary: false, non_sync: nil)
|
|
81
91
|
synchronize do
|
|
82
|
-
|
|
92
|
+
non_sync = non_sync_literal?(str.bytesize) if non_sync.nil?
|
|
93
|
+
prefix = "~" if binary
|
|
94
|
+
plus = "+" if non_sync
|
|
95
|
+
put_string("#{prefix}{#{str.bytesize}#{plus}}\r\n")
|
|
96
|
+
if non_sync
|
|
97
|
+
put_string(str)
|
|
98
|
+
return
|
|
99
|
+
end
|
|
83
100
|
@continued_command_tag = tag
|
|
84
101
|
@continuation_request_exception = nil
|
|
85
102
|
begin
|
|
@@ -94,6 +111,13 @@ module Net
|
|
|
94
111
|
end
|
|
95
112
|
end
|
|
96
113
|
|
|
114
|
+
def non_sync_literal?(bytesize)
|
|
115
|
+
capabilities_cached? &&
|
|
116
|
+
bytesize <= config.max_non_synchronizing_literal &&
|
|
117
|
+
(capable?("LITERAL+") ||
|
|
118
|
+
bytesize <= 4096 && (capable?("IMAP4rev2") || capable?("LITERAL-")))
|
|
119
|
+
end
|
|
120
|
+
|
|
97
121
|
def send_number_data(num)
|
|
98
122
|
put_string(num.to_s)
|
|
99
123
|
end
|
|
@@ -115,11 +139,13 @@ module Net
|
|
|
115
139
|
def send_date_data(date) put_string Net::IMAP.encode_date(date) end
|
|
116
140
|
def send_time_data(time) put_string Net::IMAP.encode_time(time) end
|
|
117
141
|
|
|
118
|
-
def send_symbol_data(symbol)
|
|
119
|
-
put_string("\\" + symbol.to_s)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
142
|
CommandData = Data.define(:data) do # :nodoc:
|
|
143
|
+
def self.validate(...)
|
|
144
|
+
data = new(...)
|
|
145
|
+
data.validate
|
|
146
|
+
data
|
|
147
|
+
end
|
|
148
|
+
|
|
123
149
|
def send_data(imap, tag)
|
|
124
150
|
raise NoMethodError, "#{self.class} must implement #{__method__}"
|
|
125
151
|
end
|
|
@@ -128,15 +154,109 @@ module Net
|
|
|
128
154
|
end
|
|
129
155
|
end
|
|
130
156
|
|
|
157
|
+
# Represents IMAP +text+ data, which may contain any 7-bit ASCII character,
|
|
158
|
+
# except for +NULL+, +CR+, or +LF+. +text+ is extended to allow any
|
|
159
|
+
# multibyte +UTF-8+ character when either +UTF8=ACCEPT+ or +IMAP4rev2+ have
|
|
160
|
+
# been enabled, or when the server supports only +IMAP4rev2+ and not earlier
|
|
161
|
+
# IMAP revisions, or when the server advertises +UTF8=ONLY+.
|
|
162
|
+
#
|
|
163
|
+
# NOTE: The current implementation does not validate whether the connection
|
|
164
|
+
# currently supports UTF-8. Future versions may change.
|
|
165
|
+
#
|
|
166
|
+
# The string's bytes must be valid ASCII or valid UTF-8. The string's
|
|
167
|
+
# reported encoding is ignored, but the string is _not_ transcoded.
|
|
168
|
+
class RawText < CommandData # :nodoc:
|
|
169
|
+
def initialize(data:)
|
|
170
|
+
data = String(data.to_str)
|
|
171
|
+
data = if data.encoding in Encoding::ASCII | Encoding::UTF_8
|
|
172
|
+
-data
|
|
173
|
+
elsif data.ascii_only?
|
|
174
|
+
-(data.dup.force_encoding("ASCII"))
|
|
175
|
+
else
|
|
176
|
+
-(data.dup.force_encoding("UTF-8"))
|
|
177
|
+
end
|
|
178
|
+
super
|
|
179
|
+
validate
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def validate
|
|
183
|
+
if data.include?("\0")
|
|
184
|
+
raise DataFormatError, "NULL byte must be binary literal encoded"
|
|
185
|
+
elsif !data.valid_encoding?
|
|
186
|
+
raise DataFormatError, "invalid UTF-8 must be literal encoded"
|
|
187
|
+
elsif /[\r\n]/.match?(data)
|
|
188
|
+
raise DataFormatError, "CR and LF bytes must be literal encoded"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def ascii_only? = data.ascii_only?
|
|
193
|
+
|
|
194
|
+
def send_data(imap, tag) = imap.__send__(:put_string, data)
|
|
195
|
+
end
|
|
196
|
+
|
|
131
197
|
class RawData < CommandData # :nodoc:
|
|
132
|
-
def
|
|
133
|
-
|
|
198
|
+
def initialize(data:)
|
|
199
|
+
data = split_parts(data)
|
|
200
|
+
super
|
|
201
|
+
validate
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def send_data(imap, tag) = data.each do _1.send_data(imap, tag) end
|
|
205
|
+
|
|
206
|
+
def validate
|
|
207
|
+
return unless data.last in RawText(data: text)
|
|
208
|
+
if text.rindex(/~?\{[1-9]\d*\+?\}\z/n)
|
|
209
|
+
raise DataFormatError, "RawData cannot end with literal continuation"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
private
|
|
214
|
+
|
|
215
|
+
def split_parts(data)
|
|
216
|
+
data = data.b # dups and ensures BINARY encoding
|
|
217
|
+
parts = []
|
|
218
|
+
while data.match(/(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n)
|
|
219
|
+
text, binary, bytesize, non_sync, data = $`, !!$1, $2, !!$3, $'
|
|
220
|
+
bytesize = NumValidator.coerce_number64 bytesize
|
|
221
|
+
parts << RawText[text] unless text.empty?
|
|
222
|
+
parts << extract_literal(data, binary:, bytesize:, non_sync:)
|
|
223
|
+
data.bytesplice(0, bytesize, "")
|
|
224
|
+
end
|
|
225
|
+
parts << RawText[data] unless data.empty?
|
|
226
|
+
parts
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def extract_literal(data, binary:, bytesize:, non_sync:)
|
|
230
|
+
if data.bytesize < bytesize
|
|
231
|
+
raise DataFormatError, "Too few bytes in string for literal, " \
|
|
232
|
+
"expected: %s, remaining: %s" % [bytesize, data.bytesize]
|
|
233
|
+
end
|
|
234
|
+
literal = data.byteslice(0, bytesize)
|
|
235
|
+
(binary ? Literal8 : Literal).new(data: literal, non_sync:)
|
|
134
236
|
end
|
|
135
237
|
end
|
|
136
238
|
|
|
137
239
|
class Atom < CommandData # :nodoc:
|
|
240
|
+
def initialize(**)
|
|
241
|
+
super
|
|
242
|
+
validate
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def validate
|
|
246
|
+
data.to_s.ascii_only? \
|
|
247
|
+
or raise DataFormatError, "#{self.class} must be ASCII only"
|
|
248
|
+
data.match?(ResponseParser::Patterns::ATOM_SPECIALS) \
|
|
249
|
+
and raise DataFormatError, "#{self.class} must not contain atom-specials"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def send_data(imap, tag)
|
|
253
|
+
imap.__send__(:put_string, data.to_s)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
class Flag < Atom # :nodoc:
|
|
138
258
|
def send_data(imap, tag)
|
|
139
|
-
imap.__send__(:put_string, data)
|
|
259
|
+
imap.__send__(:put_string, "\\#{data}")
|
|
140
260
|
end
|
|
141
261
|
end
|
|
142
262
|
|
|
@@ -146,9 +266,39 @@ module Net
|
|
|
146
266
|
end
|
|
147
267
|
end
|
|
148
268
|
|
|
149
|
-
class Literal <
|
|
269
|
+
class Literal < Data.define(:data, :non_sync) # :nodoc:
|
|
270
|
+
def self.validate(...)
|
|
271
|
+
data = new(...)
|
|
272
|
+
data.validate
|
|
273
|
+
data
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def initialize(data:, non_sync: nil)
|
|
277
|
+
data = -String(data.to_str).b or
|
|
278
|
+
raise DataFormatError, "#{self.class} expects string input"
|
|
279
|
+
super
|
|
280
|
+
validate
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def bytesize = data.bytesize
|
|
284
|
+
|
|
285
|
+
def validate
|
|
286
|
+
if data.include?("\0")
|
|
287
|
+
raise DataFormatError, "NULL byte not allowed in #{self.class}. " \
|
|
288
|
+
"Use #{Literal8} or a null-safe encoding."
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def send_data(imap, tag)
|
|
293
|
+
imap.__send__(:send_literal, data, tag, non_sync:)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
class Literal8 < Literal # :nodoc:
|
|
298
|
+
def validate = nil # all bytes are okay
|
|
299
|
+
|
|
150
300
|
def send_data(imap, tag)
|
|
151
|
-
imap.__send__(:
|
|
301
|
+
imap.__send__(:send_binary_literal, data, tag, non_sync:)
|
|
152
302
|
end
|
|
153
303
|
end
|
|
154
304
|
|
|
@@ -221,6 +371,14 @@ module Net
|
|
|
221
371
|
|
|
222
372
|
module_function
|
|
223
373
|
|
|
374
|
+
def literal_or_literal8(input, name: "argument")
|
|
375
|
+
return input if input in Literal | Literal8
|
|
376
|
+
data = String.try_convert(input) \
|
|
377
|
+
or raise TypeError, "expected #{name} to be String, got #{input.class}"
|
|
378
|
+
type = data.include?("\0") ? Literal8 : Literal
|
|
379
|
+
type.new(data:)
|
|
380
|
+
end
|
|
381
|
+
|
|
224
382
|
# Allows symbols in addition to strings
|
|
225
383
|
def valid_string?(str)
|
|
226
384
|
str.is_a?(Symbol) || str.respond_to?(:to_str)
|
data/lib/net/imap/config.rb
CHANGED
|
@@ -281,6 +281,40 @@ module Net
|
|
|
281
281
|
0.5r => true,
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
# The maximum bytesize for sending non-synchronizing literals, when the
|
|
285
|
+
# server supports them. To disable non-synchronizing literals, set the
|
|
286
|
+
# value to +-1+.
|
|
287
|
+
#
|
|
288
|
+
# Non-synchronizing literals are only sent when the server's
|
|
289
|
+
# capabilities[rdoc-ref:IMAP#capabilities] have been
|
|
290
|
+
# cached[rdoc-ref:IMAP#capabilities_cached?] and include either
|
|
291
|
+
# <tt>LITERAL+</tt> [RFC7888[https://www.rfc-editor.org/rfc/rfc7888]],
|
|
292
|
+
# <tt>LITERAL-</tt> [RFC7888[https://www.rfc-editor.org/rfc/rfc7888]], or
|
|
293
|
+
# +IMAP4rev2+ [RFC9051[https://www.rfc-editor.org/rfc/rfc9051]].
|
|
294
|
+
#
|
|
295
|
+
# For <tt>LITERAL+</tt>, this value is the only limit on whether a literal
|
|
296
|
+
# value is sent as non-synchronizing literals. For <tt>LITERAL-</tt> and
|
|
297
|
+
# <tt>IMAP4rev2</tt>, non-synchronizing literals must also be smaller than
|
|
298
|
+
# +4096+ bytes.
|
|
299
|
+
#
|
|
300
|
+
# Non-synchronizing literals avoid the latency of waiting for the server
|
|
301
|
+
# to allow continuation. However, if a client sends a non-synchronizing
|
|
302
|
+
# literal that is too large for the server, the server may need to close
|
|
303
|
+
# the connection. Because <tt>LITERAL+</tt> does not directly indicate
|
|
304
|
+
# the server's limits, it's best to avoid sending very large
|
|
305
|
+
# non-synchronized literals.
|
|
306
|
+
#
|
|
307
|
+
# ==== Versioned Defaults
|
|
308
|
+
#
|
|
309
|
+
# max_non_synchronizing_literal <em>was added in +v0.6.4+.</em>
|
|
310
|
+
#
|
|
311
|
+
# * original: +-1+ (_never_ send non-synchronizing literals)
|
|
312
|
+
# * +0.6+: 16 KiB
|
|
313
|
+
attr_accessor :max_non_synchronizing_literal, type: Integer?, defaults: {
|
|
314
|
+
0.0r => -1,
|
|
315
|
+
0.6r => 16 << 16, # 16 KiB
|
|
316
|
+
}
|
|
317
|
+
|
|
284
318
|
# The maximum allowed server response size. When +nil+, there is no limit
|
|
285
319
|
# on response size.
|
|
286
320
|
#
|
|
@@ -174,6 +174,22 @@ module Net
|
|
|
174
174
|
0 < num && num <= 0xffff_ffff
|
|
175
175
|
end
|
|
176
176
|
|
|
177
|
+
# Check if argument is a valid 'number64' according to RFC 9051
|
|
178
|
+
# number64 = 1*DIGIT
|
|
179
|
+
# ; Unsigned 63-bit integer
|
|
180
|
+
# ; (0 <= n <= 9,223,372,036,854,775,807)
|
|
181
|
+
def valid_number64?(num)
|
|
182
|
+
0 <= num && num <= 0x7fff_ffff_ffff_ffff
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Check if argument is a valid 'number64' according to RFC 9051
|
|
186
|
+
# nz-number64 = digit-nz *DIGIT
|
|
187
|
+
# ; Unsigned 63-bit integer
|
|
188
|
+
# ; (0 < n <= 9,223,372,036,854,775,807)
|
|
189
|
+
def valid_nz_number64?(num)
|
|
190
|
+
0 < num && num <= 0x7fff_ffff_ffff_ffff
|
|
191
|
+
end
|
|
192
|
+
|
|
177
193
|
# Check if argument is a valid 'mod-sequence-value' according to RFC 4551
|
|
178
194
|
# mod-sequence-value = 1*DIGIT
|
|
179
195
|
# ; Positive unsigned 64-bit integer
|
|
@@ -203,6 +219,20 @@ module Net
|
|
|
203
219
|
"nz-number must be non-zero unsigned 32-bit integer: #{num}"
|
|
204
220
|
end
|
|
205
221
|
|
|
222
|
+
# Ensure argument is 'number64' or raise DataFormatError
|
|
223
|
+
def ensure_number64(num)
|
|
224
|
+
return num if valid_number64?(num)
|
|
225
|
+
raise DataFormatError,
|
|
226
|
+
"number64 must be unsigned 63-bit integer: #{num}"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Ensure argument is 'nz-number64' or raise DataFormatError
|
|
230
|
+
def ensure_nz_number64(num)
|
|
231
|
+
return num if valid_nz_number64?(num)
|
|
232
|
+
raise DataFormatError,
|
|
233
|
+
"nz-number64 must be non-zero unsigned 63-bit integer: #{num}"
|
|
234
|
+
end
|
|
235
|
+
|
|
206
236
|
# Ensure argument is 'mod-sequence-value' or raise DataFormatError
|
|
207
237
|
def ensure_mod_sequence_value(num)
|
|
208
238
|
return num if valid_mod_sequence_value?(num)
|
|
@@ -237,6 +267,26 @@ module Net
|
|
|
237
267
|
end
|
|
238
268
|
end
|
|
239
269
|
|
|
270
|
+
# Like #ensure_number64, but usable with numeric String input.
|
|
271
|
+
def coerce_number64(num)
|
|
272
|
+
case num
|
|
273
|
+
when Integer then ensure_number64 num
|
|
274
|
+
when NUMBER_RE then ensure_number64 Integer num
|
|
275
|
+
else
|
|
276
|
+
raise DataFormatError, "%p is not a valid number64" % [num]
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Like #ensure_nz_number64, but usable with numeric String input.
|
|
281
|
+
def coerce_nz_number64(num)
|
|
282
|
+
case num
|
|
283
|
+
when Integer then ensure_nz_number64 num
|
|
284
|
+
when NUMBER_RE then ensure_nz_number64 Integer num
|
|
285
|
+
else
|
|
286
|
+
raise DataFormatError, "%p is not a valid nz-number64" % [num]
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
240
290
|
# Like #ensure_mod_sequence_value, but usable with numeric String input.
|
|
241
291
|
def coerce_mod_sequence_value(num)
|
|
242
292
|
case num
|
data/lib/net/imap/errors.rb
CHANGED
|
@@ -172,18 +172,11 @@ module Net
|
|
|
172
172
|
]
|
|
173
173
|
end
|
|
174
174
|
if parser_backtrace
|
|
175
|
-
|
|
176
|
-
next if loc.base_label.include? "parse_error"
|
|
177
|
-
break if loc.base_label == "parse"
|
|
178
|
-
if loc.label.include?("#") # => Class#method, since ruby 3.4
|
|
179
|
-
next unless loc.label&.include?(parser_class.name)
|
|
180
|
-
else
|
|
181
|
-
next unless loc.path&.include?("net/imap/response_parser")
|
|
182
|
-
end
|
|
175
|
+
normalized_parser_backtrace.each do |idx, path, lineno, label, base_label|
|
|
183
176
|
msg << "\n %s: %s (%s:%d)" % [
|
|
184
177
|
hl["%{key}caller[%{/key}%{idx}%%2d%{/idx}%{key}]%{/key}"] % idx,
|
|
185
|
-
hl["%{label}%%-30s%{/label}"] %
|
|
186
|
-
File.basename(
|
|
178
|
+
hl["%{label}%%-30s%{/label}"] % base_label,
|
|
179
|
+
File.basename(path, ".rb"), lineno
|
|
187
180
|
]
|
|
188
181
|
end
|
|
189
182
|
end
|
|
@@ -198,12 +191,56 @@ module Net
|
|
|
198
191
|
def processed_string = string && pos && string[...pos]
|
|
199
192
|
def remaining_string = string && pos && string[pos..]
|
|
200
193
|
|
|
194
|
+
# Returns true when all attributes are equal, except for #backtrace and
|
|
195
|
+
# #backtrace_locations which are replaced with #parser_methods. This
|
|
196
|
+
# allows deserialized errors to be compared.
|
|
197
|
+
def ==(other)
|
|
198
|
+
return false if self.class != other.class
|
|
199
|
+
methods = parser_methods
|
|
200
|
+
other_methods = other.parser_methods
|
|
201
|
+
message == other.message &&
|
|
202
|
+
methods == other_methods &&
|
|
203
|
+
string == other.string &&
|
|
204
|
+
pos == other.pos &&
|
|
205
|
+
lex_state == other.lex_state &&
|
|
206
|
+
token == other.token
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Lists the methods (from #backtrace_locations or #backtrace) called on
|
|
210
|
+
# parser_class (since ruby 3.4) or which have "net/imap/response_parser"
|
|
211
|
+
# in the path (before ruby 3.4). Most parser method names are based on
|
|
212
|
+
# rules in the IMAP grammar.
|
|
213
|
+
def parser_methods = normalized_parser_backtrace.map(&:last)
|
|
214
|
+
|
|
201
215
|
private
|
|
202
216
|
|
|
217
|
+
def normalized_parser_backtrace
|
|
218
|
+
normalize_backtrace
|
|
219
|
+
.take_while {|_, _, _, _, base_label| base_label != "parse" }
|
|
220
|
+
.reject {|_, _, _, _, base_label| base_label.nil? }
|
|
221
|
+
.reject {|_, _, _, _, base_label| base_label.include? "parse_error" }
|
|
222
|
+
.select {|_, path, _, label, _|
|
|
223
|
+
if label.include?("#") # => Class#method, since ruby 3.4
|
|
224
|
+
label.include?(parser_class.name)
|
|
225
|
+
else
|
|
226
|
+
path.include?("net/imap/response_parser")
|
|
227
|
+
end
|
|
228
|
+
}
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def normalize_backtrace
|
|
232
|
+
(backtrace_locations&.each_with_index&.map {|loc, idx|
|
|
233
|
+
[idx, loc.path, loc.lineno, loc.label, loc.base_label]
|
|
234
|
+
} || backtrace&.each_with_index&.map {|bt, idx|
|
|
235
|
+
[idx, *bt.match(/\A(\S+):(\d+):in [`'](.*?([\w]+[?!]?))'\z/)&.captures]
|
|
236
|
+
} || [])
|
|
237
|
+
end
|
|
238
|
+
|
|
203
239
|
def default_highlight_from_env
|
|
204
240
|
(ENV["FORCE_COLOR"] || "") !~ /\A(?:0|)\z/ ||
|
|
205
241
|
(ENV["TERM"] || "") !~ /\A(?:dumb|unknown|)\z/i
|
|
206
242
|
end
|
|
243
|
+
|
|
207
244
|
end
|
|
208
245
|
|
|
209
246
|
# Superclass of all errors used to encapsulate "fail" responses
|
|
@@ -75,9 +75,18 @@ module Net
|
|
|
75
75
|
#
|
|
76
76
|
# Net::IMAP::UnparsedData represents data for unknown response types or
|
|
77
77
|
# unknown extensions to response types without a well-defined extension
|
|
78
|
-
# grammar.
|
|
78
|
+
# grammar. UnparsedData represents the portion of the response which the
|
|
79
|
+
# parser has skipped over, without attempting to parse it.
|
|
79
80
|
#
|
|
80
|
-
#
|
|
81
|
+
# parser = Net::IMAP::ResponseParser.new
|
|
82
|
+
# response = parser.parse "* X-UNKNOWN-TYPE can't parse this\r\n"
|
|
83
|
+
# response => Net::IMAP::UntaggedResponse(
|
|
84
|
+
# name: "X-UNKNOWN-TYPE",
|
|
85
|
+
# data: Net::IMAP::UnparsedData(unparsed_data: "can't parse this"),
|
|
86
|
+
# )
|
|
87
|
+
#
|
|
88
|
+
# See also: UnparsedNumericResponseData, ExtensionData, IgnoredResponse,
|
|
89
|
+
# InvalidParseData.
|
|
81
90
|
class UnparsedData < Struct.new(:unparsed_data)
|
|
82
91
|
##
|
|
83
92
|
# method: unparsed_data
|
|
@@ -86,6 +95,61 @@ module Net
|
|
|
86
95
|
# The unparsed data
|
|
87
96
|
end
|
|
88
97
|
|
|
98
|
+
# **Note:** This represents an intentionally _unstable_ API. Where
|
|
99
|
+
# instances of this class are returned, future releases may return a
|
|
100
|
+
# different (incompatible) object <em>without deprecation or warning</em>.
|
|
101
|
+
#
|
|
102
|
+
# When the response parser encounters a recoverable error,
|
|
103
|
+
# Net::IMAP::InvalidParseData represents that portion of the response which
|
|
104
|
+
# could not be parsed, allowing the parser to parse the remainder of the
|
|
105
|
+
# response. InvalidParseData is always associated with a ResponseParseError
|
|
106
|
+
# which has been rescued.
|
|
107
|
+
#
|
|
108
|
+
# This could be caused by a malformed server response, by a bug in
|
|
109
|
+
# Net::IMAP::ResponseParser, or by an unsupported extension to the response
|
|
110
|
+
# syntax. For example, if a server supports +UIDPLUS+, but sends an invalid
|
|
111
|
+
# +COPYUID+ response code:
|
|
112
|
+
#
|
|
113
|
+
# parser = Net::IMAP::ResponseParser.new
|
|
114
|
+
# parsed = parser.parse "* OK [COPYUID 701 ] copied one message\r\n"
|
|
115
|
+
# parsed => {
|
|
116
|
+
# data: Net::IMAP::ResponseText(
|
|
117
|
+
# code: Net::IMAP::ResponseCode(
|
|
118
|
+
# name: "COPYUID",
|
|
119
|
+
# data: Net::IMAP::InvalidParseData(
|
|
120
|
+
# parse_error: Net::IMAP::ResponseParseError,
|
|
121
|
+
# unparsed_data: "701 ",
|
|
122
|
+
# parsed_data: nil,
|
|
123
|
+
# )
|
|
124
|
+
# )
|
|
125
|
+
# )
|
|
126
|
+
# }
|
|
127
|
+
#
|
|
128
|
+
# In this example, although <tt>[COPYUID 701 ]</tt> uses valid syntax for a
|
|
129
|
+
# _generic_ ResponseCode, it is _invalid_ syntax for a +COPYUID+ response
|
|
130
|
+
# code.
|
|
131
|
+
#
|
|
132
|
+
# See also: UnparsedData, ExtensionData
|
|
133
|
+
class InvalidParseData < Data.define(:parse_error, :unparsed_data, :parsed_data)
|
|
134
|
+
##
|
|
135
|
+
# method: parse_error
|
|
136
|
+
# :call-seq: parse_error -> ResponseParseError
|
|
137
|
+
#
|
|
138
|
+
# Returns the rescued ResponseParseError.
|
|
139
|
+
|
|
140
|
+
##
|
|
141
|
+
# method: unparsed_data
|
|
142
|
+
# :call-seq: unparsed_data -> string
|
|
143
|
+
#
|
|
144
|
+
# Returns the raw string which was skipped over by the parser.
|
|
145
|
+
|
|
146
|
+
##
|
|
147
|
+
# method: parsed_data
|
|
148
|
+
#
|
|
149
|
+
# May return a partial parse result for unparsed_data, which had already
|
|
150
|
+
# been parsed before the parse_error.
|
|
151
|
+
end
|
|
152
|
+
|
|
89
153
|
# **Note:** This represents an intentionally _unstable_ API. Where
|
|
90
154
|
# instances of this class are returned, future releases may return a
|
|
91
155
|
# different (incompatible) object <em>without deprecation or warning</em>.
|
|
@@ -93,7 +157,17 @@ module Net
|
|
|
93
157
|
# Net::IMAP::UnparsedNumericResponseData represents data for unhandled
|
|
94
158
|
# response types with a numeric prefix. See the documentation for #number.
|
|
95
159
|
#
|
|
96
|
-
#
|
|
160
|
+
# parser = Net::IMAP::ResponseParser.new
|
|
161
|
+
# response = parser.parse "* 123 X-UNKNOWN-TYPE can't parse this\r\n"
|
|
162
|
+
# response => Net::IMAP::UntaggedResponse(
|
|
163
|
+
# name: "X-UNKNOWN-TYPE",
|
|
164
|
+
# data: Net::IMAP::UnparsedNumericData(
|
|
165
|
+
# number: 123,
|
|
166
|
+
# unparsed_data: "can't parse this"
|
|
167
|
+
# ),
|
|
168
|
+
# )
|
|
169
|
+
#
|
|
170
|
+
# See also: UnparsedData, ExtensionData, IgnoredResponse, InvalidParseData
|
|
97
171
|
class UnparsedNumericResponseData < Struct.new(:number, :unparsed_data)
|
|
98
172
|
##
|
|
99
173
|
# method: number
|
|
@@ -306,6 +380,14 @@ module Net
|
|
|
306
380
|
# because the server doesn't allow deletion of mailboxes with children.
|
|
307
381
|
# #data is +nil+.
|
|
308
382
|
#
|
|
383
|
+
# === <tt>QUOTA=RES-*</tt> response codes
|
|
384
|
+
# See {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208.html#section-4.3].
|
|
385
|
+
# * +OVERQUOTA+ (also in RFC5530[https://www.rfc-editor.org/rfc/rfc5530]),
|
|
386
|
+
# with a tagged +NO+ response to an +APPEND+/+COPY+/+MOVE+ command when
|
|
387
|
+
# the command would put the target mailbox over any quota, and with an
|
|
388
|
+
# untagged +NO+ when a mailbox exceeds a soft quota (which may be caused
|
|
389
|
+
# be external events). #data is +nil+.
|
|
390
|
+
#
|
|
309
391
|
# === +CONDSTORE+ extension
|
|
310
392
|
# See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
|
|
311
393
|
# * +NOMODSEQ+, when selecting a mailbox that does not support
|
|
@@ -324,9 +406,10 @@ module Net
|
|
|
324
406
|
#
|
|
325
407
|
# Response codes are backwards compatible: Servers are allowed to send new
|
|
326
408
|
# response codes even if the client has not enabled the extension that
|
|
327
|
-
# defines them. When
|
|
328
|
-
# code
|
|
329
|
-
#
|
|
409
|
+
# defines them. When ResponseParser does not know how to parse the response
|
|
410
|
+
# code data, #data may return the unparsed string, ExtensionData, or
|
|
411
|
+
# UnparsedData. When ResponseParser attempts but fails to parse the
|
|
412
|
+
# response code data, #data returns InvalidParseData.
|
|
330
413
|
class ResponseCode < Struct.new(:name, :data)
|
|
331
414
|
##
|
|
332
415
|
# method: name
|
|
@@ -341,8 +424,13 @@ module Net
|
|
|
341
424
|
#
|
|
342
425
|
# Returns the parsed response code data, e.g: an array of capabilities
|
|
343
426
|
# strings, an array of character set strings, a list of permanent flags,
|
|
344
|
-
# an Integer, etc. The response #
|
|
345
|
-
# code data can take.
|
|
427
|
+
# an Integer, etc. The response #name determines what form the response
|
|
428
|
+
# code #data can take.
|
|
429
|
+
#
|
|
430
|
+
# When ResponseParser does not know how to parse the response code data,
|
|
431
|
+
# #data may return the unparsed string, ExtensionData, or UnparsedData.
|
|
432
|
+
# When ResponseParser attempts but fails to parse the response code data,
|
|
433
|
+
# #data returns InvalidParseData.
|
|
346
434
|
end
|
|
347
435
|
|
|
348
436
|
# MailboxList represents the data of an untagged +LIST+ response, for a
|
|
@@ -383,14 +471,23 @@ module Net
|
|
|
383
471
|
# and MailboxQuota objects.
|
|
384
472
|
#
|
|
385
473
|
# == Required capability
|
|
474
|
+
#
|
|
386
475
|
# Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
|
|
387
|
-
#
|
|
476
|
+
# or <tt>QUOTA=RES-STORAGE</tt>
|
|
477
|
+
# [RFC9208[https://www.rfc-editor.org/rfc/rfc9208]] capability.
|
|
388
478
|
class MailboxQuota < Struct.new(:mailbox, :usage, :quota)
|
|
389
479
|
##
|
|
390
480
|
# method: mailbox
|
|
391
481
|
# :call-seq: mailbox -> string
|
|
392
482
|
#
|
|
393
|
-
# The
|
|
483
|
+
# The quota root with the associated quota.
|
|
484
|
+
#
|
|
485
|
+
# NOTE: this was mistakenly named "mailbox". But the quota root's name may
|
|
486
|
+
# differ from the mailbox. A single quota root may cover multiple
|
|
487
|
+
# mailboxes, and a single mailbox may be governed by multiple quota roots.
|
|
488
|
+
|
|
489
|
+
# The quota root with the associated quota.
|
|
490
|
+
alias quota_root mailbox
|
|
394
491
|
|
|
395
492
|
##
|
|
396
493
|
# method: usage
|
|
@@ -402,7 +499,7 @@ module Net
|
|
|
402
499
|
# method: quota
|
|
403
500
|
# :call-seq: quota -> Integer
|
|
404
501
|
#
|
|
405
|
-
#
|
|
502
|
+
# Storage limit imposed on the mailbox.
|
|
406
503
|
#
|
|
407
504
|
end
|
|
408
505
|
|