net-imap 0.4.4 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of net-imap might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/workflows/pages.yml +2 -2
- data/.github/workflows/test.yml +1 -1
- data/.gitignore +2 -1
- data/docs/styles.css +0 -12
- data/lib/net/imap/data_encoding.rb +14 -2
- data/lib/net/imap/fetch_data.rb +518 -0
- data/lib/net/imap/response_data.rb +88 -210
- data/lib/net/imap/response_parser/parser_utils.rb +1 -1
- data/lib/net/imap/response_parser.rb +531 -281
- data/lib/net/imap/search_result.rb +150 -0
- data/lib/net/imap/sequence_set.rb +1414 -0
- data/lib/net/imap.rb +238 -45
- data/net-imap.gemspec +1 -0
- data/rakelib/benchmarks.rake +4 -11
- metadata +7 -3
@@ -54,6 +54,7 @@ module Net
|
|
54
54
|
T_STAR = :STAR # atom special; list wildcard
|
55
55
|
T_PERCENT = :PERCENT # atom special; list wildcard
|
56
56
|
T_LITERAL = :LITERAL # starts with atom special
|
57
|
+
T_LITERAL8 = :LITERAL8 # starts with atom char "~"
|
57
58
|
T_CRLF = :CRLF # atom special; text special; quoted special
|
58
59
|
T_TEXT = :TEXT # any char except CRLF
|
59
60
|
T_EOF = :EOF # end of response string
|
@@ -197,6 +198,7 @@ module Net
|
|
197
198
|
# ; revisions of this specification.
|
198
199
|
# flag-keyword = "$MDNSent" / "$Forwarded" / "$Junk" /
|
199
200
|
# "$NotJunk" / "$Phishing" / atom
|
201
|
+
#
|
200
202
|
# flag-perm = flag / "\*"
|
201
203
|
#
|
202
204
|
# Not checking for max one mbx-list-sflag in the parser.
|
@@ -219,19 +221,19 @@ module Net
|
|
219
221
|
MBX_FLAG = FLAG_EXTENSION
|
220
222
|
|
221
223
|
# flag-list = "(" [flag *(SP flag)] ")"
|
222
|
-
#
|
223
|
-
#
|
224
|
-
#
|
225
|
-
#
|
226
|
-
#
|
227
|
-
#
|
228
|
-
#
|
229
|
-
|
230
|
-
#
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
224
|
+
# resp-text-code =/ "PERMANENTFLAGS" SP
|
225
|
+
# "(" [flag-perm *(SP flag-perm)] ")"
|
226
|
+
# mbx-list-flags = *(mbx-list-oflag SP) mbx-list-sflag
|
227
|
+
# *(SP mbx-list-oflag) /
|
228
|
+
# mbx-list-oflag *(SP mbx-list-oflag)
|
229
|
+
# (Not checking for max one mbx-list-sflag in the parser.)
|
230
|
+
FLAG_LIST = /\G\((#{FLAG }(?:#{SP}#{FLAG })*|)\)/ni
|
231
|
+
FLAG_PERM_LIST = /\G\((#{FLAG_PERM}(?:#{SP}#{FLAG_PERM})*|)\)/ni
|
232
|
+
MBX_LIST_FLAGS = /\G (#{MBX_FLAG }(?:#{SP}#{MBX_FLAG })*) /nix
|
233
|
+
|
234
|
+
# Gmail allows SP and "]" in flags.......
|
235
|
+
QUIRKY_FLAG = Regexp.union(/\\?#{ASTRING_CHARS}/n, "\\*")
|
236
|
+
QUIRKY_FLAGS_LIST = /\G\(( [^)]* )\)/nx
|
235
237
|
|
236
238
|
# RFC3501:
|
237
239
|
# QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
|
@@ -266,6 +268,56 @@ module Net
|
|
266
268
|
# ; Is a valid RFC 3501 "atom".
|
267
269
|
TAGGED_EXT_LABEL = /#{TAGGED_LABEL_FCHAR}#{TAGGED_LABEL_CHAR}*/n
|
268
270
|
|
271
|
+
# nz-number = digit-nz *DIGIT
|
272
|
+
# ; Non-zero unsigned 32-bit integer
|
273
|
+
# ; (0 < n < 4,294,967,296)
|
274
|
+
NZ_NUMBER = /[1-9]\d*/n
|
275
|
+
|
276
|
+
# seq-number = nz-number / "*"
|
277
|
+
# ; message sequence number (COPY, FETCH, STORE
|
278
|
+
# ; commands) or unique identifier (UID COPY,
|
279
|
+
# ; UID FETCH, UID STORE commands).
|
280
|
+
# ; * represents the largest number in use. In
|
281
|
+
# ; the case of message sequence numbers, it is
|
282
|
+
# ; the number of messages in a non-empty mailbox.
|
283
|
+
# ; In the case of unique identifiers, it is the
|
284
|
+
# ; unique identifier of the last message in the
|
285
|
+
# ; mailbox or, if the mailbox is empty, the
|
286
|
+
# ; mailbox's current UIDNEXT value.
|
287
|
+
# ; The server should respond with a tagged BAD
|
288
|
+
# ; response to a command that uses a message
|
289
|
+
# ; sequence number greater than the number of
|
290
|
+
# ; messages in the selected mailbox. This
|
291
|
+
# ; includes "*" if the selected mailbox is empty.
|
292
|
+
SEQ_NUMBER = /#{NZ_NUMBER}|\*/n
|
293
|
+
|
294
|
+
# seq-range = seq-number ":" seq-number
|
295
|
+
# ; two seq-number values and all values between
|
296
|
+
# ; these two regardless of order.
|
297
|
+
# ; Example: 2:4 and 4:2 are equivalent and
|
298
|
+
# ; indicate values 2, 3, and 4.
|
299
|
+
# ; Example: a unique identifier sequence range of
|
300
|
+
# ; 3291:* includes the UID of the last message in
|
301
|
+
# ; the mailbox, even if that value is less than
|
302
|
+
# ; 3291.
|
303
|
+
SEQ_RANGE = /#{SEQ_NUMBER}:#{SEQ_NUMBER}/n
|
304
|
+
|
305
|
+
# sequence-set = (seq-number / seq-range) ["," sequence-set]
|
306
|
+
# ; set of seq-number values, regardless of order.
|
307
|
+
# ; Servers MAY coalesce overlaps and/or execute
|
308
|
+
# ; the sequence in any order.
|
309
|
+
# ; Example: a message sequence number set of
|
310
|
+
# ; 2,4:7,9,12:* for a mailbox with 15 messages is
|
311
|
+
# ; equivalent to 2,4,5,6,7,9,12,13,14,15
|
312
|
+
# ; Example: a message sequence number set of
|
313
|
+
# ; *:4,5:7 for a mailbox with 10 messages is
|
314
|
+
# ; equivalent to 10,9,8,7,6,5,4,5,6,7 and MAY
|
315
|
+
# ; be reordered and overlap coalesced to be
|
316
|
+
# ; 4,5,6,7,8,9,10.
|
317
|
+
SEQUENCE_SET_ITEM = /#{SEQ_NUMBER}|#{SEQ_RANGE}/n
|
318
|
+
SEQUENCE_SET = /#{SEQUENCE_SET_ITEM}(?:,#{SEQUENCE_SET_ITEM})*/n
|
319
|
+
SEQUENCE_SET_STR = /\A#{SEQUENCE_SET}\z/n
|
320
|
+
|
269
321
|
# RFC3501:
|
270
322
|
# literal = "{" number "}" CRLF *CHAR8
|
271
323
|
# ; Number represents the number of CHAR8s
|
@@ -279,6 +331,16 @@ module Net
|
|
279
331
|
# ; sent from server to the client.
|
280
332
|
LITERAL = /\{(\d+)\}\r\n/n
|
281
333
|
|
334
|
+
# RFC3516 (BINARY):
|
335
|
+
# literal8 = "~{" number "}" CRLF *OCTET
|
336
|
+
# ; <number> represents the number of OCTETs
|
337
|
+
# ; in the response string.
|
338
|
+
# RFC9051:
|
339
|
+
# literal8 = "~{" number64 "}" CRLF *OCTET
|
340
|
+
# ; <number64> represents the number of OCTETs
|
341
|
+
# ; in the response string.
|
342
|
+
LITERAL8 = /~\{(\d+)\}\r\n/n
|
343
|
+
|
282
344
|
module_function
|
283
345
|
|
284
346
|
def unescape_quoted!(quoted)
|
@@ -298,27 +360,28 @@ module Net
|
|
298
360
|
# the default, used in most places
|
299
361
|
BEG_REGEXP = /\G(?:\
|
300
362
|
(?# 1: SPACE )( )|\
|
301
|
-
(?# 2:
|
363
|
+
(?# 2: LITERAL8)#{Patterns::LITERAL8}|\
|
364
|
+
(?# 3: ATOM prefixed with a compatible subtype)\
|
302
365
|
((?:\
|
303
|
-
(?#
|
304
|
-
(?#
|
305
|
-
(?#
|
306
|
-
(?#
|
366
|
+
(?# 4: NIL )(NIL)|\
|
367
|
+
(?# 5: NUMBER )(\d+)|\
|
368
|
+
(?# 6: PLUS )(\+))\
|
369
|
+
(?# 7: ATOM remaining after prefix )(#{Patterns::ATOMISH})?\
|
307
370
|
(?# This enables greedy alternation without lookahead, in linear time.)\
|
308
371
|
)|\
|
309
372
|
(?# Also need to check for ATOM without a subtype prefix.)\
|
310
|
-
(?#
|
311
|
-
(?#
|
312
|
-
(?#
|
313
|
-
(?#
|
314
|
-
(?#
|
315
|
-
(?#
|
316
|
-
(?#
|
317
|
-
(?#
|
318
|
-
(?#
|
319
|
-
(?#
|
320
|
-
(?#
|
321
|
-
(?#
|
373
|
+
(?# 8: ATOM )(#{Patterns::ATOMISH})|\
|
374
|
+
(?# 9: QUOTED )#{Patterns::QUOTED_rev2}|\
|
375
|
+
(?# 10: LPAR )(\()|\
|
376
|
+
(?# 11: RPAR )(\))|\
|
377
|
+
(?# 12: BSLASH )(\\)|\
|
378
|
+
(?# 13: STAR )(\*)|\
|
379
|
+
(?# 14: LBRA )(\[)|\
|
380
|
+
(?# 15: RBRA )(\])|\
|
381
|
+
(?# 16: LITERAL )#{Patterns::LITERAL}|\
|
382
|
+
(?# 17: PERCENT )(%)|\
|
383
|
+
(?# 18: CRLF )(\r\n)|\
|
384
|
+
(?# 19: EOF )(\z))/ni
|
322
385
|
|
323
386
|
# envelope, body(structure), namespaces
|
324
387
|
DATA_REGEXP = /\G(?:\
|
@@ -359,6 +422,9 @@ module Net
|
|
359
422
|
# string = quoted / literal
|
360
423
|
def_token_matchers :string, T_QUOTED, T_LITERAL
|
361
424
|
|
425
|
+
# used by nstring8 = nstring / literal8
|
426
|
+
def_token_matchers :string8, T_QUOTED, T_LITERAL, T_LITERAL8
|
427
|
+
|
362
428
|
# use where string represents "LABEL" values
|
363
429
|
def_token_matchers :case_insensitive__string,
|
364
430
|
T_QUOTED, T_LITERAL,
|
@@ -390,6 +456,24 @@ module Net
|
|
390
456
|
# ATOM-CHAR = <any CHAR except atom-specials>
|
391
457
|
ATOM_TOKENS = [T_ATOM, T_NUMBER, T_NIL, T_LBRA, T_PLUS]
|
392
458
|
|
459
|
+
SEQUENCE_SET_TOKENS = [T_ATOM, T_NUMBER, T_STAR]
|
460
|
+
|
461
|
+
# sequence-set = (seq-number / seq-range) ["," sequence-set]
|
462
|
+
# sequence-set =/ seq-last-command
|
463
|
+
# ; Allow for "result of the last command"
|
464
|
+
# ; indicator.
|
465
|
+
# seq-last-command = "$"
|
466
|
+
#
|
467
|
+
# *note*: doesn't match seq-last-command
|
468
|
+
def sequence_set
|
469
|
+
str = combine_adjacent(*SEQUENCE_SET_TOKENS)
|
470
|
+
if Patterns::SEQUENCE_SET_STR.match?(str)
|
471
|
+
SequenceSet[str]
|
472
|
+
else
|
473
|
+
parse_error("unexpected atom %p, expected sequence-set", str)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
393
477
|
# ASTRING-CHAR = ATOM-CHAR / resp-specials
|
394
478
|
# resp-specials = "]"
|
395
479
|
ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA].freeze
|
@@ -460,6 +544,10 @@ module Net
|
|
460
544
|
NIL? ? nil : string
|
461
545
|
end
|
462
546
|
|
547
|
+
def nstring8
|
548
|
+
NIL? ? nil : string8
|
549
|
+
end
|
550
|
+
|
463
551
|
def nquoted
|
464
552
|
NIL? ? nil : quoted
|
465
553
|
end
|
@@ -469,6 +557,60 @@ module Net
|
|
469
557
|
NIL? ? nil : case_insensitive__string
|
470
558
|
end
|
471
559
|
|
560
|
+
# tagged-ext-comp = astring /
|
561
|
+
# tagged-ext-comp *(SP tagged-ext-comp) /
|
562
|
+
# "(" tagged-ext-comp ")"
|
563
|
+
# ; Extensions that follow this general
|
564
|
+
# ; syntax should use nstring instead of
|
565
|
+
# ; astring when appropriate in the context
|
566
|
+
# ; of the extension.
|
567
|
+
# ; Note that a message set or a "number"
|
568
|
+
# ; can always be represented as an "atom".
|
569
|
+
# ; A URL should be represented as
|
570
|
+
# ; a "quoted" string.
|
571
|
+
def tagged_ext_comp
|
572
|
+
vals = []
|
573
|
+
while true
|
574
|
+
vals << case lookahead!(*ASTRING_TOKENS, T_LPAR).symbol
|
575
|
+
when T_LPAR then lpar; ary = tagged_ext_comp; rpar; ary
|
576
|
+
when T_NUMBER then number
|
577
|
+
else astring
|
578
|
+
end
|
579
|
+
SP? or break
|
580
|
+
end
|
581
|
+
vals
|
582
|
+
end
|
583
|
+
|
584
|
+
# tagged-ext-simple is a subset of atom
|
585
|
+
# TODO: recognize sequence-set in the lexer
|
586
|
+
#
|
587
|
+
# tagged-ext-simple = sequence-set / number / number64
|
588
|
+
def tagged_ext_simple
|
589
|
+
number? || sequence_set
|
590
|
+
end
|
591
|
+
|
592
|
+
# tagged-ext-val = tagged-ext-simple /
|
593
|
+
# "(" [tagged-ext-comp] ")"
|
594
|
+
def tagged_ext_val
|
595
|
+
if lpar?
|
596
|
+
_ = peek_rpar? ? [] : tagged_ext_comp
|
597
|
+
rpar
|
598
|
+
_
|
599
|
+
else
|
600
|
+
tagged_ext_simple
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
# mailbox = "INBOX" / astring
|
605
|
+
# ; INBOX is case-insensitive. All case variants of
|
606
|
+
# ; INBOX (e.g., "iNbOx") MUST be interpreted as INBOX
|
607
|
+
# ; not as an astring. An astring which consists of
|
608
|
+
# ; the case-insensitive sequence "I" "N" "B" "O" "X"
|
609
|
+
# ; is considered to be INBOX and not an astring.
|
610
|
+
# ; Refer to section 5.1 for further
|
611
|
+
# ; semantic details of mailbox names.
|
612
|
+
alias mailbox astring
|
613
|
+
|
472
614
|
# valid number ranges are not enforced by parser
|
473
615
|
# number64 = 1*DIGIT
|
474
616
|
# ; Unsigned 63-bit integer
|
@@ -494,6 +636,12 @@ module Net
|
|
494
636
|
# ; Strictly ascending
|
495
637
|
alias uniqueid nz_number
|
496
638
|
|
639
|
+
# valid number ranges are not enforced by parser
|
640
|
+
#
|
641
|
+
# a 64-bit unsigned integer and is the decimal equivalent for the ID hex
|
642
|
+
# string used in the web interface and the Gmail API.
|
643
|
+
alias x_gm_id number
|
644
|
+
|
497
645
|
# [RFC3501 & RFC9051:]
|
498
646
|
# response = *(continue-req / response-data) response-done
|
499
647
|
#
|
@@ -630,34 +778,47 @@ module Net
|
|
630
778
|
|
631
779
|
# RFC3501 & RFC9051:
|
632
780
|
# response-tagged = tag SP resp-cond-state CRLF
|
633
|
-
#
|
634
|
-
# resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
|
635
|
-
# ; Status condition
|
636
|
-
#
|
637
|
-
# tag = 1*<any ASTRING-CHAR except "+">
|
638
781
|
def response_tagged
|
639
|
-
tag
|
640
|
-
name = resp_cond_state__name; SP!
|
641
|
-
TaggedResponse.new(tag, name, resp_text, @str)
|
782
|
+
TaggedResponse.new(tag, *(SP!; resp_cond_state), @str)
|
642
783
|
end
|
643
784
|
|
644
785
|
# RFC3501 & RFC9051:
|
645
786
|
# resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
|
787
|
+
#
|
788
|
+
# NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing
|
789
|
+
# servers), we don't require a final SP and instead parse this as:
|
790
|
+
#
|
791
|
+
# resp-cond-state = ("OK" / "NO" / "BAD") [SP resp-text]
|
792
|
+
def resp_cond_state
|
793
|
+
[resp_cond_state__name, SP? ? resp_text : ResponseText::EMPTY]
|
794
|
+
end
|
795
|
+
|
646
796
|
def resp_cond_state__untagged
|
647
|
-
|
648
|
-
UntaggedResponse.new(name, resp_text, @str)
|
797
|
+
UntaggedResponse.new(*resp_cond_state, @str)
|
649
798
|
end
|
650
799
|
|
651
800
|
# resp-cond-auth = ("OK" / "PREAUTH") SP resp-text
|
801
|
+
#
|
802
|
+
# NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing
|
803
|
+
# servers), we don't require a final SP and instead parse this as:
|
804
|
+
#
|
805
|
+
# resp-cond-auth = ("OK" / "PREAUTH") [SP resp-text]
|
652
806
|
def resp_cond_auth
|
653
|
-
|
654
|
-
|
807
|
+
UntaggedResponse.new(resp_cond_auth__name,
|
808
|
+
SP? ? resp_text : ResponseText::EMPTY,
|
809
|
+
@str)
|
655
810
|
end
|
656
811
|
|
657
812
|
# resp-cond-bye = "BYE" SP resp-text
|
813
|
+
#
|
814
|
+
# NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing
|
815
|
+
# servers), we don't require a final SP and instead parse this as:
|
816
|
+
#
|
817
|
+
# resp-cond-bye = "BYE" [SP resp-text]
|
658
818
|
def resp_cond_bye
|
659
|
-
|
660
|
-
|
819
|
+
UntaggedResponse.new(label(BYE),
|
820
|
+
SP? ? resp_text : ResponseText::EMPTY,
|
821
|
+
@str)
|
661
822
|
end
|
662
823
|
|
663
824
|
# message-data = nz-number SP ("EXPUNGE" / ("FETCH" SP msg-att))
|
@@ -740,10 +901,17 @@ module Net
|
|
740
901
|
when "ENVELOPE" then envelope
|
741
902
|
when "INTERNALDATE" then date_time
|
742
903
|
when "RFC822.SIZE" then number64
|
904
|
+
when /\ABINARY\[/ni then nstring8 # BINARY, IMAP4rev2
|
905
|
+
when /\ABINARY\.SIZE\[/ni then number # BINARY, IMAP4rev2
|
743
906
|
when "RFC822" then nstring # not in rev2
|
744
907
|
when "RFC822.HEADER" then nstring # not in rev2
|
745
908
|
when "RFC822.TEXT" then nstring # not in rev2
|
746
909
|
when "MODSEQ" then parens__modseq # CONDSTORE
|
910
|
+
when "EMAILID" then parens__objectid # OBJECTID
|
911
|
+
when "THREADID" then nparens__objectid # OBJECTID
|
912
|
+
when "X-GM-MSGID" then x_gm_id # GMail
|
913
|
+
when "X-GM-THRID" then x_gm_id # GMail
|
914
|
+
when "X-GM-LABELS" then x_gm_labels # GMail
|
747
915
|
else parse_error("unknown attribute `%s' for {%d}", name, n)
|
748
916
|
end
|
749
917
|
attr[name] = val
|
@@ -762,46 +930,75 @@ module Net
|
|
762
930
|
lbra? and rbra
|
763
931
|
when "BODY"
|
764
932
|
peek_lbra? and name << section and
|
765
|
-
peek_str?("<") and name <<
|
933
|
+
peek_str?("<") and name << gt__number__lt # partial
|
934
|
+
when "BINARY", "BINARY.SIZE"
|
935
|
+
name << section_binary
|
936
|
+
# see https://www.rfc-editor.org/errata/eid7246 and the note above
|
937
|
+
peek_str?("<") and name << gt__number__lt # partial
|
766
938
|
end
|
767
939
|
name
|
768
940
|
end
|
769
941
|
|
942
|
+
# this represents the partial size for BODY or BINARY
|
943
|
+
alias gt__number__lt atom
|
944
|
+
|
945
|
+
# RFC3501 & RFC9051:
|
946
|
+
# envelope = "(" env-date SP env-subject SP env-from SP
|
947
|
+
# env-sender SP env-reply-to SP env-to SP env-cc SP
|
948
|
+
# env-bcc SP env-in-reply-to SP env-message-id ")"
|
770
949
|
def envelope
|
771
950
|
@lex_state = EXPR_DATA
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
reply_to = address_list
|
787
|
-
match(T_SPACE)
|
788
|
-
to = address_list
|
789
|
-
match(T_SPACE)
|
790
|
-
cc = address_list
|
791
|
-
match(T_SPACE)
|
792
|
-
bcc = address_list
|
793
|
-
match(T_SPACE)
|
794
|
-
in_reply_to = nstring
|
795
|
-
match(T_SPACE)
|
796
|
-
message_id = nstring
|
797
|
-
match(T_RPAR)
|
798
|
-
result = Envelope.new(date, subject, from, sender, reply_to,
|
799
|
-
to, cc, bcc, in_reply_to, message_id)
|
800
|
-
end
|
951
|
+
lpar; date = env_date
|
952
|
+
SP!; subject = env_subject
|
953
|
+
SP!; from = env_from
|
954
|
+
SP!; sender = env_sender
|
955
|
+
SP!; reply_to = env_reply_to
|
956
|
+
SP!; to = env_to
|
957
|
+
SP!; cc = env_cc
|
958
|
+
SP!; bcc = env_bcc
|
959
|
+
SP!; in_reply_to = env_in_reply_to
|
960
|
+
SP!; message_id = env_message_id
|
961
|
+
rpar
|
962
|
+
Envelope.new(date, subject, from, sender, reply_to,
|
963
|
+
to, cc, bcc, in_reply_to, message_id)
|
964
|
+
ensure
|
801
965
|
@lex_state = EXPR_BEG
|
802
|
-
return result
|
803
966
|
end
|
804
967
|
|
968
|
+
# env-date = nstring
|
969
|
+
# env-subject = nstring
|
970
|
+
# env-in-reply-to = nstring
|
971
|
+
# env-message-id = nstring
|
972
|
+
alias env_date nstring
|
973
|
+
alias env_subject nstring
|
974
|
+
alias env_in_reply_to nstring
|
975
|
+
alias env_message_id nstring
|
976
|
+
|
977
|
+
# env-from = "(" 1*address ")" / nil
|
978
|
+
# env-sender = "(" 1*address ")" / nil
|
979
|
+
# env-reply-to = "(" 1*address ")" / nil
|
980
|
+
# env-to = "(" 1*address ")" / nil
|
981
|
+
# env-cc = "(" 1*address ")" / nil
|
982
|
+
# env-bcc = "(" 1*address ")" / nil
|
983
|
+
def nlist__address
|
984
|
+
return if NIL?
|
985
|
+
lpar; list = [address]; list << address until (quirky_SP?; rpar?)
|
986
|
+
list
|
987
|
+
end
|
988
|
+
|
989
|
+
alias env_from nlist__address
|
990
|
+
alias env_sender nlist__address
|
991
|
+
alias env_reply_to nlist__address
|
992
|
+
alias env_to nlist__address
|
993
|
+
alias env_cc nlist__address
|
994
|
+
alias env_bcc nlist__address
|
995
|
+
|
996
|
+
# Used when servers erroneously send an extra SP.
|
997
|
+
#
|
998
|
+
# As of 2023-11-28, Outlook.com (still) sends SP
|
999
|
+
# between +address+ in <tt>env-*</tt> lists.
|
1000
|
+
alias quirky_SP? SP?
|
1001
|
+
|
805
1002
|
# date-time = DQUOTE date-day-fixed "-" date-month "-" date-year
|
806
1003
|
# SP time SP zone DQUOTE
|
807
1004
|
alias date_time quoted
|
@@ -1070,6 +1267,13 @@ module Net
|
|
1070
1267
|
str << rbra
|
1071
1268
|
end
|
1072
1269
|
|
1270
|
+
# section-binary = "[" [section-part] "]"
|
1271
|
+
def section_binary
|
1272
|
+
str = +lbra
|
1273
|
+
str << section_part unless peek_rbra?
|
1274
|
+
str << rbra
|
1275
|
+
end
|
1276
|
+
|
1073
1277
|
# section-spec = section-msgtext / (section-part ["." section-text])
|
1074
1278
|
# section-msgtext = "HEADER" /
|
1075
1279
|
# "HEADER.FIELDS" [".NOT"] SP header-list /
|
@@ -1100,6 +1304,11 @@ module Net
|
|
1100
1304
|
str << rpar
|
1101
1305
|
end
|
1102
1306
|
|
1307
|
+
# section-part = nz-number *("." nz-number)
|
1308
|
+
# ; body part reference.
|
1309
|
+
# ; Allows for accessing nested body parts.
|
1310
|
+
alias section_part atom
|
1311
|
+
|
1103
1312
|
# RFC3501 & RFC9051:
|
1104
1313
|
# header-fld-name = astring
|
1105
1314
|
#
|
@@ -1148,21 +1357,20 @@ module Net
|
|
1148
1357
|
alias mailbox_data__lsub mailbox_data__list
|
1149
1358
|
alias mailbox_data__xlist mailbox_data__list
|
1150
1359
|
|
1360
|
+
# mailbox-list = "(" [mbx-list-flags] ")" SP
|
1361
|
+
# (DQUOTE QUOTED-CHAR DQUOTE / nil) SP mailbox
|
1362
|
+
# [SP mbox-list-extended]
|
1363
|
+
# ; This is the list information pointed to by the ABNF
|
1364
|
+
# ; item "mailbox-data", which is defined above
|
1151
1365
|
def mailbox_list
|
1152
|
-
attr
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
else
|
1158
|
-
delim = token.value
|
1159
|
-
end
|
1160
|
-
match(T_SPACE)
|
1161
|
-
name = astring
|
1162
|
-
return MailboxList.new(attr, delim, name)
|
1366
|
+
lpar; attr = peek_rpar? ? [] : mbx_list_flags; rpar
|
1367
|
+
SP!; delim = nquoted
|
1368
|
+
SP!; name = mailbox
|
1369
|
+
# TODO: mbox-list-extended
|
1370
|
+
MailboxList.new(attr, delim, name)
|
1163
1371
|
end
|
1164
1372
|
|
1165
|
-
def
|
1373
|
+
def quota_response
|
1166
1374
|
# If quota never established, get back
|
1167
1375
|
# `NO Quota root does not exist'.
|
1168
1376
|
# If quota removed, get `()' after the
|
@@ -1195,7 +1403,7 @@ module Net
|
|
1195
1403
|
end
|
1196
1404
|
end
|
1197
1405
|
|
1198
|
-
def
|
1406
|
+
def quotaroot_response
|
1199
1407
|
# Similar to getquota, but only admin can use getquota.
|
1200
1408
|
token = match(T_ATOM)
|
1201
1409
|
name = token.value.upcase
|
@@ -1254,124 +1462,145 @@ module Net
|
|
1254
1462
|
# mailbox-data = obsolete-search-response / ...
|
1255
1463
|
# obsolete-search-response = "SEARCH" *(SP nz-number)
|
1256
1464
|
def mailbox_data__search
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1260
|
-
if
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
token = lookahead
|
1265
|
-
case token.symbol
|
1266
|
-
when T_CRLF
|
1267
|
-
break
|
1268
|
-
when T_SPACE
|
1269
|
-
shift_token
|
1270
|
-
when T_NUMBER
|
1271
|
-
data.push(number)
|
1272
|
-
when T_LPAR
|
1273
|
-
# TODO: include the MODSEQ value in a response
|
1274
|
-
shift_token
|
1275
|
-
match(T_ATOM)
|
1276
|
-
match(T_SPACE)
|
1277
|
-
match(T_NUMBER)
|
1278
|
-
match(T_RPAR)
|
1279
|
-
end
|
1280
|
-
end
|
1281
|
-
else
|
1282
|
-
data = []
|
1465
|
+
name = label_in("SEARCH", "SORT")
|
1466
|
+
data = []
|
1467
|
+
while _ = SP? && nz_number? do data << _ end
|
1468
|
+
if lpar?
|
1469
|
+
label("MODSEQ"); SP!
|
1470
|
+
modseq = mod_sequence_value
|
1471
|
+
rpar
|
1283
1472
|
end
|
1284
|
-
|
1473
|
+
data = SearchResult.new(data, modseq: modseq)
|
1474
|
+
UntaggedResponse.new(name, data, @str)
|
1285
1475
|
end
|
1286
1476
|
alias sort_data mailbox_data__search
|
1287
1477
|
|
1478
|
+
# RFC5256: THREAD
|
1479
|
+
# thread-data = "THREAD" [SP 1*thread-list]
|
1288
1480
|
def thread_data
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
if token.symbol == T_SPACE
|
1294
|
-
threads = []
|
1295
|
-
|
1296
|
-
while true
|
1297
|
-
shift_token
|
1298
|
-
token = lookahead
|
1299
|
-
|
1300
|
-
case token.symbol
|
1301
|
-
when T_LPAR
|
1302
|
-
threads << thread_branch(token)
|
1303
|
-
when T_CRLF
|
1304
|
-
break
|
1305
|
-
end
|
1306
|
-
end
|
1307
|
-
else
|
1308
|
-
# no member
|
1309
|
-
threads = []
|
1481
|
+
name = label("THREAD")
|
1482
|
+
threads = []
|
1483
|
+
if SP?
|
1484
|
+
threads << thread_list while lookahead_thread_list?
|
1310
1485
|
end
|
1311
|
-
|
1312
|
-
return UntaggedResponse.new(name, threads, @str)
|
1486
|
+
UntaggedResponse.new(name, threads, @str)
|
1313
1487
|
end
|
1314
1488
|
|
1315
|
-
|
1316
|
-
|
1317
|
-
lastmember = nil
|
1489
|
+
alias lookahead_thread_list? lookahead_lpar?
|
1490
|
+
alias lookahead_thread_nested? lookahead_thread_list?
|
1318
1491
|
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
end
|
1332
|
-
lastmember = newmember
|
1333
|
-
when T_SPACE
|
1334
|
-
# do nothing
|
1335
|
-
when T_LPAR
|
1336
|
-
if rootmember.nil?
|
1337
|
-
# dummy member
|
1338
|
-
lastmember = rootmember = ThreadMember.new(nil, [])
|
1339
|
-
end
|
1492
|
+
# RFC5256: THREAD
|
1493
|
+
# thread-list = "(" (thread-members / thread-nested) ")"
|
1494
|
+
def thread_list
|
1495
|
+
lpar
|
1496
|
+
thread = if lookahead_thread_nested?
|
1497
|
+
ThreadMember.new(nil, thread_nested)
|
1498
|
+
else
|
1499
|
+
thread_members
|
1500
|
+
end
|
1501
|
+
rpar
|
1502
|
+
thread
|
1503
|
+
end
|
1340
1504
|
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1505
|
+
# RFC5256: THREAD
|
1506
|
+
# thread-members = nz-number *(SP nz-number) [SP thread-nested]
|
1507
|
+
def thread_members
|
1508
|
+
members = []
|
1509
|
+
members << nz_number # thread root
|
1510
|
+
while SP?
|
1511
|
+
case lookahead!(T_NUMBER, T_LPAR).symbol
|
1512
|
+
when T_NUMBER then members << nz_number
|
1513
|
+
else nested = thread_nested; break
|
1344
1514
|
end
|
1345
1515
|
end
|
1516
|
+
members.reverse.inject(nested || []) {|subthreads, number|
|
1517
|
+
[ThreadMember.new(number, subthreads)]
|
1518
|
+
}.first
|
1519
|
+
end
|
1346
1520
|
|
1347
|
-
|
1521
|
+
# RFC5256: THREAD
|
1522
|
+
# thread-nested = 2*thread-list
|
1523
|
+
def thread_nested
|
1524
|
+
nested = [thread_list, thread_list]
|
1525
|
+
while lookahead_thread_list? do nested << thread_list end
|
1526
|
+
nested
|
1348
1527
|
end
|
1349
1528
|
|
1529
|
+
# mailbox-data =/ "STATUS" SP mailbox SP "(" [status-att-list] ")"
|
1350
1530
|
def mailbox_data__status
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1531
|
+
resp_name = label("STATUS"); SP!
|
1532
|
+
mbox_name = mailbox; SP!
|
1533
|
+
lpar; attr = status_att_list; rpar
|
1534
|
+
UntaggedResponse.new(resp_name, StatusData.new(mbox_name, attr), @str)
|
1535
|
+
end
|
1536
|
+
|
1537
|
+
# RFC3501
|
1538
|
+
# status-att-list = status-att SP number *(SP status-att SP number)
|
1539
|
+
# RFC4466, RFC9051, and RFC3501 Errata
|
1540
|
+
# status-att-list = status-att-val *(SP status-att-val)
|
1541
|
+
def status_att_list
|
1542
|
+
attrs = [status_att_val]
|
1543
|
+
while SP? do attrs << status_att_val end
|
1544
|
+
attrs.to_h
|
1545
|
+
end
|
1546
|
+
|
1547
|
+
# RFC3501 Errata:
|
1548
|
+
# status-att-val = ("MESSAGES" SP number) / ("RECENT" SP number) /
|
1549
|
+
# ("UIDNEXT" SP nz-number) / ("UIDVALIDITY" SP nz-number) /
|
1550
|
+
# ("UNSEEN" SP number)
|
1551
|
+
# RFC4466:
|
1552
|
+
# status-att-val = ("MESSAGES" SP number) /
|
1553
|
+
# ("RECENT" SP number) /
|
1554
|
+
# ("UIDNEXT" SP nz-number) /
|
1555
|
+
# ("UIDVALIDITY" SP nz-number) /
|
1556
|
+
# ("UNSEEN" SP number)
|
1557
|
+
# ;; Extensions to the STATUS responses
|
1558
|
+
# ;; should extend this production.
|
1559
|
+
# ;; Extensions should use the generic
|
1560
|
+
# ;; syntax defined by tagged-ext.
|
1561
|
+
# RFC9051:
|
1562
|
+
# status-att-val = ("MESSAGES" SP number) /
|
1563
|
+
# ("UIDNEXT" SP nz-number) /
|
1564
|
+
# ("UIDVALIDITY" SP nz-number) /
|
1565
|
+
# ("UNSEEN" SP number) /
|
1566
|
+
# ("DELETED" SP number) /
|
1567
|
+
# ("SIZE" SP number64)
|
1568
|
+
# ; Extensions to the STATUS responses
|
1569
|
+
# ; should extend this production.
|
1570
|
+
# ; Extensions should use the generic
|
1571
|
+
# ; syntax defined by tagged-ext.
|
1572
|
+
# RFC7162:
|
1573
|
+
# status-att-val =/ "HIGHESTMODSEQ" SP mod-sequence-valzer
|
1574
|
+
# ;; Extends non-terminal defined in [RFC4466].
|
1575
|
+
# ;; Value 0 denotes that the mailbox doesn't
|
1576
|
+
# ;; support persistent mod-sequences
|
1577
|
+
# ;; as described in Section 3.1.2.2.
|
1578
|
+
# RFC7889:
|
1579
|
+
# status-att-val =/ "APPENDLIMIT" SP (number / nil)
|
1580
|
+
# ;; status-att-val is defined in RFC 4466
|
1581
|
+
# RFC8438:
|
1582
|
+
# status-att-val =/ "SIZE" SP number64
|
1583
|
+
# RFC8474:
|
1584
|
+
# status-att-val =/ "MAILBOXID" SP "(" objectid ")"
|
1585
|
+
# ; follows tagged-ext production from [RFC4466]
|
1586
|
+
def status_att_val
|
1587
|
+
key = tagged_ext_label
|
1588
|
+
SP!
|
1589
|
+
val =
|
1590
|
+
case key
|
1591
|
+
when "MESSAGES" then number # RFC3501, RFC9051
|
1592
|
+
when "UNSEEN" then number # RFC3501, RFC9051
|
1593
|
+
when "DELETED" then number # RFC3501, RFC9051
|
1594
|
+
when "UIDNEXT" then nz_number # RFC3501, RFC9051
|
1595
|
+
when "UIDVALIDITY" then nz_number # RFC3501, RFC9051
|
1596
|
+
when "RECENT" then number # RFC3501 (obsolete)
|
1597
|
+
when "SIZE" then number64 # RFC8483, RFC9051
|
1598
|
+
when "HIGHESTMODSEQ" then mod_sequence_valzer # RFC7162
|
1599
|
+
when "MAILBOXID" then parens__objectid # RFC8474
|
1600
|
+
else
|
1601
|
+
number? || ExtensionData.new(tagged_ext_val)
|
1366
1602
|
end
|
1367
|
-
|
1368
|
-
key = token.value.upcase
|
1369
|
-
match(T_SPACE)
|
1370
|
-
val = number
|
1371
|
-
attr[key] = val
|
1372
|
-
end
|
1373
|
-
data = StatusData.new(mailbox, attr)
|
1374
|
-
return UntaggedResponse.new(name, data, @str)
|
1603
|
+
[key, val]
|
1375
1604
|
end
|
1376
1605
|
|
1377
1606
|
# The presence of "IMAP4rev1" or "IMAP4rev2" is unenforced here.
|
@@ -1573,6 +1802,11 @@ module Net
|
|
1573
1802
|
# resp-text-code =/ "HIGHESTMODSEQ" SP mod-sequence-value /
|
1574
1803
|
# "NOMODSEQ" /
|
1575
1804
|
# "MODIFIED" SP sequence-set
|
1805
|
+
# RFC7162 (QRESYNC):
|
1806
|
+
# resp-text-code =/ "CLOSED"
|
1807
|
+
#
|
1808
|
+
# RFC8474: OBJECTID
|
1809
|
+
# resp-text-code =/ "MAILBOXID" SP "(" objectid ")"
|
1576
1810
|
def resp_text_code
|
1577
1811
|
name = resp_text_code__name
|
1578
1812
|
data =
|
@@ -1591,7 +1825,10 @@ module Net
|
|
1591
1825
|
"EXPUNGEISSUED", "CORRUPTION", "SERVERBUG", "CLIENTBUG", "CANNOT",
|
1592
1826
|
"LIMIT", "OVERQUOTA", "ALREADYEXISTS", "NONEXISTENT", "CLOSED",
|
1593
1827
|
"NOTSAVED", "UIDNOTSTICKY", "UNKNOWN-CTE", "HASCHILDREN"
|
1594
|
-
when "NOMODSEQ" # CONDSTORE
|
1828
|
+
when "NOMODSEQ" then nil # CONDSTORE
|
1829
|
+
when "HIGHESTMODSEQ" then SP!; mod_sequence_value # CONDSTORE
|
1830
|
+
when "MODIFIED" then SP!; sequence_set # CONDSTORE
|
1831
|
+
when "MAILBOXID" then SP!; parens__objectid # RFC8474: OBJECTID
|
1595
1832
|
else
|
1596
1833
|
SP? and text_chars_except_rbra
|
1597
1834
|
end
|
@@ -1638,91 +1875,86 @@ module Net
|
|
1638
1875
|
UIDPlusData.new(validity, src_uids, dst_uids)
|
1639
1876
|
end
|
1640
1877
|
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
when T_SPACE
|
1656
|
-
shift_token
|
1657
|
-
end
|
1658
|
-
result.push(address)
|
1659
|
-
end
|
1660
|
-
return result
|
1661
|
-
end
|
1662
|
-
end
|
1663
|
-
|
1664
|
-
ADDRESS_REGEXP = /\G\
|
1665
|
-
(?# 1: NAME )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
|
1666
|
-
(?# 2: ROUTE )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
|
1667
|
-
(?# 3: MAILBOX )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
|
1668
|
-
(?# 4: HOST )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)")\
|
1669
|
-
\)/ni
|
1670
|
-
|
1878
|
+
ADDRESS_REGEXP = /\G
|
1879
|
+
\( (?: NIL | #{Patterns::QUOTED_rev2} ) # 1: NAME
|
1880
|
+
\s (?: NIL | #{Patterns::QUOTED_rev2} ) # 2: ROUTE
|
1881
|
+
\s (?: NIL | #{Patterns::QUOTED_rev2} ) # 3: MAILBOX
|
1882
|
+
\s (?: NIL | #{Patterns::QUOTED_rev2} ) # 4: HOST
|
1883
|
+
\)
|
1884
|
+
/nix
|
1885
|
+
|
1886
|
+
# address = "(" addr-name SP addr-adl SP addr-mailbox SP
|
1887
|
+
# addr-host ")"
|
1888
|
+
# addr-adl = nstring
|
1889
|
+
# addr-host = nstring
|
1890
|
+
# addr-mailbox = nstring
|
1891
|
+
# addr-name = nstring
|
1671
1892
|
def address
|
1672
|
-
match(
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
end
|
1683
|
-
else
|
1684
|
-
name = nstring
|
1685
|
-
match(T_SPACE)
|
1686
|
-
route = nstring
|
1687
|
-
match(T_SPACE)
|
1688
|
-
mailbox = nstring
|
1689
|
-
match(T_SPACE)
|
1690
|
-
host = nstring
|
1691
|
-
match(T_RPAR)
|
1893
|
+
if (match = accept_re(ADDRESS_REGEXP))
|
1894
|
+
# note that "NIL" isn't captured by the regexp
|
1895
|
+
name, route, mailbox, host = match.captures
|
1896
|
+
.map { Patterns.unescape_quoted _1 }
|
1897
|
+
else # address may include literals
|
1898
|
+
lpar; name = addr_name
|
1899
|
+
SP!; route = addr_adl
|
1900
|
+
SP!; mailbox = addr_mailbox
|
1901
|
+
SP!; host = addr_host
|
1902
|
+
rpar
|
1692
1903
|
end
|
1693
|
-
|
1904
|
+
Address.new(name, route, mailbox, host)
|
1694
1905
|
end
|
1695
1906
|
|
1907
|
+
alias addr_adl nstring
|
1908
|
+
alias addr_host nstring
|
1909
|
+
alias addr_mailbox nstring
|
1910
|
+
alias addr_name nstring
|
1911
|
+
|
1696
1912
|
# flag-list = "(" [flag *(SP flag)] ")"
|
1697
1913
|
def flag_list
|
1698
|
-
|
1699
|
-
.split(nil)
|
1700
|
-
|
1914
|
+
if (match = accept_re(Patterns::FLAG_LIST))
|
1915
|
+
match[1].split(nil)
|
1916
|
+
.map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 }
|
1917
|
+
else
|
1918
|
+
quirky__flag_list "flags-list"
|
1919
|
+
end
|
1701
1920
|
end
|
1702
1921
|
|
1703
1922
|
# "(" [flag-perm *(SP flag-perm)] ")"
|
1704
1923
|
def flag_perm__list
|
1705
|
-
|
1706
|
-
.split(nil)
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
#
|
1714
|
-
#
|
1715
|
-
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
#
|
1722
|
-
|
1723
|
-
def parens__mbx_list_flags
|
1924
|
+
if (match = accept_re(Patterns::FLAG_PERM_LIST))
|
1925
|
+
match[1].split(nil)
|
1926
|
+
.map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 }
|
1927
|
+
else
|
1928
|
+
quirky__flag_list "PERMANENTFLAGS flag-perm list"
|
1929
|
+
end
|
1930
|
+
end
|
1931
|
+
|
1932
|
+
# This allows illegal "]" in flag names (Gmail),
|
1933
|
+
# or "\*" in a FLAGS response (greenmail).
|
1934
|
+
def quirky__flag_list(name)
|
1935
|
+
match_re(Patterns::QUIRKY_FLAGS_LIST, "quirks mode #{name}")[1]
|
1936
|
+
.scan(Patterns::QUIRKY_FLAG)
|
1937
|
+
.map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 }
|
1938
|
+
end
|
1939
|
+
|
1940
|
+
# See Patterns::MBX_LIST_FLAGS
|
1941
|
+
def mbx_list_flags
|
1724
1942
|
match_re(Patterns::MBX_LIST_FLAGS, "mbx-list-flags")[1]
|
1725
|
-
.split(nil)
|
1943
|
+
.split(nil)
|
1944
|
+
.map! { _1.delete_prefix!("\\"); _1.capitalize.to_sym }
|
1945
|
+
end
|
1946
|
+
|
1947
|
+
# See https://developers.google.com/gmail/imap/imap-extensions
|
1948
|
+
def x_gm_label; accept(T_BSLASH) ? atom.capitalize.to_sym : astring end
|
1949
|
+
|
1950
|
+
# See https://developers.google.com/gmail/imap/imap-extensions
|
1951
|
+
def x_gm_labels
|
1952
|
+
lpar; return [] if rpar?
|
1953
|
+
labels = []
|
1954
|
+
labels << x_gm_label
|
1955
|
+
labels << x_gm_label while SP?
|
1956
|
+
rpar
|
1957
|
+
labels
|
1726
1958
|
end
|
1727
1959
|
|
1728
1960
|
# See https://www.rfc-editor.org/errata/rfc3501
|
@@ -1742,8 +1974,21 @@ module Net
|
|
1742
1974
|
# ;; Per-message mod-sequence.
|
1743
1975
|
alias permsg_modsequence mod_sequence_value
|
1744
1976
|
|
1977
|
+
# RFC7162:
|
1978
|
+
# mod-sequence-valzer = "0" / mod-sequence-value
|
1979
|
+
alias mod_sequence_valzer number64
|
1980
|
+
|
1745
1981
|
def parens__modseq; lpar; _ = permsg_modsequence; rpar; _ end
|
1746
1982
|
|
1983
|
+
# RFC8474:
|
1984
|
+
# objectid = 1*255(ALPHA / DIGIT / "_" / "-")
|
1985
|
+
# ; characters in object identifiers are case
|
1986
|
+
# ; significant
|
1987
|
+
alias objectid atom
|
1988
|
+
|
1989
|
+
def parens__objectid; lpar; _ = objectid; rpar; _ end
|
1990
|
+
def nparens__objectid; NIL? ? nil : parens__objectid end
|
1991
|
+
|
1747
1992
|
# RFC-4315 (UIDPLUS) or RFC9051 (IMAP4rev2):
|
1748
1993
|
# uid-set = (uniqueid / uid-range) *("," uid-set)
|
1749
1994
|
# uid-range = (uniqueid ":" uniqueid)
|
@@ -1789,42 +2034,47 @@ module Net
|
|
1789
2034
|
@pos = $~.end(0)
|
1790
2035
|
if $1
|
1791
2036
|
return Token.new(T_SPACE, $+)
|
1792
|
-
elsif $2
|
2037
|
+
elsif $2
|
2038
|
+
len = $+.to_i
|
2039
|
+
val = @str[@pos, len]
|
2040
|
+
@pos += len
|
2041
|
+
return Token.new(T_LITERAL8, val)
|
2042
|
+
elsif $3 && $7
|
1793
2043
|
# greedily match ATOM, prefixed with NUMBER, NIL, or PLUS.
|
1794
|
-
return Token.new(T_ATOM, $
|
1795
|
-
elsif $3
|
1796
|
-
return Token.new(T_NIL, $+)
|
2044
|
+
return Token.new(T_ATOM, $3)
|
1797
2045
|
elsif $4
|
1798
|
-
return Token.new(
|
2046
|
+
return Token.new(T_NIL, $+)
|
1799
2047
|
elsif $5
|
2048
|
+
return Token.new(T_NUMBER, $+)
|
2049
|
+
elsif $6
|
1800
2050
|
return Token.new(T_PLUS, $+)
|
1801
|
-
elsif $
|
2051
|
+
elsif $8
|
1802
2052
|
# match ATOM, without a NUMBER, NIL, or PLUS prefix
|
1803
2053
|
return Token.new(T_ATOM, $+)
|
1804
|
-
elsif $8
|
1805
|
-
return Token.new(T_QUOTED, Patterns.unescape_quoted($+))
|
1806
2054
|
elsif $9
|
1807
|
-
return Token.new(
|
2055
|
+
return Token.new(T_QUOTED, Patterns.unescape_quoted($+))
|
1808
2056
|
elsif $10
|
1809
|
-
return Token.new(
|
2057
|
+
return Token.new(T_LPAR, $+)
|
1810
2058
|
elsif $11
|
1811
|
-
return Token.new(
|
2059
|
+
return Token.new(T_RPAR, $+)
|
1812
2060
|
elsif $12
|
1813
|
-
return Token.new(
|
2061
|
+
return Token.new(T_BSLASH, $+)
|
1814
2062
|
elsif $13
|
1815
|
-
return Token.new(
|
2063
|
+
return Token.new(T_STAR, $+)
|
1816
2064
|
elsif $14
|
1817
|
-
return Token.new(
|
2065
|
+
return Token.new(T_LBRA, $+)
|
1818
2066
|
elsif $15
|
2067
|
+
return Token.new(T_RBRA, $+)
|
2068
|
+
elsif $16
|
1819
2069
|
len = $+.to_i
|
1820
2070
|
val = @str[@pos, len]
|
1821
2071
|
@pos += len
|
1822
2072
|
return Token.new(T_LITERAL, val)
|
1823
|
-
elsif $16
|
1824
|
-
return Token.new(T_PERCENT, $+)
|
1825
2073
|
elsif $17
|
1826
|
-
return Token.new(
|
2074
|
+
return Token.new(T_PERCENT, $+)
|
1827
2075
|
elsif $18
|
2076
|
+
return Token.new(T_CRLF, $+)
|
2077
|
+
elsif $19
|
1828
2078
|
return Token.new(T_EOF, $+)
|
1829
2079
|
else
|
1830
2080
|
parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid")
|