Skip to content

Commit 5212a0b

Browse files
committed
🚧 Parse sequence-set into SequenceSet object
The SequenceSet class is only a placeholder for now, because the more complete implementation isn't ready yet. But we need `sequence-set` for both `tagged-ext-value`. And we need `tagged-ext-value` for the RFC4466 extension grammar for `STATUS`, `ESEARCH`, `LIST`, etc. The more complete SequenceSet implementation is needed for `ESEARCH`.
1 parent 8a60524 commit 5212a0b

File tree

3 files changed

+137
-1
lines changed

3 files changed

+137
-1
lines changed

lib/net/imap/response_data.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
module Net
44
class IMAP < Protocol
5-
autoload :FetchData, File.expand_path("fetch_data", __dir__)
5+
autoload :FetchData, "#{__dir__}/fetch_data"
6+
autoload :SequenceSet, "#{__dir__}/sequence_set"
67

78
# Net::IMAP::ContinuationRequest represents command continuation requests.
89
#

lib/net/imap/response_parser.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,56 @@ module RFC3629
267267
# ; Is a valid RFC 3501 "atom".
268268
TAGGED_EXT_LABEL = /#{TAGGED_LABEL_FCHAR}#{TAGGED_LABEL_CHAR}*/n
269269

270+
# nz-number = digit-nz *DIGIT
271+
# ; Non-zero unsigned 32-bit integer
272+
# ; (0 < n < 4,294,967,296)
273+
NZ_NUMBER = /[1-9]\d*/n
274+
275+
# seq-number = nz-number / "*"
276+
# ; message sequence number (COPY, FETCH, STORE
277+
# ; commands) or unique identifier (UID COPY,
278+
# ; UID FETCH, UID STORE commands).
279+
# ; * represents the largest number in use. In
280+
# ; the case of message sequence numbers, it is
281+
# ; the number of messages in a non-empty mailbox.
282+
# ; In the case of unique identifiers, it is the
283+
# ; unique identifier of the last message in the
284+
# ; mailbox or, if the mailbox is empty, the
285+
# ; mailbox's current UIDNEXT value.
286+
# ; The server should respond with a tagged BAD
287+
# ; response to a command that uses a message
288+
# ; sequence number greater than the number of
289+
# ; messages in the selected mailbox. This
290+
# ; includes "*" if the selected mailbox is empty.
291+
SEQ_NUMBER = /#{NZ_NUMBER}|\*/n
292+
293+
# seq-range = seq-number ":" seq-number
294+
# ; two seq-number values and all values between
295+
# ; these two regardless of order.
296+
# ; Example: 2:4 and 4:2 are equivalent and
297+
# ; indicate values 2, 3, and 4.
298+
# ; Example: a unique identifier sequence range of
299+
# ; 3291:* includes the UID of the last message in
300+
# ; the mailbox, even if that value is less than
301+
# ; 3291.
302+
SEQ_RANGE = /#{SEQ_NUMBER}:#{SEQ_NUMBER}/n
303+
304+
# sequence-set = (seq-number / seq-range) ["," sequence-set]
305+
# ; set of seq-number values, regardless of order.
306+
# ; Servers MAY coalesce overlaps and/or execute
307+
# ; the sequence in any order.
308+
# ; Example: a message sequence number set of
309+
# ; 2,4:7,9,12:* for a mailbox with 15 messages is
310+
# ; equivalent to 2,4,5,6,7,9,12,13,14,15
311+
# ; Example: a message sequence number set of
312+
# ; *:4,5:7 for a mailbox with 10 messages is
313+
# ; equivalent to 10,9,8,7,6,5,4,5,6,7 and MAY
314+
# ; be reordered and overlap coalesced to be
315+
# ; 4,5,6,7,8,9,10.
316+
SEQUENCE_SET_ITEM = /#{SEQ_NUMBER}|#{SEQ_RANGE}/n
317+
SEQUENCE_SET = /#{SEQUENCE_SET_ITEM}(?:,#{SEQUENCE_SET_ITEM})*/n
318+
SEQUENCE_SET_STR = /\A#{SEQUENCE_SET}\z/n
319+
270320
# RFC3501:
271321
# literal = "{" number "}" CRLF *CHAR8
272322
# ; Number represents the number of CHAR8s
@@ -405,6 +455,24 @@ def unescape_quoted(quoted)
405455
# ATOM-CHAR = <any CHAR except atom-specials>
406456
ATOM_TOKENS = [T_ATOM, T_NUMBER, T_NIL, T_LBRA, T_PLUS]
407457

458+
SEQUENCE_SET_TOKENS = [T_ATOM, T_NUMBER, T_STAR]
459+
460+
# sequence-set = (seq-number / seq-range) ["," sequence-set]
461+
# sequence-set =/ seq-last-command
462+
# ; Allow for "result of the last command"
463+
# ; indicator.
464+
# seq-last-command = "$"
465+
#
466+
# *note*: doesn't match seq-last-command
467+
def sequence_set
468+
str = combine_adjacent(*SEQUENCE_SET_TOKENS)
469+
if Patterns::SEQUENCE_SET_STR.match?(str)
470+
SequenceSet.new(str)
471+
else
472+
parse_error("unexpected atom %p, expected sequence-set", str)
473+
end
474+
end
475+
408476
# ASTRING-CHAR = ATOM-CHAR / resp-specials
409477
# resp-specials = "]"
410478
ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA].freeze

lib/net/imap/sequence_set.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
3+
module Net
4+
class IMAP
5+
6+
##
7+
# An IMAP {sequence
8+
# set}[https://www.rfc-editor.org/rfc/rfc9051.html#section-4.1.1],
9+
# is a set of message sequence numbers or unique identifier numbers
10+
# ("UIDs"). It contains numbers and ranges of numbers. The numbers are all
11+
# non-zero unsigned 32-bit integers and one special value, <tt>*</tt>, that
12+
# represents the largest value in the mailbox.
13+
#
14+
# *NOTE:* This SequenceSet class is currently a placeholder for unhandled
15+
# extension data. All it does now is validate. It will be expanded to a
16+
# full API in a future release.
17+
class SequenceSet
18+
19+
def self.[](str) new(str).freeze end
20+
21+
def initialize(input)
22+
@atom = -String.try_convert(input)
23+
validate
24+
end
25+
26+
# Returns the IMAP string representation. In the IMAP grammar,
27+
# +sequence-set+ is a subset of +atom+ which is a subset of +astring+.
28+
attr_accessor :atom
29+
30+
# Returns #atom. In the IMAP grammar, +atom+ is a subset of +astring+.
31+
alias astring atom
32+
33+
# Returns the value of #atom
34+
alias to_s atom
35+
36+
# Hash equality requires the same encoded #atom representation.
37+
#
38+
# Net::IMAP::SequenceSet["1:3"] .eql? Net::IMAP::SequenceSet["1:3"] # => true
39+
# Net::IMAP::SequenceSet["1,2,3"].eql? Net::IMAP::SequenceSet["1:3"] # => false
40+
# Net::IMAP::SequenceSet["1,3"] .eql? Net::IMAP::SequenceSet["3,1"] # => false
41+
# Net::IMAP::SequenceSet["9,1:*"].eql? Net::IMAP::SequenceSet["1:*"] # => false
42+
#
43+
def eql?(other) self.class == other.class && atom == other.atom end
44+
alias == eql?
45+
46+
# See #eql?
47+
def hash; [self.class. atom].hash end
48+
49+
def inspect
50+
(frozen? ? "%s[%p]" : "#<%s %p>") % [self.class, to_s]
51+
end
52+
53+
# Unstable API, for internal use only (Net::IMAP#validate_data)
54+
def validate # :nodoc:
55+
ResponseParser::Patterns::SEQUENCE_SET_STR.match?(@atom) or
56+
raise ArgumentError, "invalid sequence-set: %p" % [input]
57+
true
58+
end
59+
60+
# Unstable API, for internal use only (Net::IMAP#send_data)
61+
def send_data(imap, tag) # :nodoc:
62+
imap.__send__(:put_string, atom)
63+
end
64+
65+
end
66+
end
67+
end

0 commit comments

Comments
 (0)