@@ -267,6 +267,56 @@ module RFC3629
267
267
# ; Is a valid RFC 3501 "atom".
268
268
TAGGED_EXT_LABEL = /#{ TAGGED_LABEL_FCHAR } #{ TAGGED_LABEL_CHAR } */n
269
269
270
+ # nz-number = digit-nz *DIGIT
271
+ # ; Non-zero unsigned 32-bit integer
272
+ # ; (0 < n < 4,294,967,296)
273
+ NZ_NUMBER = /[1-9]\d */n
274
+
275
+ # seq-number = nz-number / "*"
276
+ # ; message sequence number (COPY, FETCH, STORE
277
+ # ; commands) or unique identifier (UID COPY,
278
+ # ; UID FETCH, UID STORE commands).
279
+ # ; * represents the largest number in use. In
280
+ # ; the case of message sequence numbers, it is
281
+ # ; the number of messages in a non-empty mailbox.
282
+ # ; In the case of unique identifiers, it is the
283
+ # ; unique identifier of the last message in the
284
+ # ; mailbox or, if the mailbox is empty, the
285
+ # ; mailbox's current UIDNEXT value.
286
+ # ; The server should respond with a tagged BAD
287
+ # ; response to a command that uses a message
288
+ # ; sequence number greater than the number of
289
+ # ; messages in the selected mailbox. This
290
+ # ; includes "*" if the selected mailbox is empty.
291
+ SEQ_NUMBER = /#{ NZ_NUMBER } |\* /n
292
+
293
+ # seq-range = seq-number ":" seq-number
294
+ # ; two seq-number values and all values between
295
+ # ; these two regardless of order.
296
+ # ; Example: 2:4 and 4:2 are equivalent and
297
+ # ; indicate values 2, 3, and 4.
298
+ # ; Example: a unique identifier sequence range of
299
+ # ; 3291:* includes the UID of the last message in
300
+ # ; the mailbox, even if that value is less than
301
+ # ; 3291.
302
+ SEQ_RANGE = /#{ SEQ_NUMBER } :#{ SEQ_NUMBER } /n
303
+
304
+ # sequence-set = (seq-number / seq-range) ["," sequence-set]
305
+ # ; set of seq-number values, regardless of order.
306
+ # ; Servers MAY coalesce overlaps and/or execute
307
+ # ; the sequence in any order.
308
+ # ; Example: a message sequence number set of
309
+ # ; 2,4:7,9,12:* for a mailbox with 15 messages is
310
+ # ; equivalent to 2,4,5,6,7,9,12,13,14,15
311
+ # ; Example: a message sequence number set of
312
+ # ; *:4,5:7 for a mailbox with 10 messages is
313
+ # ; equivalent to 10,9,8,7,6,5,4,5,6,7 and MAY
314
+ # ; be reordered and overlap coalesced to be
315
+ # ; 4,5,6,7,8,9,10.
316
+ SEQUENCE_SET_ITEM = /#{ SEQ_NUMBER } |#{ SEQ_RANGE } /n
317
+ SEQUENCE_SET = /#{ SEQUENCE_SET_ITEM } (?:,#{ SEQUENCE_SET_ITEM } )*/n
318
+ SEQUENCE_SET_STR = /\A #{ SEQUENCE_SET } \z /n
319
+
270
320
# RFC3501:
271
321
# literal = "{" number "}" CRLF *CHAR8
272
322
# ; Number represents the number of CHAR8s
@@ -405,6 +455,24 @@ def unescape_quoted(quoted)
405
455
# ATOM-CHAR = <any CHAR except atom-specials>
406
456
ATOM_TOKENS = [ T_ATOM , T_NUMBER , T_NIL , T_LBRA , T_PLUS ]
407
457
458
+ SEQUENCE_SET_TOKENS = [ T_ATOM , T_NUMBER , T_STAR ]
459
+
460
+ # sequence-set = (seq-number / seq-range) ["," sequence-set]
461
+ # sequence-set =/ seq-last-command
462
+ # ; Allow for "result of the last command"
463
+ # ; indicator.
464
+ # seq-last-command = "$"
465
+ #
466
+ # *note*: doesn't match seq-last-command
467
+ def sequence_set
468
+ str = combine_adjacent ( *SEQUENCE_SET_TOKENS )
469
+ if Patterns ::SEQUENCE_SET_STR . match? ( str )
470
+ SequenceSet . new ( str )
471
+ else
472
+ parse_error ( "unexpected atom %p, expected sequence-set" , str )
473
+ end
474
+ end
475
+
408
476
# ASTRING-CHAR = ATOM-CHAR / resp-specials
409
477
# resp-specials = "]"
410
478
ASTRING_CHARS_TOKENS = [ *ATOM_TOKENS , T_RBRA ] . freeze
@@ -488,6 +556,60 @@ def case_insensitive__nstring
488
556
NIL? ? nil : case_insensitive__string
489
557
end
490
558
559
+ # tagged-ext-comp = astring /
560
+ # tagged-ext-comp *(SP tagged-ext-comp) /
561
+ # "(" tagged-ext-comp ")"
562
+ # ; Extensions that follow this general
563
+ # ; syntax should use nstring instead of
564
+ # ; astring when appropriate in the context
565
+ # ; of the extension.
566
+ # ; Note that a message set or a "number"
567
+ # ; can always be represented as an "atom".
568
+ # ; A URL should be represented as
569
+ # ; a "quoted" string.
570
+ def tagged_ext_comp
571
+ vals = [ ]
572
+ while true
573
+ vals << case lookahead! ( *ASTRING_TOKENS , T_LPAR ) . symbol
574
+ when T_LPAR then lpar ; ary = tagged_ext_comp ; rpar ; ary
575
+ when T_NUMBER then number
576
+ else astring
577
+ end
578
+ SP? or break
579
+ end
580
+ vals
581
+ end
582
+
583
+ # tagged-ext-simple is a subset of atom
584
+ # TODO: recognize sequence-set in the lexer
585
+ #
586
+ # tagged-ext-simple = sequence-set / number / number64
587
+ def tagged_ext_simple
588
+ number? || sequence_set
589
+ end
590
+
591
+ # tagged-ext-val = tagged-ext-simple /
592
+ # "(" [tagged-ext-comp] ")"
593
+ def tagged_ext_val
594
+ if lpar?
595
+ _ = peek_rpar? ? [ ] : tagged_ext_comp
596
+ rpar
597
+ _
598
+ else
599
+ tagged_ext_simple
600
+ end
601
+ end
602
+
603
+ # mailbox = "INBOX" / astring
604
+ # ; INBOX is case-insensitive. All case variants of
605
+ # ; INBOX (e.g., "iNbOx") MUST be interpreted as INBOX
606
+ # ; not as an astring. An astring which consists of
607
+ # ; the case-insensitive sequence "I" "N" "B" "O" "X"
608
+ # ; is considered to be INBOX and not an astring.
609
+ # ; Refer to section 5.1 for further
610
+ # ; semantic details of mailbox names.
611
+ alias mailbox astring
612
+
491
613
# valid number ranges are not enforced by parser
492
614
# number64 = 1*DIGIT
493
615
# ; Unsigned 63-bit integer
@@ -1396,31 +1518,79 @@ def thread_branch(token)
1396
1518
return rootmember
1397
1519
end
1398
1520
1521
+ # mailbox-data =/ "STATUS" SP mailbox SP "(" [status-att-list] ")"
1399
1522
def mailbox_data__status
1400
- token = match ( T_ATOM )
1401
- name = token . value . upcase
1402
- match ( T_SPACE )
1403
- mailbox = astring
1404
- match ( T_SPACE )
1405
- match ( T_LPAR )
1406
- attr = { }
1407
- while true
1408
- token = lookahead
1409
- case token . symbol
1410
- when T_RPAR
1411
- shift_token
1412
- break
1413
- when T_SPACE
1414
- shift_token
1523
+ resp_name = label ( "STATUS" ) ; SP!
1524
+ mbox_name = mailbox ; SP!
1525
+ lpar ; attr = status_att_list ; rpar
1526
+ UntaggedResponse . new ( resp_name , StatusData . new ( mbox_name , attr ) , @str )
1527
+ end
1528
+
1529
+ # RFC3501
1530
+ # status-att-list = status-att SP number *(SP status-att SP number)
1531
+ # RFC4466, RFC9051, and RFC3501 Errata
1532
+ # status-att-list = status-att-val *(SP status-att-val)
1533
+ def status_att_list
1534
+ attrs = [ status_att_val ]
1535
+ while SP? do attrs << status_att_val end
1536
+ attrs . to_h
1537
+ end
1538
+
1539
+ # RFC3501 Errata:
1540
+ # status-att-val = ("MESSAGES" SP number) / ("RECENT" SP number) /
1541
+ # ("UIDNEXT" SP nz-number) / ("UIDVALIDITY" SP nz-number) /
1542
+ # ("UNSEEN" SP number)
1543
+ # RFC4466:
1544
+ # status-att-val = ("MESSAGES" SP number) /
1545
+ # ("RECENT" SP number) /
1546
+ # ("UIDNEXT" SP nz-number) /
1547
+ # ("UIDVALIDITY" SP nz-number) /
1548
+ # ("UNSEEN" SP number)
1549
+ # ;; Extensions to the STATUS responses
1550
+ # ;; should extend this production.
1551
+ # ;; Extensions should use the generic
1552
+ # ;; syntax defined by tagged-ext.
1553
+ # RFC9051:
1554
+ # status-att-val = ("MESSAGES" SP number) /
1555
+ # ("UIDNEXT" SP nz-number) /
1556
+ # ("UIDVALIDITY" SP nz-number) /
1557
+ # ("UNSEEN" SP number) /
1558
+ # ("DELETED" SP number) /
1559
+ # ("SIZE" SP number64)
1560
+ # ; Extensions to the STATUS responses
1561
+ # ; should extend this production.
1562
+ # ; Extensions should use the generic
1563
+ # ; syntax defined by tagged-ext.
1564
+ # RFC7162:
1565
+ # status-att-val =/ "HIGHESTMODSEQ" SP mod-sequence-valzer
1566
+ # ;; Extends non-terminal defined in [RFC4466].
1567
+ # ;; Value 0 denotes that the mailbox doesn't
1568
+ # ;; support persistent mod-sequences
1569
+ # ;; as described in Section 3.1.2.2.
1570
+ # RFC7889:
1571
+ # status-att-val =/ "APPENDLIMIT" SP (number / nil)
1572
+ # ;; status-att-val is defined in RFC 4466
1573
+ # RFC8438:
1574
+ # status-att-val =/ "SIZE" SP number64
1575
+ # RFC8474:
1576
+ # status-att-val =/ "MAILBOXID" SP "(" objectid ")"
1577
+ # ; follows tagged-ext production from [RFC4466]
1578
+ def status_att_val
1579
+ key = tagged_ext_label
1580
+ SP!
1581
+ val =
1582
+ case key
1583
+ when "MESSAGES" then number # RFC3501, RFC9051
1584
+ when "UNSEEN" then number # RFC3501, RFC9051
1585
+ when "DELETED" then number # RFC3501, RFC9051
1586
+ when "UIDNEXT" then nz_number # RFC3501, RFC9051
1587
+ when "UIDVALIDITY" then nz_number # RFC3501, RFC9051
1588
+ when "RECENT" then number # RFC3501 (obsolete)
1589
+ when "SIZE" then number64 # RFC8483, RFC9051
1590
+ else
1591
+ number? || ExtensionData . new ( tagged_ext_val )
1415
1592
end
1416
- token = match ( T_ATOM )
1417
- key = token . value . upcase
1418
- match ( T_SPACE )
1419
- val = number
1420
- attr [ key ] = val
1421
- end
1422
- data = StatusData . new ( mailbox , attr )
1423
- return UntaggedResponse . new ( name , data , @str )
1593
+ [ key , val ]
1424
1594
end
1425
1595
1426
1596
# The presence of "IMAP4rev1" or "IMAP4rev2" is unenforced here.
0 commit comments