Skip to content

Commit 91bad4a

Browse files
committed
♻️ Unify response-data with a single case stmt
Use `peek_re(RE_RESPONSE_TYPE)` and the `TAGGED_EXT_LABEL` regexp, to unify numeric and non-numeric types in the same case stmt. This is much simpler than two tier version, but I don't have benchmarks as to whether or not this version of the code performs any better. Add aliases for nz-number. Not validating that the numbers are within their allowed range, for now at least.
1 parent 6a2e69a commit 91bad4a

File tree

1 file changed

+87
-46
lines changed

1 file changed

+87
-46
lines changed

lib/net/imap/response_parser.rb

Lines changed: 87 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ module RFC3629
210210
TEXT_rev1 = /#{TEXT_CHAR}+/
211211
TEXT_rev2 = /#{Regexp.union TEXT_CHAR, UTF8_2, UTF8_3, UTF8_4}+/
212212

213+
# tagged-label-fchar = ALPHA / "-" / "_" / "."
214+
TAGGED_LABEL_FCHAR = /[a-zA-Z\-_.]/n
215+
# tagged-label-char = tagged-label-fchar / DIGIT / ":"
216+
TAGGED_LABEL_CHAR = /[a-zA-Z\-_.0-9:]*/n
217+
# tagged-ext-label = tagged-label-fchar *tagged-label-char
218+
# ; Is a valid RFC 3501 "atom".
219+
TAGGED_EXT_LABEL = /#{TAGGED_LABEL_FCHAR}#{TAGGED_LABEL_CHAR}*/n
220+
213221
# RFC3501:
214222
# literal = "{" number "}" CRLF *CHAR8
215223
# ; Number represents the number of CHAR8s
@@ -407,6 +415,13 @@ def case_insensitive__nstring
407415
alias number64 number
408416
alias number64? number?
409417

418+
# valid number ranges are not enforced by parser
419+
# nz-number = digit-nz *DIGIT
420+
# ; Non-zero unsigned 32-bit integer
421+
# ; (0 < n < 4,294,967,296)
422+
alias nz_number number
423+
alias nz_number? number?
424+
410425
# [RFC3501 & RFC9051:]
411426
# response = *(continue-req / response-data) response-done
412427
#
@@ -440,36 +455,64 @@ def continue_req
440455
ContinuationRequest.new(SP? ? resp_text : ResponseText::EMPTY, @str)
441456
end
442457

458+
RE_RESPONSE_TYPE = /\G(?:\d+ )?(?<type>#{Patterns::TAGGED_EXT_LABEL})/n
459+
460+
# [RFC3501:]
461+
# response-data = "*" SP (resp-cond-state / resp-cond-bye /
462+
# mailbox-data / message-data / capability-data) CRLF
463+
# [RFC4466:]
464+
# response-data = "*" SP response-payload CRLF
465+
# response-payload = resp-cond-state / resp-cond-bye /
466+
# mailbox-data / message-data / capability-data
467+
# RFC5161 (ENABLE capability):
468+
# response-data =/ "*" SP enable-data CRLF
469+
# RFC5255 (LANGUAGE capability)
470+
# response-payload =/ language-data
471+
# RFC5255 (I18NLEVEL=1 and I18NLEVEL=2 capabilities)
472+
# response-payload =/ comparator-data
473+
# [RFC9051:]
474+
# response-data = "*" SP (resp-cond-state / resp-cond-bye /
475+
# mailbox-data / message-data / capability-data /
476+
# enable-data) CRLF
477+
#
478+
# [merging in greeting and response-fatal:]
479+
# greeting = "*" SP (resp-cond-auth / resp-cond-bye) CRLF
480+
# response-fatal = "*" SP resp-cond-bye CRLF
481+
# response-data =/ "*" SP (resp-cond-auth / resp-cond-bye) CRLF
482+
# [removing duplicates, this is simply]
483+
# response-payload =/ resp-cond-auth
484+
#
485+
# TODO: remove resp-cond-auth and handle greeting separately
443486
def response_data
444487
STAR!; SP!
445-
token = lookahead!(T_NUMBER, T_ATOM)
446-
case token.symbol
447-
when T_NUMBER then numeric_response
448-
when T_ATOM
449-
case token.value.upcase
450-
when "OK" then response_cond # RFC3501, RFC9051
451-
when "BAD" then response_cond # RFC3501, RFC9051
452-
when "NO" then response_cond # RFC3501, RFC9051
453-
when "PREAUTH" then response_cond # RFC3501, RFC9051
454-
when "BYE" then response_cond # RFC3501, RFC9051
455-
when "FLAGS" then flags_response # RFC3501, RFC9051
456-
when "ID" then id_response # RFC2971
457-
when "LIST" then list_response # RFC3501, RFC9051
458-
when "LSUB" then list_response # RFC3501 (obsolete)
459-
when "XLIST" then list_response # deprecated
460-
when "NAMESPACE" then namespace_response # RFC2342, RFC9051
461-
when "QUOTA" then getquota_response # RFC2087, RFC9208
462-
when "QUOTAROOT" then getquotaroot_response # RFC2087, RFC9208
463-
when "ACL" then getacl_response # RFC4314
464-
when "SEARCH" then search_response # RFC3501 (obsolete)
465-
when "SORT" then search_response # RFC5256, RFC7162
466-
when "THREAD" then thread_response # RFC5256
467-
when "STATUS" then status_response # RFC3501, RFC9051
468-
when "CAPABILITY" then capability_data__untagged # RFC3501, RFC9051
469-
when "ENABLED" then enable_data # RFC5161, RFC9051
470-
when "NOOP" then ignored_response
471-
else unparsed_response
472-
end
488+
m = peek_re(RE_RESPONSE_TYPE) or parse_error("unparsable response")
489+
case m["type"].upcase
490+
when "OK" then response_cond # RFC3501, RFC9051
491+
when "BAD" then response_cond # RFC3501, RFC9051
492+
when "NO" then response_cond # RFC3501, RFC9051
493+
when "PREAUTH" then response_cond # RFC3501, RFC9051
494+
when "BYE" then response_cond # RFC3501, RFC9051
495+
when "FLAGS" then flags_response # RFC3501, RFC9051
496+
when "ID" then id_response # RFC2971
497+
when "LIST" then list_response # RFC3501, RFC9051
498+
when "LSUB" then list_response # RFC3501 (obsolete)
499+
when "XLIST" then list_response # deprecated
500+
when "NAMESPACE" then namespace_response # RFC2342, RFC9051
501+
when "QUOTA" then getquota_response # RFC2087, RFC9208
502+
when "QUOTAROOT" then getquotaroot_response # RFC2087, RFC9208
503+
when "ACL" then getacl_response # RFC4314
504+
when "SEARCH" then search_response # RFC3501 (obsolete)
505+
when "SORT" then search_response # RFC5256, RFC7162
506+
when "THREAD" then thread_response # RFC5256
507+
when "STATUS" then status_response # RFC3501, RFC9051
508+
when "CAPABILITY" then capability_data__untagged # RFC3501, RFC9051
509+
when "ENABLED" then enable_data # RFC5161, RFC9051
510+
when "FETCH" then message_data__fetch # RFC3501, RFC9051
511+
when "EXPUNGE" then message_data__expunge # RFC3501, RFC9051
512+
when "EXISTS" then mailbox_data__exists # RFC3501, RFC9051
513+
when "RECENT" then mailbox_data__recent # RFC3501 (obsolete)
514+
when "NOOP" then ignored_response
515+
else unparsed_response
473516
end
474517
end
475518

@@ -510,26 +553,24 @@ def response_cond
510553
return UntaggedResponse.new(name, resp_text, @str)
511554
end
512555

513-
def numeric_response
514-
n = number
515-
match(T_SPACE)
516-
token = match(T_ATOM)
517-
name = token.value.upcase
518-
case name
519-
when "EXISTS", "RECENT", "EXPUNGE"
520-
return UntaggedResponse.new(name, n, @str)
521-
when "FETCH"
522-
shift_token
523-
match(T_SPACE)
524-
data = FetchData.new(n, msg_att(n))
525-
return UntaggedResponse.new(name, data, @str)
526-
else
527-
klass = name == "NOOP" ? IgnoredResponse : UntaggedResponse
528-
SP?; txt = remaining_unparsed
529-
klass.new(name, UnparsedData.new(n, txt), @str)
530-
end
556+
# message-data = nz-number SP ("EXPUNGE" / ("FETCH" SP msg-att))
557+
def message_data__fetch
558+
seq = nz_number; SP!
559+
name = label "FETCH"; SP!
560+
data = FetchData.new(seq, msg_att(seq))
561+
UntaggedResponse.new(name, data, @str)
531562
end
532563

564+
def response_data__simple_numeric
565+
data = nz_number; SP!
566+
name = tagged_ext_label
567+
UntaggedResponse.new(name, data, @str)
568+
end
569+
570+
alias message_data__expunge response_data__simple_numeric
571+
alias mailbox_data__exists response_data__simple_numeric
572+
alias mailbox_data__recent response_data__simple_numeric
573+
533574
def msg_att(n)
534575
match(T_LPAR)
535576
attr = {}

0 commit comments

Comments
 (0)