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.

@@ -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: ATOM prefixed with a compatible subtype)\
362
+ (?# 2: LITERAL8)#{Patterns::LITERAL8}|\
363
+ (?# 3: ATOM prefixed with a compatible subtype)\
302
364
  ((?:\
303
- (?# 3: NIL )(NIL)|\
304
- (?# 4: NUMBER )(\d+)|\
305
- (?# 5: PLUS )(\+))\
306
- (?# 6: ATOM remaining after prefix )(#{Patterns::ATOMISH})?\
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
- (?# 7: ATOM )(#{Patterns::ATOMISH})|\
311
- (?# 8: QUOTED )#{Patterns::QUOTED_rev2}|\
312
- (?# 9: LPAR )(\()|\
313
- (?# 10: RPAR )(\))|\
314
- (?# 11: BSLASH )(\\)|\
315
- (?# 12: STAR )(\*)|\
316
- (?# 13: LBRA )(\[)|\
317
- (?# 14: RBRA )(\])|\
318
- (?# 15: LITERAL )#{Patterns::LITERAL}|\
319
- (?# 16: PERCENT )(%)|\
320
- (?# 17: CRLF )(\r\n)|\
321
- (?# 18: EOF )(\z))/ni
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 << atom # partial
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
- token = match(T_ATOM)
1352
- name = token.value.upcase
1353
- match(T_SPACE)
1354
- mailbox = astring
1355
- match(T_SPACE)
1356
- match(T_LPAR)
1357
- attr = {}
1358
- while true
1359
- token = lookahead
1360
- case token.symbol
1361
- when T_RPAR
1362
- shift_token
1363
- break
1364
- when T_SPACE
1365
- shift_token
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
- token = match(T_ATOM)
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 && $6
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, $2)
1795
- elsif $3
1796
- return Token.new(T_NIL, $+)
2047
+ return Token.new(T_ATOM, $3)
1797
2048
  elsif $4
1798
- return Token.new(T_NUMBER, $+)
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 $7
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(T_LPAR, $+)
2058
+ return Token.new(T_QUOTED, Patterns.unescape_quoted($+))
1808
2059
  elsif $10
1809
- return Token.new(T_RPAR, $+)
2060
+ return Token.new(T_LPAR, $+)
1810
2061
  elsif $11
1811
- return Token.new(T_BSLASH, $+)
2062
+ return Token.new(T_RPAR, $+)
1812
2063
  elsif $12
1813
- return Token.new(T_STAR, $+)
2064
+ return Token.new(T_BSLASH, $+)
1814
2065
  elsif $13
1815
- return Token.new(T_LBRA, $+)
2066
+ return Token.new(T_STAR, $+)
1816
2067
  elsif $14
1817
- return Token.new(T_RBRA, $+)
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(T_CRLF, $+)
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