Skip to content

Commit 724fe71

Browse files
authored
🔀 Merge pull request #199 from ruby/parser/simplify-response
♻️ Update `response` and `continue-req` to new parser style
2 parents ab7d3ee + a535c5b commit 724fe71

File tree

3 files changed

+48
-28
lines changed

3 files changed

+48
-28
lines changed

lib/net/imap/response_data.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ class TaggedResponse < Struct.new(:tag, :name, :data, :raw_data)
108108
# UntaggedResponse#data when the response type is a "condition" ("OK", "NO",
109109
# "BAD", "PREAUTH", or "BYE").
110110
class ResponseText < Struct.new(:code, :text)
111+
# Used to avoid an allocation when ResponseText is empty
112+
EMPTY = new(nil, "").freeze
113+
111114
##
112115
# method: code
113116
# :call-seq: code -> ResponseCode or nil

lib/net/imap/response_parser.rb

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ def unescape_quoted(quoted)
283283
Token = Struct.new(:symbol, :value)
284284

285285
def_char_matchers :SP, " ", :T_SPACE
286+
def_char_matchers :PLUS, "+", :T_PLUS
286287

287288
def_char_matchers :lpar, "(", :T_LPAR
288289
def_char_matchers :rpar, ")", :T_RPAR
@@ -325,6 +326,9 @@ def unescape_quoted(quoted)
325326
# TODO: add to lexer and only match tagged-ext-label
326327
def_token_matchers :tagged_ext_label, T_ATOM, T_NIL, send: :upcase
327328

329+
def_token_matchers :CRLF, T_CRLF
330+
def_token_matchers :EOF, T_EOF
331+
328332
# atom = 1*ATOM-CHAR
329333
# ATOM-CHAR = <any CHAR except atom-specials>
330334
ATOM_TOKENS = [T_ATOM, T_NUMBER, T_NIL, T_LBRA, T_PLUS]
@@ -402,37 +406,40 @@ def case_insensitive__nstring
402406
alias number64 number
403407
alias number64? number?
404408

409+
# [RFC3501 & RFC9051:]
410+
# response = *(continue-req / response-data) response-done
411+
#
412+
# For simplicity, response isn't interpreted as the combination of the
413+
# three response types, but instead represents any individual server
414+
# response. Our simplified interpretation is defined as:
415+
# response = continue-req | response_data | response-tagged
416+
#
417+
# n.b: our "response-tagged" definition parses "greeting" too.
405418
def response
406-
token = lookahead
407-
case token.symbol
408-
when T_PLUS
409-
result = continue_req
410-
when T_STAR
411-
result = response_untagged
412-
else
413-
result = response_tagged
414-
end
415-
while lookahead.symbol == T_SPACE
416-
# Ignore trailing space for Microsoft Exchange Server
417-
shift_token
418-
end
419-
match(T_CRLF)
420-
match(T_EOF)
421-
return result
419+
resp = case lookahead!(T_PLUS, T_STAR, *TAG_TOKENS).symbol
420+
when T_PLUS then continue_req
421+
when T_STAR then response_data
422+
else response_tagged
423+
end
424+
accept_spaces # QUIRKY: Ignore trailing space (MS Exchange Server?)
425+
CRLF!
426+
EOF!
427+
resp
422428
end
423429

430+
# RFC3501 & RFC9051:
431+
# continue-req = "+" SP (resp-text / base64) CRLF
432+
#
433+
# n.b: base64 is valid resp-text. And in the spirit of RFC9051 Appx E 23
434+
# (and to workaround existing servers), we use the following grammar:
435+
#
436+
# continue-req = "+" (SP (resp-text)) CRLF
424437
def continue_req
425-
match(T_PLUS)
426-
token = lookahead
427-
if token.symbol == T_SPACE
428-
shift_token
429-
return ContinuationRequest.new(resp_text, @str)
430-
else
431-
return ContinuationRequest.new(ResponseText.new(nil, ""), @str)
432-
end
438+
PLUS!
439+
ContinuationRequest.new(SP? ? resp_text : ResponseText::EMPTY, @str)
433440
end
434441

435-
def response_untagged
442+
def response_data
436443
match(T_STAR)
437444
match(T_SPACE)
438445
token = lookahead
@@ -1564,10 +1571,10 @@ def nil_atom
15641571
#
15651572
# This advances @pos directly so it's safe before changing @lex_state.
15661573
def accept_spaces
1567-
shift_token if @token&.symbol == T_SPACE
1568-
if @str.index(SPACES_REGEXP, @pos)
1574+
return false unless SP?
1575+
@str.index(SPACES_REGEXP, @pos) and
15691576
@pos = $~.end(0)
1570-
end
1577+
true
15711578
end
15721579

15731580
def next_token

lib/net/imap/response_parser/parser_utils.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ def lookahead
170170
@token ||= next_token
171171
end
172172

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+
173183
def peek_str?(str)
174184
assert_no_lookahead if Net::IMAP.debug
175185
@str[@pos, str.length] == str

0 commit comments

Comments
 (0)