@@ -185,6 +185,54 @@ module RFC3629
185185 CODE_TEXT_CHAR = TEXT_CHAR - RESP_SPECIALS
186186 CODE_TEXT = /#{ CODE_TEXT_CHAR } +/n
187187
188+ # flag = "\Answered" / "\Flagged" / "\Deleted" /
189+ # "\Seen" / "\Draft" / flag-keyword / flag-extension
190+ # ; Does not include "\Recent"
191+ # flag-extension = "\" atom
192+ # ; Future expansion. Client implementations
193+ # ; MUST accept flag-extension flags. Server
194+ # ; implementations MUST NOT generate
195+ # ; flag-extension flags except as defined by
196+ # ; a future Standard or Standards Track
197+ # ; revisions of this specification.
198+ # flag-keyword = "$MDNSent" / "$Forwarded" / "$Junk" /
199+ # "$NotJunk" / "$Phishing" / atom
200+ # flag-perm = flag / "\*"
201+ #
202+ # Not checking for max one mbx-list-sflag in the parser.
203+ # >>>
204+ # mbx-list-oflag = "\Noinferiors" / child-mbox-flag /
205+ # "\Subscribed" / "\Remote" / flag-extension
206+ # ; Other flags; multiple from this list are
207+ # ; possible per LIST response, but each flag
208+ # ; can only appear once per LIST response
209+ # mbx-list-sflag = "\NonExistent" / "\Noselect" / "\Marked" /
210+ # "\Unmarked"
211+ # ; Selectability flags; only one per LIST response
212+ # child-mbox-flag = "\HasChildren" / "\HasNoChildren"
213+ # ; attributes for the CHILDREN return option, at most
214+ # ; one possible per LIST response
215+ FLAG = /\\ ?#{ ATOM } /n
216+ FLAG_EXTENSION = /\\ #{ ATOM } /n
217+ FLAG_KEYWORD = ATOM
218+ FLAG_PERM = Regexp . union ( FLAG , "\\ *" )
219+ MBX_FLAG = FLAG_EXTENSION
220+
221+ # flag-list = "(" [flag *(SP flag)] ")"
222+ #
223+ # part of resp-text-code:
224+ # >>>
225+ # "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")"
226+ #
227+ # parens from mailbox-list are included in the regexp:
228+ # >>>
229+ # mbx-list-flags = *(mbx-list-oflag SP) mbx-list-sflag
230+ # *(SP mbx-list-oflag) /
231+ # mbx-list-oflag *(SP mbx-list-oflag)
232+ FLAG_LIST = /\G \( (#{ FLAG } (?:#{ SP } #{ FLAG } )*|)\) /ni
233+ FLAG_PERM_LIST = /\G \( (#{ FLAG_PERM } (?:#{ SP } #{ FLAG_PERM } )*|)\) /ni
234+ MBX_LIST_FLAGS = /\G \( (#{ MBX_FLAG } (?:#{ SP } #{ MBX_FLAG } )*|)\) /ni
235+
188236 # RFC3501:
189237 # QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
190238 # "\" quoted-specials
@@ -383,6 +431,14 @@ def label(word)
383431 parse_error ( "unexpected atom %p, expected %p instead" , val , word )
384432 end
385433
434+ # Use #label or #label_in to assert specific known labels
435+ # (+tagged-ext-label+ only, not +atom+).
436+ def label_in ( *labels )
437+ lbl = tagged_ext_label and labels . include? ( lbl ) and return lbl
438+ parse_error ( "unexpected atom %p, expected one of %s instead" ,
439+ lbl , labels . join ( " or " ) )
440+ end
441+
386442 # expects "OK" or "PREAUTH" and raises InvalidResponseError on failure
387443 def resp_cond_auth__name
388444 lbl = tagged_ext_label and AUTH_CONDS . include? lbl and return lbl
@@ -1084,18 +1140,21 @@ def header_fld_name
10841140 end
10851141 end
10861142
1143+ # mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list /
1144+ # "LSUB" SP mailbox-list / "SEARCH" *(SP nz-number) /
1145+ # "STATUS" SP mailbox SP "(" [status-att-list] ")" /
1146+ # number SP "EXISTS" / number SP "RECENT"
1147+
10871148 def mailbox_data__flags
1088- token = match ( T_ATOM )
1089- name = token . value . upcase
1090- match ( T_SPACE )
1091- return UntaggedResponse . new ( name , flag_list , @str )
1149+ name = label ( "FLAGS" )
1150+ SP!
1151+ UntaggedResponse . new ( name , flag_list , @str )
10921152 end
10931153
10941154 def mailbox_data__list
1095- token = match ( T_ATOM )
1096- name = token . value . upcase
1097- match ( T_SPACE )
1098- return UntaggedResponse . new ( name , mailbox_list , @str )
1155+ name = label_in ( "LIST" , "LSUB" , "XLIST" )
1156+ SP!
1157+ UntaggedResponse . new ( name , mailbox_list , @str )
10991158 end
11001159 alias mailbox_data__lsub mailbox_data__list
11011160 alias mailbox_data__xlist mailbox_data__list
@@ -1645,28 +1704,38 @@ def address
16451704 return Address . new ( name , route , mailbox , host )
16461705 end
16471706
1648- FLAG_REGEXP = /\
1649- (?# FLAG )\\ ([^\x80 -\xff (){ \x00 -\x1f \x7f %"\\ ]+)|\
1650- (?# ATOM )([^\x80 -\xff (){ \x00 -\x1f \x7f %*"\\ ]+)/n
1651-
1707+ # flag-list = "(" [flag *(SP flag)] ")"
16521708 def flag_list
1653- if @str . index ( /\( ([^)]*)\) /ni , @pos )
1654- @pos = $~. end ( 0 )
1655- return $1. scan ( FLAG_REGEXP ) . collect { |flag , atom |
1656- if atom
1657- atom
1658- else
1659- flag . capitalize . intern
1660- end
1661- }
1662- else
1663- parse_error ( "invalid flag list" )
1664- end
1709+ match_re ( Patterns ::FLAG_LIST , "flag-list" ) [ 1 ]
1710+ . split ( nil )
1711+ . map! { _1 . start_with? ( "\\ " ) ? _1 [ 1 ..] . capitalize . to_sym : _1 }
1712+ end
1713+
1714+ # "(" [flag-perm *(SP flag-perm)] ")"
1715+ def flag_perm__list
1716+ match_re ( Patterns ::FLAG_PERM_LIST , "PERMANENTFLAGS flag-perm list" ) [ 1 ]
1717+ . split ( nil )
1718+ . map! { _1 . start_with? ( "\\ " ) ? _1 [ 1 ..] . capitalize . to_sym : _1 }
1719+ end
1720+
1721+ # Not checking for max one mbx-list-sflag in the parser.
1722+ # >>>
1723+ # mbx-list-flags = *(mbx-list-oflag SP) mbx-list-sflag
1724+ # *(SP mbx-list-oflag) /
1725+ # mbx-list-oflag *(SP mbx-list-oflag)
1726+ # mbx-list-oflag = "\Noinferiors" / child-mbox-flag /
1727+ # "\Subscribed" / "\Remote" / flag-extension
1728+ # ; Other flags; multiple from this list are
1729+ # ; possible per LIST response, but each flag
1730+ # ; can only appear once per LIST response
1731+ # mbx-list-sflag = "\NonExistent" / "\Noselect" / "\Marked" /
1732+ # "\Unmarked"
1733+ # ; Selectability flags; only one per LIST response
1734+ def parens__mbx_list_flags
1735+ match_re ( Patterns ::MBX_LIST_FLAGS , "mbx-list-flags" ) [ 1 ]
1736+ . split ( nil ) . map! { _1 . capitalize . to_sym }
16651737 end
16661738
1667- # TODO: not quite correct. flag-perm != flag
1668- alias flag_perm__list flag_list
1669-
16701739 # See https://www.rfc-editor.org/errata/rfc3501
16711740 #
16721741 # charset = atom / quoted
0 commit comments