Skip to content

Commit 69f4dfb

Browse files
committed
🥅 Validate response-tagged in the parser
The `response-tagged` parser now enforces `resp-cond-state`, raising `InvalidResponseError` for any invalid status conditions. It is also refactored to the new parser style. An explicit method for `tag` is added, which is parsed similarly to `astring_chars` and `atom`. ```abnf response-tagged = tag SP resp-cond-state CRLF resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text ; Status condition tag = 1*<any ASTRING-CHAR except "+"> ``` Currently, any exception raised by the parser will abruptly drop the connection. In the future, if the response handler thread is made more robust against recoverable errors, `InvalidResponseError` should be considered non-recoverable. (There is a test that covers this already).
1 parent 609acd9 commit 69f4dfb

File tree

1 file changed

+44
-15
lines changed

1 file changed

+44
-15
lines changed

lib/net/imap/response_parser.rb

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,21 @@ def parse(str)
5858
T_TEXT = :TEXT # any char except CRLF
5959
T_EOF = :EOF # end of response string
6060

61+
module ResponseConditions
62+
OK = "OK"
63+
NO = "NO"
64+
BAD = "BAD"
65+
BYE = "BYE"
66+
PREAUTH = "PREAUTH"
67+
68+
RESP_COND_STATES = [OK, NO, BAD ].freeze
69+
RESP_DATA_CONDS = [OK, NO, BAD, BYE, ].freeze
70+
AUTH_CONDS = [OK, PREAUTH].freeze
71+
GREETING_CONDS = [OK, BYE, PREAUTH].freeze
72+
RESP_CONDS = [OK, NO, BAD, BYE, PREAUTH].freeze
73+
end
74+
include ResponseConditions
75+
6176
module Patterns
6277

6378
module CharClassSubtraction
@@ -320,10 +335,13 @@ def unescape_quoted(quoted)
320335

321336
ASTRING_TOKENS = [T_QUOTED, *ASTRING_CHARS_TOKENS, T_LITERAL].freeze
322337

323-
# atom = 1*ATOM-CHAR
324-
#
325-
# TODO: match atom entirely by regexp (in the "lexer")
326-
def atom; -combine_adjacent(*ATOM_TOKENS) end
338+
# tag = 1*<any ASTRING-CHAR except "+">
339+
TAG_TOKENS = (ASTRING_CHARS_TOKENS - [T_PLUS]).freeze
340+
341+
# TODO: handle atom, astring_chars, and tag entirely inside the lexer
342+
def atom; combine_adjacent(*ATOM_TOKENS) end
343+
def astring_chars; combine_adjacent(*ASTRING_CHARS_TOKENS) end
344+
def tag; combine_adjacent(*TAG_TOKENS) end
327345

328346
# the #accept version of #atom
329347
def atom?; -combine_adjacent(*ATOM_TOKENS) if lookahead?(*ATOM_TOKENS) end
@@ -336,11 +354,6 @@ def case_insensitive__atom?
336354
-combine_adjacent(*ATOM_TOKENS).upcase if lookahead?(*ATOM_TOKENS)
337355
end
338356

339-
# TODO: handle astring_chars entirely inside the lexer
340-
def astring_chars
341-
combine_adjacent(*ASTRING_CHARS_TOKENS)
342-
end
343-
344357
# astring = 1*ASTRING-CHAR / string
345358
def astring
346359
lookahead?(*ASTRING_CHARS_TOKENS) ? astring_chars : string
@@ -357,6 +370,17 @@ def label(word)
357370
parse_error("unexpected atom %p, expected %p instead", val, word)
358371
end
359372

373+
# expects "OK" or "NO" or "BAD" and raises InvalidResponseError on failure
374+
def resp_cond_state__name
375+
if RESP_COND_STATES.include?(actual = tagged_ext_label)
376+
actual
377+
else
378+
raise InvalidResponseError, "bad response type %p, expected %s" % [
379+
actual, RESP_COND_STATES.join(" or ")
380+
]
381+
end
382+
end
383+
360384
# nstring = string / nil
361385
def nstring
362386
NIL? ? nil : string
@@ -452,13 +476,18 @@ def response_untagged
452476
end
453477
end
454478

479+
# RFC3501 & RFC9051:
480+
# response-tagged = tag SP resp-cond-state CRLF
481+
#
482+
# resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
483+
# ; Status condition
484+
#
485+
# tag = 1*<any ASTRING-CHAR except "+">
455486
def response_tagged
456-
tag = astring_chars
457-
match(T_SPACE)
458-
token = match(T_ATOM)
459-
name = token.value.upcase
460-
match(T_SPACE)
461-
return TaggedResponse.new(tag, name, resp_text, @str)
487+
tag = tag(); SP!
488+
name = resp_cond_state__name; SP!
489+
data = resp_text
490+
TaggedResponse.new(tag, name, data, @str)
462491
end
463492

464493
def response_cond

0 commit comments

Comments
 (0)