@@ -210,6 +210,14 @@ module RFC3629
210
210
TEXT_rev1 = /#{ TEXT_CHAR } +/
211
211
TEXT_rev2 = /#{ Regexp . union TEXT_CHAR , UTF8_2 , UTF8_3 , UTF8_4 } +/
212
212
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
+
213
221
# RFC3501:
214
222
# literal = "{" number "}" CRLF *CHAR8
215
223
# ; Number represents the number of CHAR8s
@@ -284,6 +292,7 @@ def unescape_quoted(quoted)
284
292
285
293
def_char_matchers :SP , " " , :T_SPACE
286
294
def_char_matchers :PLUS , "+" , :T_PLUS
295
+ def_char_matchers :STAR , "*" , :T_STAR
287
296
288
297
def_char_matchers :lpar , "(" , :T_LPAR
289
298
def_char_matchers :rpar , ")" , :T_RPAR
@@ -406,6 +415,13 @@ def case_insensitive__nstring
406
415
alias number64 number
407
416
alias number64? number?
408
417
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
+
409
425
# [RFC3501 & RFC9051:]
410
426
# response = *(continue-req / response-data) response-done
411
427
#
@@ -439,47 +455,64 @@ def continue_req
439
455
ContinuationRequest . new ( SP? ? resp_text : ResponseText ::EMPTY , @str )
440
456
end
441
457
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
442
486
def response_data
443
- match ( T_STAR )
444
- match ( T_SPACE )
445
- token = lookahead
446
- if token . symbol == T_NUMBER
447
- return numeric_response
448
- elsif token . symbol == T_ATOM
449
- case token . value
450
- when /\A (?:OK|NO|BAD|BYE|PREAUTH)\z /ni
451
- return response_cond
452
- when /\A (?:FLAGS)\z /ni
453
- return flags_response
454
- when /\A (?:ID)\z /ni
455
- return id_response
456
- when /\A (?:LIST|LSUB|XLIST)\z /ni
457
- return list_response
458
- when /\A (?:NAMESPACE)\z /ni
459
- return namespace_response
460
- when /\A (?:QUOTA)\z /ni
461
- return getquota_response
462
- when /\A (?:QUOTAROOT)\z /ni
463
- return getquotaroot_response
464
- when /\A (?:ACL)\z /ni
465
- return getacl_response
466
- when /\A (?:SEARCH|SORT)\z /ni
467
- return search_response
468
- when /\A (?:THREAD)\z /ni
469
- return thread_response
470
- when /\A (?:STATUS)\z /ni
471
- return status_response
472
- when /\A (?:CAPABILITY)\z /ni
473
- return capability_data__untagged
474
- when /\A (?:NOOP)\z /ni
475
- return ignored_response
476
- when /\A (?:ENABLED)\z /ni
477
- return enable_data
478
- else
479
- return unparsed_response
480
- end
481
- else
482
- parse_error ( "unexpected token %s" , token . symbol )
487
+ STAR! ; SP!
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 "FETCH" then message_data__fetch # RFC3501, RFC9051
492
+ when "EXPUNGE" then message_data__expunge # RFC3501, RFC9051
493
+ when "EXISTS" then mailbox_data__exists # RFC3501, RFC9051
494
+ when "SEARCH" then search_response # RFC3501 (obsolete)
495
+ when "CAPABILITY" then capability_data__untagged # RFC3501, RFC9051
496
+ when "FLAGS" then flags_response # RFC3501, RFC9051
497
+ when "LIST" then list_response # RFC3501, RFC9051
498
+ when "STATUS" then status_response # RFC3501, RFC9051
499
+ when "NAMESPACE" then namespace_response # RFC2342, RFC9051
500
+ when "ENABLED" then enable_data # RFC5161, RFC9051
501
+ when "BAD" then response_cond # RFC3501, RFC9051
502
+ when "NO" then response_cond # RFC3501, RFC9051
503
+ when "PREAUTH" then response_cond # RFC3501, RFC9051
504
+ when "BYE" then response_cond # RFC3501, RFC9051
505
+ when "RECENT" then mailbox_data__recent # RFC3501 (obsolete)
506
+ when "SORT" then sort_data # RFC5256, RFC7162
507
+ when "THREAD" then thread_response # RFC5256
508
+ when "QUOTA" then getquota_response # RFC2087, RFC9208
509
+ when "QUOTAROOT" then getquotaroot_response # RFC2087, RFC9208
510
+ when "ID" then id_response # RFC2971
511
+ when "ACL" then getacl_response # RFC4314
512
+ when "LSUB" then list_response # RFC3501 (obsolete)
513
+ when "XLIST" then list_response # deprecated
514
+ when "NOOP" then ignored_response
515
+ else unparsed_response
483
516
end
484
517
end
485
518
@@ -520,26 +553,24 @@ def response_cond
520
553
return UntaggedResponse . new ( name , resp_text , @str )
521
554
end
522
555
523
- def numeric_response
524
- n = number
525
- match ( T_SPACE )
526
- token = match ( T_ATOM )
527
- name = token . value . upcase
528
- case name
529
- when "EXISTS" , "RECENT" , "EXPUNGE"
530
- return UntaggedResponse . new ( name , n , @str )
531
- when "FETCH"
532
- shift_token
533
- match ( T_SPACE )
534
- data = FetchData . new ( n , msg_att ( n ) )
535
- return UntaggedResponse . new ( name , data , @str )
536
- else
537
- klass = name == "NOOP" ? IgnoredResponse : UntaggedResponse
538
- SP? ; txt = remaining_unparsed
539
- klass . new ( name , UnparsedData . new ( n , txt ) , @str )
540
- 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 )
562
+ end
563
+
564
+ def response_data__simple_numeric
565
+ data = nz_number ; SP!
566
+ name = tagged_ext_label
567
+ UntaggedResponse . new ( name , data , @str )
541
568
end
542
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
+
543
574
def msg_att ( n )
544
575
match ( T_LPAR )
545
576
attr = { }
0 commit comments