net-imap 0.4.4 → 0.4.5
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/.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 +26 -207
- data/lib/net/imap/response_parser/parser_utils.rb +1 -1
- data/lib/net/imap/response_parser.rb +311 -58
- data/lib/net/imap/sequence_set.rb +67 -0
- data/lib/net/imap.rb +76 -17
- data/rakelib/benchmarks.rake +4 -11
- metadata +4 -2
@@ -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
|
@@ -266,6 +267,56 @@ module Net
|
|
266
267
|
# ; Is a valid RFC 3501 "atom".
|
267
268
|
TAGGED_EXT_LABEL = /#{TAGGED_LABEL_FCHAR}#{TAGGED_LABEL_CHAR}*/n
|
268
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
|
+
|
269
320
|
# RFC3501:
|
270
321
|
# literal = "{" number "}" CRLF *CHAR8
|
271
322
|
# ; Number represents the number of CHAR8s
|
@@ -279,6 +330,16 @@ module Net
|
|
279
330
|
# ; sent from server to the client.
|
280
331
|
LITERAL = /\{(\d+)\}\r\n/n
|
281
332
|
|
333
|
+
# RFC3516 (BINARY):
|
334
|
+
# literal8 = "~{" number "}" CRLF *OCTET
|
335
|
+
# ; <number> represents the number of OCTETs
|
336
|
+
# ; in the response string.
|
337
|
+
# RFC9051:
|
338
|
+
# literal8 = "~{" number64 "}" CRLF *OCTET
|
339
|
+
# ; <number64> represents the number of OCTETs
|
340
|
+
# ; in the response string.
|
341
|
+
LITERAL8 = /~\{(\d+)\}\r\n/n
|
342
|
+
|
282
343
|
module_function
|
283
344
|
|
284
345
|
def unescape_quoted!(quoted)
|
@@ -298,27 +359,28 @@ module Net
|
|
298
359
|
# the default, used in most places
|
299
360
|
BEG_REGEXP = /\G(?:\
|
300
361
|
(?# 1: SPACE )( )|\
|
301
|
-
(?# 2:
|
362
|
+
(?# 2: LITERAL8)#{Patterns::LITERAL8}|\
|
363
|
+
(?# 3: ATOM prefixed with a compatible subtype)\
|
302
364
|
((?:\
|
303
|
-
(?#
|
304
|
-
(?#
|
305
|
-
(?#
|
306
|
-
(?#
|
365
|
+
(?# 4: NIL )(NIL)|\
|
366
|
+
(?# 5: NUMBER )(\d+)|\
|
367
|
+
(?# 6: PLUS )(\+))\
|
368
|
+
(?# 7: ATOM remaining after prefix )(#{Patterns::ATOMISH})?\
|
307
369
|
(?# This enables greedy alternation without lookahead, in linear time.)\
|
308
370
|
)|\
|
309
371
|
(?# Also need to check for ATOM without a subtype prefix.)\
|
310
|
-
(?#
|
311
|
-
(?#
|
312
|
-
(?#
|
313
|
-
(?#
|
314
|
-
(?#
|
315
|
-
(?#
|
316
|
-
(?#
|
317
|
-
(?#
|
318
|
-
(?#
|
319
|
-
(?#
|
320
|
-
(?#
|
321
|
-
(?#
|
372
|
+
(?# 8: ATOM )(#{Patterns::ATOMISH})|\
|
373
|
+
(?# 9: QUOTED )#{Patterns::QUOTED_rev2}|\
|
374
|
+
(?# 10: LPAR )(\()|\
|
375
|
+
(?# 11: RPAR )(\))|\
|
376
|
+
(?# 12: BSLASH )(\\)|\
|
377
|
+
(?# 13: STAR )(\*)|\
|
378
|
+
(?# 14: LBRA )(\[)|\
|
379
|
+
(?# 15: RBRA )(\])|\
|
380
|
+
(?# 16: LITERAL )#{Patterns::LITERAL}|\
|
381
|
+
(?# 17: PERCENT )(%)|\
|
382
|
+
(?# 18: CRLF )(\r\n)|\
|
383
|
+
(?# 19: EOF )(\z))/ni
|
322
384
|
|
323
385
|
# envelope, body(structure), namespaces
|
324
386
|
DATA_REGEXP = /\G(?:\
|
@@ -359,6 +421,9 @@ module Net
|
|
359
421
|
# string = quoted / literal
|
360
422
|
def_token_matchers :string, T_QUOTED, T_LITERAL
|
361
423
|
|
424
|
+
# used by nstring8 = nstring / literal8
|
425
|
+
def_token_matchers :string8, T_QUOTED, T_LITERAL, T_LITERAL8
|
426
|
+
|
362
427
|
# use where string represents "LABEL" values
|
363
428
|
def_token_matchers :case_insensitive__string,
|
364
429
|
T_QUOTED, T_LITERAL,
|
@@ -390,6 +455,24 @@ module Net
|
|
390
455
|
# ATOM-CHAR = <any CHAR except atom-specials>
|
391
456
|
ATOM_TOKENS = [T_ATOM, T_NUMBER, T_NIL, T_LBRA, T_PLUS]
|
392
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
|
+
|
393
476
|
# ASTRING-CHAR = ATOM-CHAR / resp-specials
|
394
477
|
# resp-specials = "]"
|
395
478
|
ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA].freeze
|
@@ -460,6 +543,10 @@ module Net
|
|
460
543
|
NIL? ? nil : string
|
461
544
|
end
|
462
545
|
|
546
|
+
def nstring8
|
547
|
+
NIL? ? nil : string8
|
548
|
+
end
|
549
|
+
|
463
550
|
def nquoted
|
464
551
|
NIL? ? nil : quoted
|
465
552
|
end
|
@@ -469,6 +556,60 @@ module Net
|
|
469
556
|
NIL? ? nil : case_insensitive__string
|
470
557
|
end
|
471
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
|
+
|
472
613
|
# valid number ranges are not enforced by parser
|
473
614
|
# number64 = 1*DIGIT
|
474
615
|
# ; Unsigned 63-bit integer
|
@@ -494,6 +635,12 @@ module Net
|
|
494
635
|
# ; Strictly ascending
|
495
636
|
alias uniqueid nz_number
|
496
637
|
|
638
|
+
# valid number ranges are not enforced by parser
|
639
|
+
#
|
640
|
+
# a 64-bit unsigned integer and is the decimal equivalent for the ID hex
|
641
|
+
# string used in the web interface and the Gmail API.
|
642
|
+
alias x_gm_id number
|
643
|
+
|
497
644
|
# [RFC3501 & RFC9051:]
|
498
645
|
# response = *(continue-req / response-data) response-done
|
499
646
|
#
|
@@ -740,10 +887,17 @@ module Net
|
|
740
887
|
when "ENVELOPE" then envelope
|
741
888
|
when "INTERNALDATE" then date_time
|
742
889
|
when "RFC822.SIZE" then number64
|
890
|
+
when /\ABINARY\[/ni then nstring8 # BINARY, IMAP4rev2
|
891
|
+
when /\ABINARY\.SIZE\[/ni then number # BINARY, IMAP4rev2
|
743
892
|
when "RFC822" then nstring # not in rev2
|
744
893
|
when "RFC822.HEADER" then nstring # not in rev2
|
745
894
|
when "RFC822.TEXT" then nstring # not in rev2
|
746
895
|
when "MODSEQ" then parens__modseq # CONDSTORE
|
896
|
+
when "EMAILID" then parens__objectid # OBJECTID
|
897
|
+
when "THREADID" then nparens__objectid # OBJECTID
|
898
|
+
when "X-GM-MSGID" then x_gm_id # GMail
|
899
|
+
when "X-GM-THRID" then x_gm_id # GMail
|
900
|
+
when "X-GM-LABELS" then x_gm_labels # GMail
|
747
901
|
else parse_error("unknown attribute `%s' for {%d}", name, n)
|
748
902
|
end
|
749
903
|
attr[name] = val
|
@@ -762,11 +916,18 @@ module Net
|
|
762
916
|
lbra? and rbra
|
763
917
|
when "BODY"
|
764
918
|
peek_lbra? and name << section and
|
765
|
-
peek_str?("<") and name <<
|
919
|
+
peek_str?("<") and name << gt__number__lt # partial
|
920
|
+
when "BINARY", "BINARY.SIZE"
|
921
|
+
name << section_binary
|
922
|
+
# see https://www.rfc-editor.org/errata/eid7246 and the note above
|
923
|
+
peek_str?("<") and name << gt__number__lt # partial
|
766
924
|
end
|
767
925
|
name
|
768
926
|
end
|
769
927
|
|
928
|
+
# this represents the partial size for BODY or BINARY
|
929
|
+
alias gt__number__lt atom
|
930
|
+
|
770
931
|
def envelope
|
771
932
|
@lex_state = EXPR_DATA
|
772
933
|
token = lookahead
|
@@ -1070,6 +1231,13 @@ module Net
|
|
1070
1231
|
str << rbra
|
1071
1232
|
end
|
1072
1233
|
|
1234
|
+
# section-binary = "[" [section-part] "]"
|
1235
|
+
def section_binary
|
1236
|
+
str = +lbra
|
1237
|
+
str << section_part unless peek_rbra?
|
1238
|
+
str << rbra
|
1239
|
+
end
|
1240
|
+
|
1073
1241
|
# section-spec = section-msgtext / (section-part ["." section-text])
|
1074
1242
|
# section-msgtext = "HEADER" /
|
1075
1243
|
# "HEADER.FIELDS" [".NOT"] SP header-list /
|
@@ -1100,6 +1268,11 @@ module Net
|
|
1100
1268
|
str << rpar
|
1101
1269
|
end
|
1102
1270
|
|
1271
|
+
# section-part = nz-number *("." nz-number)
|
1272
|
+
# ; body part reference.
|
1273
|
+
# ; Allows for accessing nested body parts.
|
1274
|
+
alias section_part atom
|
1275
|
+
|
1103
1276
|
# RFC3501 & RFC9051:
|
1104
1277
|
# header-fld-name = astring
|
1105
1278
|
#
|
@@ -1347,31 +1520,80 @@ module Net
|
|
1347
1520
|
return rootmember
|
1348
1521
|
end
|
1349
1522
|
|
1523
|
+
# mailbox-data =/ "STATUS" SP mailbox SP "(" [status-att-list] ")"
|
1350
1524
|
def mailbox_data__status
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1525
|
+
resp_name = label("STATUS"); SP!
|
1526
|
+
mbox_name = mailbox; SP!
|
1527
|
+
lpar; attr = status_att_list; rpar
|
1528
|
+
UntaggedResponse.new(resp_name, StatusData.new(mbox_name, attr), @str)
|
1529
|
+
end
|
1530
|
+
|
1531
|
+
# RFC3501
|
1532
|
+
# status-att-list = status-att SP number *(SP status-att SP number)
|
1533
|
+
# RFC4466, RFC9051, and RFC3501 Errata
|
1534
|
+
# status-att-list = status-att-val *(SP status-att-val)
|
1535
|
+
def status_att_list
|
1536
|
+
attrs = [status_att_val]
|
1537
|
+
while SP? do attrs << status_att_val end
|
1538
|
+
attrs.to_h
|
1539
|
+
end
|
1540
|
+
|
1541
|
+
# RFC3501 Errata:
|
1542
|
+
# status-att-val = ("MESSAGES" SP number) / ("RECENT" SP number) /
|
1543
|
+
# ("UIDNEXT" SP nz-number) / ("UIDVALIDITY" SP nz-number) /
|
1544
|
+
# ("UNSEEN" SP number)
|
1545
|
+
# RFC4466:
|
1546
|
+
# status-att-val = ("MESSAGES" SP number) /
|
1547
|
+
# ("RECENT" SP number) /
|
1548
|
+
# ("UIDNEXT" SP nz-number) /
|
1549
|
+
# ("UIDVALIDITY" SP nz-number) /
|
1550
|
+
# ("UNSEEN" SP number)
|
1551
|
+
# ;; Extensions to the STATUS responses
|
1552
|
+
# ;; should extend this production.
|
1553
|
+
# ;; Extensions should use the generic
|
1554
|
+
# ;; syntax defined by tagged-ext.
|
1555
|
+
# RFC9051:
|
1556
|
+
# status-att-val = ("MESSAGES" SP number) /
|
1557
|
+
# ("UIDNEXT" SP nz-number) /
|
1558
|
+
# ("UIDVALIDITY" SP nz-number) /
|
1559
|
+
# ("UNSEEN" SP number) /
|
1560
|
+
# ("DELETED" SP number) /
|
1561
|
+
# ("SIZE" SP number64)
|
1562
|
+
# ; Extensions to the STATUS responses
|
1563
|
+
# ; should extend this production.
|
1564
|
+
# ; Extensions should use the generic
|
1565
|
+
# ; syntax defined by tagged-ext.
|
1566
|
+
# RFC7162:
|
1567
|
+
# status-att-val =/ "HIGHESTMODSEQ" SP mod-sequence-valzer
|
1568
|
+
# ;; Extends non-terminal defined in [RFC4466].
|
1569
|
+
# ;; Value 0 denotes that the mailbox doesn't
|
1570
|
+
# ;; support persistent mod-sequences
|
1571
|
+
# ;; as described in Section 3.1.2.2.
|
1572
|
+
# RFC7889:
|
1573
|
+
# status-att-val =/ "APPENDLIMIT" SP (number / nil)
|
1574
|
+
# ;; status-att-val is defined in RFC 4466
|
1575
|
+
# RFC8438:
|
1576
|
+
# status-att-val =/ "SIZE" SP number64
|
1577
|
+
# RFC8474:
|
1578
|
+
# status-att-val =/ "MAILBOXID" SP "(" objectid ")"
|
1579
|
+
# ; follows tagged-ext production from [RFC4466]
|
1580
|
+
def status_att_val
|
1581
|
+
key = tagged_ext_label
|
1582
|
+
SP!
|
1583
|
+
val =
|
1584
|
+
case key
|
1585
|
+
when "MESSAGES" then number # RFC3501, RFC9051
|
1586
|
+
when "UNSEEN" then number # RFC3501, RFC9051
|
1587
|
+
when "DELETED" then number # RFC3501, RFC9051
|
1588
|
+
when "UIDNEXT" then nz_number # RFC3501, RFC9051
|
1589
|
+
when "UIDVALIDITY" then nz_number # RFC3501, RFC9051
|
1590
|
+
when "RECENT" then number # RFC3501 (obsolete)
|
1591
|
+
when "SIZE" then number64 # RFC8483, RFC9051
|
1592
|
+
when "MAILBOXID" then parens__objectid # RFC8474
|
1593
|
+
else
|
1594
|
+
number? || ExtensionData.new(tagged_ext_val)
|
1366
1595
|
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)
|
1596
|
+
[key, val]
|
1375
1597
|
end
|
1376
1598
|
|
1377
1599
|
# The presence of "IMAP4rev1" or "IMAP4rev2" is unenforced here.
|
@@ -1573,6 +1795,9 @@ module Net
|
|
1573
1795
|
# resp-text-code =/ "HIGHESTMODSEQ" SP mod-sequence-value /
|
1574
1796
|
# "NOMODSEQ" /
|
1575
1797
|
# "MODIFIED" SP sequence-set
|
1798
|
+
#
|
1799
|
+
# RFC8474: OBJECTID
|
1800
|
+
# resp-text-code =/ "MAILBOXID" SP "(" objectid ")"
|
1576
1801
|
def resp_text_code
|
1577
1802
|
name = resp_text_code__name
|
1578
1803
|
data =
|
@@ -1592,6 +1817,7 @@ module Net
|
|
1592
1817
|
"LIMIT", "OVERQUOTA", "ALREADYEXISTS", "NONEXISTENT", "CLOSED",
|
1593
1818
|
"NOTSAVED", "UIDNOTSTICKY", "UNKNOWN-CTE", "HASCHILDREN"
|
1594
1819
|
when "NOMODSEQ" # CONDSTORE
|
1820
|
+
when "MAILBOXID" then SP!; parens__objectid # RFC8474: OBJECTID
|
1595
1821
|
else
|
1596
1822
|
SP? and text_chars_except_rbra
|
1597
1823
|
end
|
@@ -1725,6 +1951,19 @@ module Net
|
|
1725
1951
|
.split(nil).map! { _1.capitalize.to_sym }
|
1726
1952
|
end
|
1727
1953
|
|
1954
|
+
# See https://developers.google.com/gmail/imap/imap-extensions
|
1955
|
+
def x_gm_label; accept(T_BSLASH) ? atom.capitalize.to_sym : astring end
|
1956
|
+
|
1957
|
+
# See https://developers.google.com/gmail/imap/imap-extensions
|
1958
|
+
def x_gm_labels
|
1959
|
+
lpar; return [] if rpar?
|
1960
|
+
labels = []
|
1961
|
+
labels << x_gm_label
|
1962
|
+
labels << x_gm_label while SP?
|
1963
|
+
rpar
|
1964
|
+
labels
|
1965
|
+
end
|
1966
|
+
|
1728
1967
|
# See https://www.rfc-editor.org/errata/rfc3501
|
1729
1968
|
#
|
1730
1969
|
# charset = atom / quoted
|
@@ -1744,6 +1983,15 @@ module Net
|
|
1744
1983
|
|
1745
1984
|
def parens__modseq; lpar; _ = permsg_modsequence; rpar; _ end
|
1746
1985
|
|
1986
|
+
# RFC8474:
|
1987
|
+
# objectid = 1*255(ALPHA / DIGIT / "_" / "-")
|
1988
|
+
# ; characters in object identifiers are case
|
1989
|
+
# ; significant
|
1990
|
+
alias objectid atom
|
1991
|
+
|
1992
|
+
def parens__objectid; lpar; _ = objectid; rpar; _ end
|
1993
|
+
def nparens__objectid; NIL? ? nil : parens__objectid end
|
1994
|
+
|
1747
1995
|
# RFC-4315 (UIDPLUS) or RFC9051 (IMAP4rev2):
|
1748
1996
|
# uid-set = (uniqueid / uid-range) *("," uid-set)
|
1749
1997
|
# uid-range = (uniqueid ":" uniqueid)
|
@@ -1789,42 +2037,47 @@ module Net
|
|
1789
2037
|
@pos = $~.end(0)
|
1790
2038
|
if $1
|
1791
2039
|
return Token.new(T_SPACE, $+)
|
1792
|
-
elsif $2
|
2040
|
+
elsif $2
|
2041
|
+
len = $+.to_i
|
2042
|
+
val = @str[@pos, len]
|
2043
|
+
@pos += len
|
2044
|
+
return Token.new(T_LITERAL8, val)
|
2045
|
+
elsif $3 && $7
|
1793
2046
|
# greedily match ATOM, prefixed with NUMBER, NIL, or PLUS.
|
1794
|
-
return Token.new(T_ATOM, $
|
1795
|
-
elsif $3
|
1796
|
-
return Token.new(T_NIL, $+)
|
2047
|
+
return Token.new(T_ATOM, $3)
|
1797
2048
|
elsif $4
|
1798
|
-
return Token.new(
|
2049
|
+
return Token.new(T_NIL, $+)
|
1799
2050
|
elsif $5
|
2051
|
+
return Token.new(T_NUMBER, $+)
|
2052
|
+
elsif $6
|
1800
2053
|
return Token.new(T_PLUS, $+)
|
1801
|
-
elsif $
|
2054
|
+
elsif $8
|
1802
2055
|
# match ATOM, without a NUMBER, NIL, or PLUS prefix
|
1803
2056
|
return Token.new(T_ATOM, $+)
|
1804
|
-
elsif $8
|
1805
|
-
return Token.new(T_QUOTED, Patterns.unescape_quoted($+))
|
1806
2057
|
elsif $9
|
1807
|
-
return Token.new(
|
2058
|
+
return Token.new(T_QUOTED, Patterns.unescape_quoted($+))
|
1808
2059
|
elsif $10
|
1809
|
-
return Token.new(
|
2060
|
+
return Token.new(T_LPAR, $+)
|
1810
2061
|
elsif $11
|
1811
|
-
return Token.new(
|
2062
|
+
return Token.new(T_RPAR, $+)
|
1812
2063
|
elsif $12
|
1813
|
-
return Token.new(
|
2064
|
+
return Token.new(T_BSLASH, $+)
|
1814
2065
|
elsif $13
|
1815
|
-
return Token.new(
|
2066
|
+
return Token.new(T_STAR, $+)
|
1816
2067
|
elsif $14
|
1817
|
-
return Token.new(
|
2068
|
+
return Token.new(T_LBRA, $+)
|
1818
2069
|
elsif $15
|
2070
|
+
return Token.new(T_RBRA, $+)
|
2071
|
+
elsif $16
|
1819
2072
|
len = $+.to_i
|
1820
2073
|
val = @str[@pos, len]
|
1821
2074
|
@pos += len
|
1822
2075
|
return Token.new(T_LITERAL, val)
|
1823
|
-
elsif $16
|
1824
|
-
return Token.new(T_PERCENT, $+)
|
1825
2076
|
elsif $17
|
1826
|
-
return Token.new(
|
2077
|
+
return Token.new(T_PERCENT, $+)
|
1827
2078
|
elsif $18
|
2079
|
+
return Token.new(T_CRLF, $+)
|
2080
|
+
elsif $19
|
1828
2081
|
return Token.new(T_EOF, $+)
|
1829
2082
|
else
|
1830
2083
|
parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid")
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
|
6
|
+
##
|
7
|
+
# An IMAP {sequence
|
8
|
+
# set}[https://www.rfc-editor.org/rfc/rfc9051.html#section-4.1.1],
|
9
|
+
# is a set of message sequence numbers or unique identifier numbers
|
10
|
+
# ("UIDs"). It contains numbers and ranges of numbers. The numbers are all
|
11
|
+
# non-zero unsigned 32-bit integers and one special value, <tt>*</tt>, that
|
12
|
+
# represents the largest value in the mailbox.
|
13
|
+
#
|
14
|
+
# *NOTE:* This SequenceSet class is currently a placeholder for unhandled
|
15
|
+
# extension data. All it does now is validate. It will be expanded to a
|
16
|
+
# full API in a future release.
|
17
|
+
class SequenceSet
|
18
|
+
|
19
|
+
def self.[](str) new(str).freeze end
|
20
|
+
|
21
|
+
def initialize(input)
|
22
|
+
@atom = -String.try_convert(input)
|
23
|
+
validate
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the IMAP string representation. In the IMAP grammar,
|
27
|
+
# +sequence-set+ is a subset of +atom+ which is a subset of +astring+.
|
28
|
+
attr_accessor :atom
|
29
|
+
|
30
|
+
# Returns #atom. In the IMAP grammar, +atom+ is a subset of +astring+.
|
31
|
+
alias astring atom
|
32
|
+
|
33
|
+
# Returns the value of #atom
|
34
|
+
alias to_s atom
|
35
|
+
|
36
|
+
# Hash equality requires the same encoded #atom representation.
|
37
|
+
#
|
38
|
+
# Net::IMAP::SequenceSet["1:3"] .eql? Net::IMAP::SequenceSet["1:3"] # => true
|
39
|
+
# Net::IMAP::SequenceSet["1,2,3"].eql? Net::IMAP::SequenceSet["1:3"] # => false
|
40
|
+
# Net::IMAP::SequenceSet["1,3"] .eql? Net::IMAP::SequenceSet["3,1"] # => false
|
41
|
+
# Net::IMAP::SequenceSet["9,1:*"].eql? Net::IMAP::SequenceSet["1:*"] # => false
|
42
|
+
#
|
43
|
+
def eql?(other) self.class == other.class && atom == other.atom end
|
44
|
+
alias == eql?
|
45
|
+
|
46
|
+
# See #eql?
|
47
|
+
def hash; [self.class. atom].hash end
|
48
|
+
|
49
|
+
def inspect
|
50
|
+
(frozen? ? "%s[%p]" : "#<%s %p>") % [self.class, to_s]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Unstable API, for internal use only (Net::IMAP#validate_data)
|
54
|
+
def validate # :nodoc:
|
55
|
+
ResponseParser::Patterns::SEQUENCE_SET_STR.match?(@atom) or
|
56
|
+
raise ArgumentError, "invalid sequence-set: %p" % [input]
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Unstable API, for internal use only (Net::IMAP#send_data)
|
61
|
+
def send_data(imap, tag) # :nodoc:
|
62
|
+
imap.__send__(:put_string, atom)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|