Skip to content

Commit ebdee6d

Browse files
authored
🔀 Merge pull request #211 from nevans/parser/better-faster-cleaner-resp-text-code
âš¡ Simpler, faster `resp-text-code` parser (for response codes)
2 parents 6dda581 + 1891124 commit ebdee6d

File tree

1 file changed

+85
-64
lines changed

1 file changed

+85
-64
lines changed

‎lib/net/imap/response_parser.rb

Lines changed: 85 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,11 +1346,13 @@ def enable_data
13461346
end
13471347

13481348
# As a workaround for buggy servers, allow a trailing SP:
1349-
# *(SP capapility) [SP]
1349+
# *(SP capability) [SP]
13501350
def capability__list
1351-
data = []; while _ = SP? && capability? do data << _ end; data
1351+
list = []; while SP? && (capa = capability?) do list << capa end; list
13521352
end
13531353

1354+
alias resp_code__capability capability__list
1355+
13541356
# capability = ("AUTH=" auth-type) / atom
13551357
# ; New capabilities MUST begin with "X" or be
13561358
# ; registered with IANA as standard or
@@ -1473,68 +1475,91 @@ def resp_text
14731475
end
14741476
end
14751477

1476-
# See https://www.rfc-editor.org/errata/rfc3501
1478+
# RFC3501 (See https://www.rfc-editor.org/errata/rfc3501):
1479+
# resp-text-code = "ALERT" /
1480+
# "BADCHARSET" [SP "(" charset *(SP charset) ")" ] /
1481+
# capability-data / "PARSE" /
1482+
# "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" /
1483+
# "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
1484+
# "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
1485+
# "UNSEEN" SP nz-number /
1486+
# atom [SP 1*<any TEXT-CHAR except "]">]
1487+
# capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev1"
1488+
# *(SP capability)
1489+
#
1490+
# RFC5530:
1491+
# resp-text-code =/ "UNAVAILABLE" / "AUTHENTICATIONFAILED" /
1492+
# "AUTHORIZATIONFAILED" / "EXPIRED" /
1493+
# "PRIVACYREQUIRED" / "CONTACTADMIN" / "NOPERM" /
1494+
# "INUSE" / "EXPUNGEISSUED" / "CORRUPTION" /
1495+
# "SERVERBUG" / "CLIENTBUG" / "CANNOT" /
1496+
# "LIMIT" / "OVERQUOTA" / "ALREADYEXISTS" /
1497+
# "NONEXISTENT"
1498+
# RFC9051:
1499+
# resp-text-code = "ALERT" /
1500+
# "BADCHARSET" [SP "(" charset *(SP charset) ")" ] /
1501+
# capability-data / "PARSE" /
1502+
# "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" /
1503+
# "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
1504+
# "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
1505+
# resp-code-apnd / resp-code-copy / "UIDNOTSTICKY" /
1506+
# "UNAVAILABLE" / "AUTHENTICATIONFAILED" /
1507+
# "AUTHORIZATIONFAILED" / "EXPIRED" /
1508+
# "PRIVACYREQUIRED" / "CONTACTADMIN" / "NOPERM" /
1509+
# "INUSE" / "EXPUNGEISSUED" / "CORRUPTION" /
1510+
# "SERVERBUG" / "CLIENTBUG" / "CANNOT" /
1511+
# "LIMIT" / "OVERQUOTA" / "ALREADYEXISTS" /
1512+
# "NONEXISTENT" / "NOTSAVED" / "HASCHILDREN" /
1513+
# "CLOSED" /
1514+
# "UNKNOWN-CTE" /
1515+
# atom [SP 1*<any TEXT-CHAR except "]">]
1516+
# capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev2"
1517+
# *(SP capability)
14771518
#
1478-
# resp-text-code = "ALERT" /
1479-
# "BADCHARSET" [SP "(" charset *(SP charset) ")" ] /
1480-
# capability-data / "PARSE" /
1481-
# "PERMANENTFLAGS" SP "("
1482-
# [flag-perm *(SP flag-perm)] ")" /
1483-
# "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
1484-
# "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
1485-
# "UNSEEN" SP nz-number /
1486-
# atom [SP 1*<any TEXT-CHAR except "]">]
1519+
# RFC4315 (UIDPLUS), RFC9051 (IMAP4rev2):
1520+
# resp-code-apnd = "APPENDUID" SP nz-number SP append-uid
1521+
# resp-code-copy = "COPYUID" SP nz-number SP uid-set SP uid-set
1522+
# resp-text-code =/ resp-code-apnd / resp-code-copy / "UIDNOTSTICKY"
14871523
#
1488-
# +UIDPLUS+ ABNF:: https://www.rfc-editor.org/rfc/rfc4315.html#section-4
1489-
# resp-text-code =/ resp-code-apnd / resp-code-copy / "UIDNOTSTICKY"
1524+
# RFC7162 (CONDSTORE):
1525+
# resp-text-code =/ "HIGHESTMODSEQ" SP mod-sequence-value /
1526+
# "NOMODSEQ" /
1527+
# "MODIFIED" SP sequence-set
14901528
def resp_text_code
1491-
token = match(T_ATOM)
1492-
name = token.value.upcase
1493-
case name
1494-
when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n
1495-
result = ResponseCode.new(name, nil)
1496-
when /\A(?:BADCHARSET)\z/n
1497-
result = ResponseCode.new(name, charset_list)
1498-
when /\A(?:CAPABILITY)\z/ni
1499-
result = ResponseCode.new(name, capability__list)
1500-
when /\A(?:PERMANENTFLAGS)\z/n
1501-
match(T_SPACE)
1502-
result = ResponseCode.new(name, flag_list)
1503-
when /\A(?:UIDVALIDITY|UIDNEXT|UNSEEN)\z/n
1504-
match(T_SPACE)
1505-
result = ResponseCode.new(name, number)
1506-
when /\A(?:APPENDUID)\z/n
1507-
result = ResponseCode.new(name, resp_code_apnd__data)
1508-
when /\A(?:COPYUID)\z/n
1509-
result = ResponseCode.new(name, resp_code_copy__data)
1510-
else
1511-
token = lookahead
1512-
if token.symbol == T_SPACE
1513-
shift_token
1514-
result = ResponseCode.new(name, text_chars_except_rbra)
1529+
name = resp_text_code__name
1530+
data =
1531+
case name
1532+
when "CAPABILITY" then resp_code__capability
1533+
when "PERMANENTFLAGS" then SP? ? flag_perm__list : []
1534+
when "UIDNEXT" then SP!; nz_number
1535+
when "UIDVALIDITY" then SP!; nz_number
1536+
when "UNSEEN" then SP!; nz_number # rev1 only
1537+
when "APPENDUID" then SP!; resp_code_apnd__data # rev2, UIDPLUS
1538+
when "COPYUID" then SP!; resp_code_copy__data # rev2, UIDPLUS
1539+
when "BADCHARSET" then SP? ? charset__list : []
1540+
when "ALERT", "PARSE", "READ-ONLY", "READ-WRITE", "TRYCREATE",
1541+
"UNAVAILABLE", "AUTHENTICATIONFAILED", "AUTHORIZATIONFAILED",
1542+
"EXPIRED", "PRIVACYREQUIRED", "CONTACTADMIN", "NOPERM", "INUSE",
1543+
"EXPUNGEISSUED", "CORRUPTION", "SERVERBUG", "CLIENTBUG", "CANNOT",
1544+
"LIMIT", "OVERQUOTA", "ALREADYEXISTS", "NONEXISTENT", "CLOSED",
1545+
"NOTSAVED", "UIDNOTSTICKY", "UNKNOWN-CTE", "HASCHILDREN"
1546+
when "NOMODSEQ" # CONDSTORE
15151547
else
1516-
result = ResponseCode.new(name, nil)
1548+
SP? and text_chars_except_rbra
15171549
end
1518-
end
1519-
return result
1550+
ResponseCode.new(name, data)
15201551
end
15211552

1553+
alias resp_text_code__name case_insensitive__atom
1554+
15221555
# 1*<any TEXT-CHAR except "]">
15231556
def text_chars_except_rbra
15241557
match_re(CTEXT_REGEXP, '1*<any TEXT-CHAR except "]">')[0]
15251558
end
15261559

1527-
def charset_list
1528-
result = []
1529-
if accept(T_SPACE)
1530-
match(T_LPAR)
1531-
result << charset
1532-
while accept(T_SPACE)
1533-
result << charset
1534-
end
1535-
match(T_RPAR)
1536-
end
1537-
result
1560+
# "(" charset *(SP charset) ")"
1561+
def charset__list
1562+
lpar; list = [charset]; while SP? do list << charset end; rpar; list
15381563
end
15391564

15401565
# already matched: "APPENDUID"
@@ -1550,18 +1575,18 @@ def charset_list
15501575
# match uid_set even if that returns a single-member array.
15511576
#
15521577
def resp_code_apnd__data
1553-
match(T_SPACE); validity = number
1554-
match(T_SPACE); dst_uids = uid_set # uniqueid ⊂ uid-set
1578+
validity = number; SP!
1579+
dst_uids = uid_set # uniqueid ⊂ uid-set
15551580
UIDPlusData.new(validity, nil, dst_uids)
15561581
end
15571582

15581583
# already matched: "COPYUID"
15591584
#
15601585
# resp-code-copy = "COPYUID" SP nz-number SP uid-set SP uid-set
15611586
def resp_code_copy__data
1562-
match(T_SPACE); validity = number
1563-
match(T_SPACE); src_uids = uid_set
1564-
match(T_SPACE); dst_uids = uid_set
1587+
validity = number; SP!
1588+
src_uids = uid_set; SP!
1589+
dst_uids = uid_set
15651590
UIDPlusData.new(validity, src_uids, dst_uids)
15661591
end
15671592

@@ -1639,17 +1664,13 @@ def flag_list
16391664
end
16401665
end
16411666

1667+
# TODO: not quite correct. flag-perm != flag
1668+
alias flag_perm__list flag_list
16421669

16431670
# See https://www.rfc-editor.org/errata/rfc3501
16441671
#
16451672
# charset = atom / quoted
1646-
def charset
1647-
if token = accept(T_QUOTED)
1648-
token.value
1649-
else
1650-
atom
1651-
end
1652-
end
1673+
def charset; quoted? || atom end
16531674

16541675
# RFC7162:
16551676
# mod-sequence-value = 1*DIGIT

0 commit comments

Comments
 (0)