net-imap 0.4.2 → 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 -0
- data/Gemfile +2 -0
- data/docs/styles.css +0 -12
- data/lib/net/imap/data_encoding.rb +14 -2
- data/lib/net/imap/errors.rb +20 -0
- data/lib/net/imap/fetch_data.rb +518 -0
- data/lib/net/imap/response_data.rb +70 -211
- data/lib/net/imap/response_parser/parser_utils.rb +15 -5
- data/lib/net/imap/response_parser.rb +922 -428
- data/lib/net/imap/sasl/authenticators.rb +2 -2
- data/lib/net/imap/sasl/xoauth2_authenticator.rb +1 -1
- data/lib/net/imap/sequence_set.rb +67 -0
- data/lib/net/imap.rb +98 -41
- data/net-imap.gemspec +3 -2
- data/rakelib/benchmarks.rake +91 -0
- metadata +5 -6
- data/benchmarks/generate_parser_benchmarks +0 -52
- data/benchmarks/parser.yml +0 -578
- data/benchmarks/stringprep.yml +0 -65
- data/benchmarks/table-regexps.yml +0 -39
@@ -54,10 +54,26 @@ 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
|
60
61
|
|
62
|
+
module ResponseConditions
|
63
|
+
OK = "OK"
|
64
|
+
NO = "NO"
|
65
|
+
BAD = "BAD"
|
66
|
+
BYE = "BYE"
|
67
|
+
PREAUTH = "PREAUTH"
|
68
|
+
|
69
|
+
RESP_COND_STATES = [OK, NO, BAD ].freeze
|
70
|
+
RESP_DATA_CONDS = [OK, NO, BAD, BYE, ].freeze
|
71
|
+
AUTH_CONDS = [OK, PREAUTH].freeze
|
72
|
+
GREETING_CONDS = [OK, BYE, PREAUTH].freeze
|
73
|
+
RESP_CONDS = [OK, NO, BAD, BYE, PREAUTH].freeze
|
74
|
+
end
|
75
|
+
include ResponseConditions
|
76
|
+
|
61
77
|
module Patterns
|
62
78
|
|
63
79
|
module CharClassSubtraction
|
@@ -170,6 +186,54 @@ module Net
|
|
170
186
|
CODE_TEXT_CHAR = TEXT_CHAR - RESP_SPECIALS
|
171
187
|
CODE_TEXT = /#{CODE_TEXT_CHAR}+/n
|
172
188
|
|
189
|
+
# flag = "\Answered" / "\Flagged" / "\Deleted" /
|
190
|
+
# "\Seen" / "\Draft" / flag-keyword / flag-extension
|
191
|
+
# ; Does not include "\Recent"
|
192
|
+
# flag-extension = "\" atom
|
193
|
+
# ; Future expansion. Client implementations
|
194
|
+
# ; MUST accept flag-extension flags. Server
|
195
|
+
# ; implementations MUST NOT generate
|
196
|
+
# ; flag-extension flags except as defined by
|
197
|
+
# ; a future Standard or Standards Track
|
198
|
+
# ; revisions of this specification.
|
199
|
+
# flag-keyword = "$MDNSent" / "$Forwarded" / "$Junk" /
|
200
|
+
# "$NotJunk" / "$Phishing" / atom
|
201
|
+
# flag-perm = flag / "\*"
|
202
|
+
#
|
203
|
+
# Not checking for max one mbx-list-sflag in the parser.
|
204
|
+
# >>>
|
205
|
+
# mbx-list-oflag = "\Noinferiors" / child-mbox-flag /
|
206
|
+
# "\Subscribed" / "\Remote" / flag-extension
|
207
|
+
# ; Other flags; multiple from this list are
|
208
|
+
# ; possible per LIST response, but each flag
|
209
|
+
# ; can only appear once per LIST response
|
210
|
+
# mbx-list-sflag = "\NonExistent" / "\Noselect" / "\Marked" /
|
211
|
+
# "\Unmarked"
|
212
|
+
# ; Selectability flags; only one per LIST response
|
213
|
+
# child-mbox-flag = "\HasChildren" / "\HasNoChildren"
|
214
|
+
# ; attributes for the CHILDREN return option, at most
|
215
|
+
# ; one possible per LIST response
|
216
|
+
FLAG = /\\?#{ATOM}/n
|
217
|
+
FLAG_EXTENSION = /\\#{ATOM}/n
|
218
|
+
FLAG_KEYWORD = ATOM
|
219
|
+
FLAG_PERM = Regexp.union(FLAG, "\\*")
|
220
|
+
MBX_FLAG = FLAG_EXTENSION
|
221
|
+
|
222
|
+
# flag-list = "(" [flag *(SP flag)] ")"
|
223
|
+
#
|
224
|
+
# part of resp-text-code:
|
225
|
+
# >>>
|
226
|
+
# "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")"
|
227
|
+
#
|
228
|
+
# parens from mailbox-list are included in the regexp:
|
229
|
+
# >>>
|
230
|
+
# mbx-list-flags = *(mbx-list-oflag SP) mbx-list-sflag
|
231
|
+
# *(SP mbx-list-oflag) /
|
232
|
+
# mbx-list-oflag *(SP mbx-list-oflag)
|
233
|
+
FLAG_LIST = /\G\((#{FLAG }(?:#{SP}#{FLAG })*|)\)/ni
|
234
|
+
FLAG_PERM_LIST = /\G\((#{FLAG_PERM}(?:#{SP}#{FLAG_PERM})*|)\)/ni
|
235
|
+
MBX_LIST_FLAGS = /\G\((#{MBX_FLAG }(?:#{SP}#{MBX_FLAG })*|)\)/ni
|
236
|
+
|
173
237
|
# RFC3501:
|
174
238
|
# QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
|
175
239
|
# "\" quoted-specials
|
@@ -195,6 +259,64 @@ module Net
|
|
195
259
|
TEXT_rev1 = /#{TEXT_CHAR}+/
|
196
260
|
TEXT_rev2 = /#{Regexp.union TEXT_CHAR, UTF8_2, UTF8_3, UTF8_4}+/
|
197
261
|
|
262
|
+
# tagged-label-fchar = ALPHA / "-" / "_" / "."
|
263
|
+
TAGGED_LABEL_FCHAR = /[a-zA-Z\-_.]/n
|
264
|
+
# tagged-label-char = tagged-label-fchar / DIGIT / ":"
|
265
|
+
TAGGED_LABEL_CHAR = /[a-zA-Z\-_.0-9:]*/n
|
266
|
+
# tagged-ext-label = tagged-label-fchar *tagged-label-char
|
267
|
+
# ; Is a valid RFC 3501 "atom".
|
268
|
+
TAGGED_EXT_LABEL = /#{TAGGED_LABEL_FCHAR}#{TAGGED_LABEL_CHAR}*/n
|
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
|
+
|
198
320
|
# RFC3501:
|
199
321
|
# literal = "{" number "}" CRLF *CHAR8
|
200
322
|
# ; Number represents the number of CHAR8s
|
@@ -208,6 +330,16 @@ module Net
|
|
208
330
|
# ; sent from server to the client.
|
209
331
|
LITERAL = /\{(\d+)\}\r\n/n
|
210
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
|
+
|
211
343
|
module_function
|
212
344
|
|
213
345
|
def unescape_quoted!(quoted)
|
@@ -227,27 +359,28 @@ module Net
|
|
227
359
|
# the default, used in most places
|
228
360
|
BEG_REGEXP = /\G(?:\
|
229
361
|
(?# 1: SPACE )( )|\
|
230
|
-
(?# 2:
|
362
|
+
(?# 2: LITERAL8)#{Patterns::LITERAL8}|\
|
363
|
+
(?# 3: ATOM prefixed with a compatible subtype)\
|
231
364
|
((?:\
|
232
|
-
(?#
|
233
|
-
(?#
|
234
|
-
(?#
|
235
|
-
(?#
|
365
|
+
(?# 4: NIL )(NIL)|\
|
366
|
+
(?# 5: NUMBER )(\d+)|\
|
367
|
+
(?# 6: PLUS )(\+))\
|
368
|
+
(?# 7: ATOM remaining after prefix )(#{Patterns::ATOMISH})?\
|
236
369
|
(?# This enables greedy alternation without lookahead, in linear time.)\
|
237
370
|
)|\
|
238
371
|
(?# Also need to check for ATOM without a subtype prefix.)\
|
239
|
-
(?#
|
240
|
-
(?#
|
241
|
-
(?#
|
242
|
-
(?#
|
243
|
-
(?#
|
244
|
-
(?#
|
245
|
-
(?#
|
246
|
-
(?#
|
247
|
-
(?#
|
248
|
-
(?#
|
249
|
-
(?#
|
250
|
-
(?#
|
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
|
251
384
|
|
252
385
|
# envelope, body(structure), namespaces
|
253
386
|
DATA_REGEXP = /\G(?:\
|
@@ -268,6 +401,8 @@ module Net
|
|
268
401
|
Token = Struct.new(:symbol, :value)
|
269
402
|
|
270
403
|
def_char_matchers :SP, " ", :T_SPACE
|
404
|
+
def_char_matchers :PLUS, "+", :T_PLUS
|
405
|
+
def_char_matchers :STAR, "*", :T_STAR
|
271
406
|
|
272
407
|
def_char_matchers :lpar, "(", :T_LPAR
|
273
408
|
def_char_matchers :rpar, ")", :T_RPAR
|
@@ -286,6 +421,9 @@ module Net
|
|
286
421
|
# string = quoted / literal
|
287
422
|
def_token_matchers :string, T_QUOTED, T_LITERAL
|
288
423
|
|
424
|
+
# used by nstring8 = nstring / literal8
|
425
|
+
def_token_matchers :string8, T_QUOTED, T_LITERAL, T_LITERAL8
|
426
|
+
|
289
427
|
# use where string represents "LABEL" values
|
290
428
|
def_token_matchers :case_insensitive__string,
|
291
429
|
T_QUOTED, T_LITERAL,
|
@@ -310,20 +448,44 @@ module Net
|
|
310
448
|
# TODO: add to lexer and only match tagged-ext-label
|
311
449
|
def_token_matchers :tagged_ext_label, T_ATOM, T_NIL, send: :upcase
|
312
450
|
|
451
|
+
def_token_matchers :CRLF, T_CRLF
|
452
|
+
def_token_matchers :EOF, T_EOF
|
453
|
+
|
313
454
|
# atom = 1*ATOM-CHAR
|
314
455
|
# ATOM-CHAR = <any CHAR except atom-specials>
|
315
456
|
ATOM_TOKENS = [T_ATOM, T_NUMBER, T_NIL, T_LBRA, T_PLUS]
|
316
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
|
+
|
317
476
|
# ASTRING-CHAR = ATOM-CHAR / resp-specials
|
318
477
|
# resp-specials = "]"
|
319
478
|
ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA].freeze
|
320
479
|
|
321
480
|
ASTRING_TOKENS = [T_QUOTED, *ASTRING_CHARS_TOKENS, T_LITERAL].freeze
|
322
481
|
|
323
|
-
#
|
324
|
-
|
325
|
-
|
326
|
-
|
482
|
+
# tag = 1*<any ASTRING-CHAR except "+">
|
483
|
+
TAG_TOKENS = (ASTRING_CHARS_TOKENS - [T_PLUS]).freeze
|
484
|
+
|
485
|
+
# TODO: handle atom, astring_chars, and tag entirely inside the lexer
|
486
|
+
def atom; combine_adjacent(*ATOM_TOKENS) end
|
487
|
+
def astring_chars; combine_adjacent(*ASTRING_CHARS_TOKENS) end
|
488
|
+
def tag; combine_adjacent(*TAG_TOKENS) end
|
327
489
|
|
328
490
|
# the #accept version of #atom
|
329
491
|
def atom?; -combine_adjacent(*ATOM_TOKENS) if lookahead?(*ATOM_TOKENS) end
|
@@ -336,11 +498,6 @@ module Net
|
|
336
498
|
-combine_adjacent(*ATOM_TOKENS).upcase if lookahead?(*ATOM_TOKENS)
|
337
499
|
end
|
338
500
|
|
339
|
-
# TODO: handle astring_chars entirely inside the lexer
|
340
|
-
def astring_chars
|
341
|
-
combine_adjacent(*ASTRING_CHARS_TOKENS)
|
342
|
-
end
|
343
|
-
|
344
501
|
# astring = 1*ASTRING-CHAR / string
|
345
502
|
def astring
|
346
503
|
lookahead?(*ASTRING_CHARS_TOKENS) ? astring_chars : string
|
@@ -357,11 +514,39 @@ module Net
|
|
357
514
|
parse_error("unexpected atom %p, expected %p instead", val, word)
|
358
515
|
end
|
359
516
|
|
517
|
+
# Use #label or #label_in to assert specific known labels
|
518
|
+
# (+tagged-ext-label+ only, not +atom+).
|
519
|
+
def label_in(*labels)
|
520
|
+
lbl = tagged_ext_label and labels.include?(lbl) and return lbl
|
521
|
+
parse_error("unexpected atom %p, expected one of %s instead",
|
522
|
+
lbl, labels.join(" or "))
|
523
|
+
end
|
524
|
+
|
525
|
+
# expects "OK" or "PREAUTH" and raises InvalidResponseError on failure
|
526
|
+
def resp_cond_auth__name
|
527
|
+
lbl = tagged_ext_label and AUTH_CONDS.include? lbl and return lbl
|
528
|
+
raise InvalidResponseError, "bad response type %p, expected %s" % [
|
529
|
+
lbl, AUTH_CONDS.join(" or ")
|
530
|
+
]
|
531
|
+
end
|
532
|
+
|
533
|
+
# expects "OK" or "NO" or "BAD" and raises InvalidResponseError on failure
|
534
|
+
def resp_cond_state__name
|
535
|
+
lbl = tagged_ext_label and RESP_COND_STATES.include? lbl and return lbl
|
536
|
+
raise InvalidResponseError, "bad response type %p, expected %s" % [
|
537
|
+
lbl, RESP_COND_STATES.join(" or ")
|
538
|
+
]
|
539
|
+
end
|
540
|
+
|
360
541
|
# nstring = string / nil
|
361
542
|
def nstring
|
362
543
|
NIL? ? nil : string
|
363
544
|
end
|
364
545
|
|
546
|
+
def nstring8
|
547
|
+
NIL? ? nil : string8
|
548
|
+
end
|
549
|
+
|
365
550
|
def nquoted
|
366
551
|
NIL? ? nil : quoted
|
367
552
|
end
|
@@ -371,6 +556,60 @@ module Net
|
|
371
556
|
NIL? ? nil : case_insensitive__string
|
372
557
|
end
|
373
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
|
+
|
374
613
|
# valid number ranges are not enforced by parser
|
375
614
|
# number64 = 1*DIGIT
|
376
615
|
# ; Unsigned 63-bit integer
|
@@ -378,156 +617,316 @@ module Net
|
|
378
617
|
alias number64 number
|
379
618
|
alias number64? number?
|
380
619
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
result = response_untagged
|
388
|
-
else
|
389
|
-
result = response_tagged
|
390
|
-
end
|
391
|
-
while lookahead.symbol == T_SPACE
|
392
|
-
# Ignore trailing space for Microsoft Exchange Server
|
393
|
-
shift_token
|
394
|
-
end
|
395
|
-
match(T_CRLF)
|
396
|
-
match(T_EOF)
|
397
|
-
return result
|
398
|
-
end
|
620
|
+
# valid number ranges are not enforced by parser
|
621
|
+
# nz-number = digit-nz *DIGIT
|
622
|
+
# ; Non-zero unsigned 32-bit integer
|
623
|
+
# ; (0 < n < 4,294,967,296)
|
624
|
+
alias nz_number number
|
625
|
+
alias nz_number? number?
|
399
626
|
|
627
|
+
# valid number ranges are not enforced by parser
|
628
|
+
# nz-number64 = digit-nz *DIGIT
|
629
|
+
# ; Unsigned 63-bit integer
|
630
|
+
# ; (0 < n <= 9,223,372,036,854,775,807)
|
631
|
+
alias nz_number64 nz_number
|
632
|
+
|
633
|
+
# valid number ranges are not enforced by parser
|
634
|
+
# uniqueid = nz-number
|
635
|
+
# ; Strictly ascending
|
636
|
+
alias uniqueid nz_number
|
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
|
+
|
644
|
+
# [RFC3501 & RFC9051:]
|
645
|
+
# response = *(continue-req / response-data) response-done
|
646
|
+
#
|
647
|
+
# For simplicity, response isn't interpreted as the combination of the
|
648
|
+
# three response types, but instead represents any individual server
|
649
|
+
# response. Our simplified interpretation is defined as:
|
650
|
+
# response = continue-req | response_data | response-tagged
|
651
|
+
#
|
652
|
+
# n.b: our "response-tagged" definition parses "greeting" too.
|
653
|
+
def response
|
654
|
+
resp = case lookahead!(T_PLUS, T_STAR, *TAG_TOKENS).symbol
|
655
|
+
when T_PLUS then continue_req
|
656
|
+
when T_STAR then response_data
|
657
|
+
else response_tagged
|
658
|
+
end
|
659
|
+
accept_spaces # QUIRKY: Ignore trailing space (MS Exchange Server?)
|
660
|
+
CRLF!
|
661
|
+
EOF!
|
662
|
+
resp
|
663
|
+
end
|
664
|
+
|
665
|
+
# RFC3501 & RFC9051:
|
666
|
+
# continue-req = "+" SP (resp-text / base64) CRLF
|
667
|
+
#
|
668
|
+
# n.b: base64 is valid resp-text. And in the spirit of RFC9051 Appx E 23
|
669
|
+
# (and to workaround existing servers), we use the following grammar:
|
670
|
+
#
|
671
|
+
# continue-req = "+" (SP (resp-text)) CRLF
|
400
672
|
def continue_req
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
673
|
+
PLUS!
|
674
|
+
ContinuationRequest.new(SP? ? resp_text : ResponseText::EMPTY, @str)
|
675
|
+
end
|
676
|
+
|
677
|
+
RE_RESPONSE_TYPE = /\G(?:\d+ )?(?<type>#{Patterns::TAGGED_EXT_LABEL})/n
|
678
|
+
|
679
|
+
# [RFC3501:]
|
680
|
+
# response-data = "*" SP (resp-cond-state / resp-cond-bye /
|
681
|
+
# mailbox-data / message-data / capability-data) CRLF
|
682
|
+
# [RFC4466:]
|
683
|
+
# response-data = "*" SP response-payload CRLF
|
684
|
+
# response-payload = resp-cond-state / resp-cond-bye /
|
685
|
+
# mailbox-data / message-data / capability-data
|
686
|
+
# RFC5161 (ENABLE capability):
|
687
|
+
# response-data =/ "*" SP enable-data CRLF
|
688
|
+
# RFC5255 (LANGUAGE capability)
|
689
|
+
# response-payload =/ language-data
|
690
|
+
# RFC5255 (I18NLEVEL=1 and I18NLEVEL=2 capabilities)
|
691
|
+
# response-payload =/ comparator-data
|
692
|
+
# [RFC9051:]
|
693
|
+
# response-data = "*" SP (resp-cond-state / resp-cond-bye /
|
694
|
+
# mailbox-data / message-data / capability-data /
|
695
|
+
# enable-data) CRLF
|
696
|
+
#
|
697
|
+
# [merging in greeting and response-fatal:]
|
698
|
+
# greeting = "*" SP (resp-cond-auth / resp-cond-bye) CRLF
|
699
|
+
# response-fatal = "*" SP resp-cond-bye CRLF
|
700
|
+
# response-data =/ "*" SP (resp-cond-auth / resp-cond-bye) CRLF
|
701
|
+
# [removing duplicates, this is simply]
|
702
|
+
# response-payload =/ resp-cond-auth
|
703
|
+
#
|
704
|
+
# TODO: remove resp-cond-auth and handle greeting separately
|
705
|
+
def response_data
|
706
|
+
STAR!; SP!
|
707
|
+
m = peek_re(RE_RESPONSE_TYPE) or parse_error("unparsable response")
|
708
|
+
case m["type"].upcase
|
709
|
+
when "OK" then resp_cond_state__untagged # RFC3501, RFC9051
|
710
|
+
when "FETCH" then message_data__fetch # RFC3501, RFC9051
|
711
|
+
when "EXPUNGE" then message_data__expunge # RFC3501, RFC9051
|
712
|
+
when "EXISTS" then mailbox_data__exists # RFC3501, RFC9051
|
713
|
+
when "ESEARCH" then esearch_response # RFC4731, RFC9051, etc
|
714
|
+
when "VANISHED" then expunged_resp # RFC7162
|
715
|
+
when "UIDFETCH" then uidfetch_resp # (draft) UIDONLY
|
716
|
+
when "SEARCH" then mailbox_data__search # RFC3501 (obsolete)
|
717
|
+
when "CAPABILITY" then capability_data__untagged # RFC3501, RFC9051
|
718
|
+
when "FLAGS" then mailbox_data__flags # RFC3501, RFC9051
|
719
|
+
when "LIST" then mailbox_data__list # RFC3501, RFC9051
|
720
|
+
when "STATUS" then mailbox_data__status # RFC3501, RFC9051
|
721
|
+
when "NAMESPACE" then namespace_response # RFC2342, RFC9051
|
722
|
+
when "ENABLED" then enable_data # RFC5161, RFC9051
|
723
|
+
when "BAD" then resp_cond_state__untagged # RFC3501, RFC9051
|
724
|
+
when "NO" then resp_cond_state__untagged # RFC3501, RFC9051
|
725
|
+
when "PREAUTH" then resp_cond_auth # RFC3501, RFC9051
|
726
|
+
when "BYE" then resp_cond_bye # RFC3501, RFC9051
|
727
|
+
when "RECENT" then mailbox_data__recent # RFC3501 (obsolete)
|
728
|
+
when "SORT" then sort_data # RFC5256, RFC7162
|
729
|
+
when "THREAD" then thread_data # RFC5256
|
730
|
+
when "QUOTA" then quota_response # RFC2087, RFC9208
|
731
|
+
when "QUOTAROOT" then quotaroot_response # RFC2087, RFC9208
|
732
|
+
when "ID" then id_response # RFC2971
|
733
|
+
when "ACL" then acl_data # RFC4314
|
734
|
+
when "LISTRIGHTS" then listrights_data # RFC4314
|
735
|
+
when "MYRIGHTS" then myrights_data # RFC4314
|
736
|
+
when "METADATA" then metadata_resp # RFC5464
|
737
|
+
when "LANGUAGE" then language_data # RFC5255
|
738
|
+
when "COMPARATOR" then comparator_data # RFC5255
|
739
|
+
when "CONVERTED" then message_data__converted # RFC5259
|
740
|
+
when "LSUB" then mailbox_data__lsub # RFC3501 (obsolete)
|
741
|
+
when "XLIST" then mailbox_data__xlist # deprecated
|
742
|
+
when "NOOP" then response_data__noop
|
743
|
+
else response_data__unhandled
|
408
744
|
end
|
409
745
|
end
|
410
746
|
|
411
|
-
def
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
when /\A(?:OK|NO|BAD|BYE|PREAUTH)\z/ni
|
420
|
-
return response_cond
|
421
|
-
when /\A(?:FLAGS)\z/ni
|
422
|
-
return flags_response
|
423
|
-
when /\A(?:ID)\z/ni
|
424
|
-
return id_response
|
425
|
-
when /\A(?:LIST|LSUB|XLIST)\z/ni
|
426
|
-
return list_response
|
427
|
-
when /\A(?:NAMESPACE)\z/ni
|
428
|
-
return namespace_response
|
429
|
-
when /\A(?:QUOTA)\z/ni
|
430
|
-
return getquota_response
|
431
|
-
when /\A(?:QUOTAROOT)\z/ni
|
432
|
-
return getquotaroot_response
|
433
|
-
when /\A(?:ACL)\z/ni
|
434
|
-
return getacl_response
|
435
|
-
when /\A(?:SEARCH|SORT)\z/ni
|
436
|
-
return search_response
|
437
|
-
when /\A(?:THREAD)\z/ni
|
438
|
-
return thread_response
|
439
|
-
when /\A(?:STATUS)\z/ni
|
440
|
-
return status_response
|
441
|
-
when /\A(?:CAPABILITY)\z/ni
|
442
|
-
return capability_data__untagged
|
443
|
-
when /\A(?:NOOP)\z/ni
|
444
|
-
return ignored_response
|
445
|
-
when /\A(?:ENABLED)\z/ni
|
446
|
-
return enable_data
|
447
|
-
else
|
448
|
-
return text_response
|
747
|
+
def response_data__unhandled(klass = UntaggedResponse)
|
748
|
+
num = number?; SP?
|
749
|
+
type = tagged_ext_label; SP?
|
750
|
+
text = remaining_unparsed
|
751
|
+
data =
|
752
|
+
if num && text then UnparsedNumericResponseData.new(num, text)
|
753
|
+
elsif text then UnparsedData.new(text)
|
754
|
+
else num
|
449
755
|
end
|
450
|
-
|
451
|
-
parse_error("unexpected token %s", token.symbol)
|
452
|
-
end
|
756
|
+
klass.new(type, data, @str)
|
453
757
|
end
|
454
758
|
|
759
|
+
# reads all the way up until CRLF
|
760
|
+
def remaining_unparsed
|
761
|
+
str = @str[@pos...-2] and @pos += str.bytesize
|
762
|
+
str&.empty? ? nil : str
|
763
|
+
end
|
764
|
+
|
765
|
+
def response_data__ignored; response_data__unhandled(IgnoredResponse) end
|
766
|
+
alias response_data__noop response_data__ignored
|
767
|
+
|
768
|
+
alias esearch_response response_data__unhandled
|
769
|
+
alias expunged_resp response_data__unhandled
|
770
|
+
alias uidfetch_resp response_data__unhandled
|
771
|
+
alias listrights_data response_data__unhandled
|
772
|
+
alias myrights_data response_data__unhandled
|
773
|
+
alias metadata_resp response_data__unhandled
|
774
|
+
alias language_data response_data__unhandled
|
775
|
+
alias comparator_data response_data__unhandled
|
776
|
+
alias message_data__converted response_data__unhandled
|
777
|
+
|
778
|
+
# RFC3501 & RFC9051:
|
779
|
+
# response-tagged = tag SP resp-cond-state CRLF
|
780
|
+
#
|
781
|
+
# resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
|
782
|
+
# ; Status condition
|
783
|
+
#
|
784
|
+
# tag = 1*<any ASTRING-CHAR except "+">
|
455
785
|
def response_tagged
|
456
|
-
tag
|
457
|
-
|
458
|
-
|
459
|
-
name = token.value.upcase
|
460
|
-
match(T_SPACE)
|
461
|
-
return TaggedResponse.new(tag, name, resp_text, @str)
|
786
|
+
tag = tag(); SP!
|
787
|
+
name = resp_cond_state__name; SP!
|
788
|
+
TaggedResponse.new(tag, name, resp_text, @str)
|
462
789
|
end
|
463
790
|
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
791
|
+
# RFC3501 & RFC9051:
|
792
|
+
# resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
|
793
|
+
def resp_cond_state__untagged
|
794
|
+
name = resp_cond_state__name; SP!
|
795
|
+
UntaggedResponse.new(name, resp_text, @str)
|
469
796
|
end
|
470
797
|
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
798
|
+
# resp-cond-auth = ("OK" / "PREAUTH") SP resp-text
|
799
|
+
def resp_cond_auth
|
800
|
+
name = resp_cond_auth__name; SP!
|
801
|
+
UntaggedResponse.new(name, resp_text, @str)
|
802
|
+
end
|
803
|
+
|
804
|
+
# resp-cond-bye = "BYE" SP resp-text
|
805
|
+
def resp_cond_bye
|
806
|
+
name = label(BYE); SP!
|
807
|
+
UntaggedResponse.new(name, resp_text, @str)
|
808
|
+
end
|
809
|
+
|
810
|
+
# message-data = nz-number SP ("EXPUNGE" / ("FETCH" SP msg-att))
|
811
|
+
def message_data__fetch
|
812
|
+
seq = nz_number; SP!
|
813
|
+
name = label "FETCH"; SP!
|
814
|
+
data = FetchData.new(seq, msg_att(seq))
|
815
|
+
UntaggedResponse.new(name, data, @str)
|
816
|
+
end
|
817
|
+
|
818
|
+
def response_data__simple_numeric
|
819
|
+
data = nz_number; SP!
|
820
|
+
name = tagged_ext_label
|
821
|
+
UntaggedResponse.new(name, data, @str)
|
485
822
|
end
|
486
823
|
|
824
|
+
alias message_data__expunge response_data__simple_numeric
|
825
|
+
alias mailbox_data__exists response_data__simple_numeric
|
826
|
+
alias mailbox_data__recent response_data__simple_numeric
|
827
|
+
|
828
|
+
# RFC3501 & RFC9051:
|
829
|
+
# msg-att = "(" (msg-att-dynamic / msg-att-static)
|
830
|
+
# *(SP (msg-att-dynamic / msg-att-static)) ")"
|
831
|
+
#
|
832
|
+
# msg-att-dynamic = "FLAGS" SP "(" [flag-fetch *(SP flag-fetch)] ")"
|
833
|
+
# RFC5257 (ANNOTATE extension):
|
834
|
+
# msg-att-dynamic =/ "ANNOTATION" SP
|
835
|
+
# ( "(" entry-att *(SP entry-att) ")" /
|
836
|
+
# "(" entry *(SP entry) ")" )
|
837
|
+
# RFC7162 (CONDSTORE extension):
|
838
|
+
# msg-att-dynamic =/ fetch-mod-resp
|
839
|
+
# fetch-mod-resp = "MODSEQ" SP "(" permsg-modsequence ")"
|
840
|
+
# RFC8970 (PREVIEW extension):
|
841
|
+
# msg-att-dynamic =/ "PREVIEW" SP nstring
|
842
|
+
#
|
843
|
+
# RFC3501:
|
844
|
+
# msg-att-static = "ENVELOPE" SP envelope /
|
845
|
+
# "INTERNALDATE" SP date-time /
|
846
|
+
# "RFC822" [".HEADER" / ".TEXT"] SP nstring /
|
847
|
+
# "RFC822.SIZE" SP number /
|
848
|
+
# "BODY" ["STRUCTURE"] SP body /
|
849
|
+
# "BODY" section ["<" number ">"] SP nstring /
|
850
|
+
# "UID" SP uniqueid
|
851
|
+
# RFC3516 (BINARY extension):
|
852
|
+
# msg-att-static =/ "BINARY" section-binary SP (nstring / literal8)
|
853
|
+
# / "BINARY.SIZE" section-binary SP number
|
854
|
+
# RFC8514 (SAVEDATE extension):
|
855
|
+
# msg-att-static =/ "SAVEDATE" SP (date-time / nil)
|
856
|
+
# RFC8474 (OBJECTID extension):
|
857
|
+
# msg-att-static =/ fetch-emailid-resp / fetch-threadid-resp
|
858
|
+
# fetch-emailid-resp = "EMAILID" SP "(" objectid ")"
|
859
|
+
# fetch-threadid-resp = "THREADID" SP ( "(" objectid ")" / nil )
|
860
|
+
# RFC9051:
|
861
|
+
# msg-att-static = "ENVELOPE" SP envelope /
|
862
|
+
# "INTERNALDATE" SP date-time /
|
863
|
+
# "RFC822.SIZE" SP number64 /
|
864
|
+
# "BODY" ["STRUCTURE"] SP body /
|
865
|
+
# "BODY" section ["<" number ">"] SP nstring /
|
866
|
+
# "BINARY" section-binary SP (nstring / literal8) /
|
867
|
+
# "BINARY.SIZE" section-binary SP number /
|
868
|
+
# "UID" SP uniqueid
|
869
|
+
#
|
870
|
+
# Re https://www.rfc-editor.org/errata/eid7246, I'm adding "offset" to the
|
871
|
+
# official "BINARY" ABNF, like so:
|
872
|
+
#
|
873
|
+
# msg-att-static =/ "BINARY" section-binary ["<" number ">"] SP
|
874
|
+
# (nstring / literal8)
|
487
875
|
def msg_att(n)
|
488
|
-
|
876
|
+
lpar
|
489
877
|
attr = {}
|
490
878
|
while true
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
when /\A(?:MODSEQ)\z/ni
|
516
|
-
name, val = modseq_data
|
517
|
-
else
|
518
|
-
parse_error("unknown attribute `%s' for {%d}", token.value, n)
|
519
|
-
end
|
879
|
+
name = msg_att__label; SP!
|
880
|
+
val =
|
881
|
+
case name
|
882
|
+
when "UID" then uniqueid
|
883
|
+
when "FLAGS" then flag_list
|
884
|
+
when "BODY" then body
|
885
|
+
when /\ABODY\[/ni then nstring
|
886
|
+
when "BODYSTRUCTURE" then body
|
887
|
+
when "ENVELOPE" then envelope
|
888
|
+
when "INTERNALDATE" then date_time
|
889
|
+
when "RFC822.SIZE" then number64
|
890
|
+
when /\ABINARY\[/ni then nstring8 # BINARY, IMAP4rev2
|
891
|
+
when /\ABINARY\.SIZE\[/ni then number # BINARY, IMAP4rev2
|
892
|
+
when "RFC822" then nstring # not in rev2
|
893
|
+
when "RFC822.HEADER" then nstring # not in rev2
|
894
|
+
when "RFC822.TEXT" then nstring # not in rev2
|
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
|
901
|
+
else parse_error("unknown attribute `%s' for {%d}", name, n)
|
902
|
+
end
|
520
903
|
attr[name] = val
|
904
|
+
break unless SP?
|
905
|
+
break if lookahead_rpar?
|
906
|
+
end
|
907
|
+
rpar
|
908
|
+
attr
|
909
|
+
end
|
910
|
+
|
911
|
+
# appends "[section]" and "<partial>" to the base label
|
912
|
+
def msg_att__label
|
913
|
+
case (name = tagged_ext_label)
|
914
|
+
when /\A(?:RFC822(?:\.HEADER|\.TEXT)?)\z/ni
|
915
|
+
# ignoring "[]" fixes https://bugs.ruby-lang.org/issues/5620
|
916
|
+
lbra? and rbra
|
917
|
+
when "BODY"
|
918
|
+
peek_lbra? and name << section and
|
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
|
521
924
|
end
|
522
|
-
|
925
|
+
name
|
523
926
|
end
|
524
927
|
|
525
|
-
|
526
|
-
|
527
|
-
name = token.value.upcase
|
528
|
-
match(T_SPACE)
|
529
|
-
return name, envelope
|
530
|
-
end
|
928
|
+
# this represents the partial size for BODY or BINARY
|
929
|
+
alias gt__number__lt atom
|
531
930
|
|
532
931
|
def envelope
|
533
932
|
@lex_state = EXPR_DATA
|
@@ -564,58 +963,10 @@ module Net
|
|
564
963
|
return result
|
565
964
|
end
|
566
965
|
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
return name, flag_list
|
572
|
-
end
|
573
|
-
|
574
|
-
def internaldate_data
|
575
|
-
token = match(T_ATOM)
|
576
|
-
name = token.value.upcase
|
577
|
-
match(T_SPACE)
|
578
|
-
token = match(T_QUOTED)
|
579
|
-
return name, token.value
|
580
|
-
end
|
581
|
-
|
582
|
-
def rfc822_text
|
583
|
-
token = match(T_ATOM)
|
584
|
-
name = token.value.upcase
|
585
|
-
token = lookahead
|
586
|
-
if token.symbol == T_LBRA
|
587
|
-
shift_token
|
588
|
-
match(T_RBRA)
|
589
|
-
end
|
590
|
-
match(T_SPACE)
|
591
|
-
return name, nstring
|
592
|
-
end
|
593
|
-
|
594
|
-
def rfc822_size
|
595
|
-
token = match(T_ATOM)
|
596
|
-
name = token.value.upcase
|
597
|
-
match(T_SPACE)
|
598
|
-
return name, number
|
599
|
-
end
|
600
|
-
|
601
|
-
def body_data
|
602
|
-
token = match(T_ATOM)
|
603
|
-
name = token.value.upcase
|
604
|
-
token = lookahead
|
605
|
-
if token.symbol == T_SPACE
|
606
|
-
shift_token
|
607
|
-
return name, body
|
608
|
-
end
|
609
|
-
name.concat(section)
|
610
|
-
token = lookahead
|
611
|
-
if token.symbol == T_ATOM
|
612
|
-
name.concat(token.value)
|
613
|
-
shift_token
|
614
|
-
end
|
615
|
-
match(T_SPACE)
|
616
|
-
data = nstring
|
617
|
-
return name, data
|
618
|
-
end
|
966
|
+
# date-time = DQUOTE date-day-fixed "-" date-month "-" date-year
|
967
|
+
# SP time SP zone DQUOTE
|
968
|
+
alias date_time quoted
|
969
|
+
alias ndatetime nquoted
|
619
970
|
|
620
971
|
# RFC-3501 & RFC-9051:
|
621
972
|
# body = "(" (body-type-1part / body-type-mpart) ")"
|
@@ -844,6 +1195,7 @@ module Net
|
|
844
1195
|
if lpar?
|
845
1196
|
result = [case_insensitive__string]
|
846
1197
|
result << case_insensitive__string while SP?
|
1198
|
+
rpar
|
847
1199
|
result
|
848
1200
|
else
|
849
1201
|
case_insensitive__nstring
|
@@ -872,101 +1224,102 @@ module Net
|
|
872
1224
|
end
|
873
1225
|
end
|
874
1226
|
|
1227
|
+
# section = "[" [section-spec] "]"
|
875
1228
|
def section
|
876
|
-
str =
|
877
|
-
|
878
|
-
str
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
str
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
str
|
907
|
-
|
908
|
-
end
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
def
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
name
|
967
|
-
|
968
|
-
|
969
|
-
|
1229
|
+
str = +lbra
|
1230
|
+
str << section_spec unless peek_rbra?
|
1231
|
+
str << rbra
|
1232
|
+
end
|
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
|
+
|
1241
|
+
# section-spec = section-msgtext / (section-part ["." section-text])
|
1242
|
+
# section-msgtext = "HEADER" /
|
1243
|
+
# "HEADER.FIELDS" [".NOT"] SP header-list /
|
1244
|
+
# "TEXT"
|
1245
|
+
# ; top-level or MESSAGE/RFC822 or
|
1246
|
+
# ; MESSAGE/GLOBAL part
|
1247
|
+
# section-part = nz-number *("." nz-number)
|
1248
|
+
# ; body part reference.
|
1249
|
+
# ; Allows for accessing nested body parts.
|
1250
|
+
# section-text = section-msgtext / "MIME"
|
1251
|
+
# ; text other than actual body part (headers,
|
1252
|
+
# ; etc.)
|
1253
|
+
#
|
1254
|
+
# n.b: we could "cheat" here and just grab all text inside the brackets,
|
1255
|
+
# but literals would need special treatment.
|
1256
|
+
def section_spec
|
1257
|
+
str = "".b
|
1258
|
+
str << atom # grabs everything up to "SP header-list" or "]"
|
1259
|
+
str << " " << header_list if SP?
|
1260
|
+
str
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
# header-list = "(" header-fld-name *(SP header-fld-name) ")"
|
1264
|
+
def header_list
|
1265
|
+
str = +""
|
1266
|
+
str << lpar << header_fld_name
|
1267
|
+
str << " " << header_fld_name while SP?
|
1268
|
+
str << rpar
|
1269
|
+
end
|
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
|
+
|
1276
|
+
# RFC3501 & RFC9051:
|
1277
|
+
# header-fld-name = astring
|
1278
|
+
#
|
1279
|
+
# NOTE: Previously, Net::IMAP recreated the raw original source string.
|
1280
|
+
# Now, it grabs the raw encoded value using @str and @pos. A future
|
1281
|
+
# version may simply return the decoded astring value. Although that is
|
1282
|
+
# technically incompatible, it should almost never make a difference: all
|
1283
|
+
# standard header field names are valid atoms:
|
1284
|
+
#
|
1285
|
+
# https://www.iana.org/assignments/message-headers/message-headers.xhtml
|
1286
|
+
#
|
1287
|
+
# Although RFC3501 allows any astring, RFC5322-valid header names are one
|
1288
|
+
# or more of the printable US-ASCII characters, except SP and colon. So
|
1289
|
+
# empty string isn't valid, and literals aren't needed and should not be
|
1290
|
+
# used. This is explicitly unchanged by [I18N-HDRS] (RFC6532).
|
1291
|
+
#
|
1292
|
+
# RFC5233:
|
1293
|
+
# optional-field = field-name ":" unstructured CRLF
|
1294
|
+
# field-name = 1*ftext
|
1295
|
+
# ftext = %d33-57 / ; Printable US-ASCII
|
1296
|
+
# %d59-126 ; characters not including
|
1297
|
+
# ; ":".
|
1298
|
+
def header_fld_name
|
1299
|
+
assert_no_lookahead
|
1300
|
+
start = @pos
|
1301
|
+
astring
|
1302
|
+
@str[start...@pos - 1]
|
1303
|
+
end
|
1304
|
+
|
1305
|
+
# mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list /
|
1306
|
+
# "LSUB" SP mailbox-list / "SEARCH" *(SP nz-number) /
|
1307
|
+
# "STATUS" SP mailbox SP "(" [status-att-list] ")" /
|
1308
|
+
# number SP "EXISTS" / number SP "RECENT"
|
1309
|
+
|
1310
|
+
def mailbox_data__flags
|
1311
|
+
name = label("FLAGS")
|
1312
|
+
SP!
|
1313
|
+
UntaggedResponse.new(name, flag_list, @str)
|
1314
|
+
end
|
1315
|
+
|
1316
|
+
def mailbox_data__list
|
1317
|
+
name = label_in("LIST", "LSUB", "XLIST")
|
1318
|
+
SP!
|
1319
|
+
UntaggedResponse.new(name, mailbox_list, @str)
|
1320
|
+
end
|
1321
|
+
alias mailbox_data__lsub mailbox_data__list
|
1322
|
+
alias mailbox_data__xlist mailbox_data__list
|
970
1323
|
|
971
1324
|
def mailbox_list
|
972
1325
|
attr = flag_list
|
@@ -1032,7 +1385,8 @@ module Net
|
|
1032
1385
|
return UntaggedResponse.new(name, data, @str)
|
1033
1386
|
end
|
1034
1387
|
|
1035
|
-
|
1388
|
+
# acl-data = "ACL" SP mailbox *(SP identifier SP rights)
|
1389
|
+
def acl_data
|
1036
1390
|
token = match(T_ATOM)
|
1037
1391
|
name = token.value.upcase
|
1038
1392
|
match(T_SPACE)
|
@@ -1058,7 +1412,21 @@ module Net
|
|
1058
1412
|
return UntaggedResponse.new(name, data, @str)
|
1059
1413
|
end
|
1060
1414
|
|
1061
|
-
|
1415
|
+
# RFC3501:
|
1416
|
+
# mailbox-data = "SEARCH" *(SP nz-number) / ...
|
1417
|
+
# RFC5256: SORT
|
1418
|
+
# sort-data = "SORT" *(SP nz-number)
|
1419
|
+
# RFC7162: CONDSTORE, QRESYNC
|
1420
|
+
# mailbox-data =/ "SEARCH" [1*(SP nz-number) SP
|
1421
|
+
# search-sort-mod-seq]
|
1422
|
+
# sort-data = "SORT" [1*(SP nz-number) SP
|
1423
|
+
# search-sort-mod-seq]
|
1424
|
+
# ; Updates the SORT response from RFC 5256.
|
1425
|
+
# search-sort-mod-seq = "(" "MODSEQ" SP mod-sequence-value ")"
|
1426
|
+
# RFC9051:
|
1427
|
+
# mailbox-data = obsolete-search-response / ...
|
1428
|
+
# obsolete-search-response = "SEARCH" *(SP nz-number)
|
1429
|
+
def mailbox_data__search
|
1062
1430
|
token = match(T_ATOM)
|
1063
1431
|
name = token.value.upcase
|
1064
1432
|
token = lookahead
|
@@ -1088,8 +1456,9 @@ module Net
|
|
1088
1456
|
end
|
1089
1457
|
return UntaggedResponse.new(name, data, @str)
|
1090
1458
|
end
|
1459
|
+
alias sort_data mailbox_data__search
|
1091
1460
|
|
1092
|
-
def
|
1461
|
+
def thread_data
|
1093
1462
|
token = match(T_ATOM)
|
1094
1463
|
name = token.value.upcase
|
1095
1464
|
token = lookahead
|
@@ -1151,31 +1520,80 @@ module Net
|
|
1151
1520
|
return rootmember
|
1152
1521
|
end
|
1153
1522
|
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1523
|
+
# mailbox-data =/ "STATUS" SP mailbox SP "(" [status-att-list] ")"
|
1524
|
+
def mailbox_data__status
|
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)
|
1170
1595
|
end
|
1171
|
-
|
1172
|
-
key = token.value.upcase
|
1173
|
-
match(T_SPACE)
|
1174
|
-
val = number
|
1175
|
-
attr[key] = val
|
1176
|
-
end
|
1177
|
-
data = StatusData.new(mailbox, attr)
|
1178
|
-
return UntaggedResponse.new(name, data, @str)
|
1596
|
+
[key, val]
|
1179
1597
|
end
|
1180
1598
|
|
1181
1599
|
# The presence of "IMAP4rev1" or "IMAP4rev2" is unenforced here.
|
@@ -1198,11 +1616,13 @@ module Net
|
|
1198
1616
|
end
|
1199
1617
|
|
1200
1618
|
# As a workaround for buggy servers, allow a trailing SP:
|
1201
|
-
# *(SP
|
1619
|
+
# *(SP capability) [SP]
|
1202
1620
|
def capability__list
|
1203
|
-
|
1621
|
+
list = []; while SP? && (capa = capability?) do list << capa end; list
|
1204
1622
|
end
|
1205
1623
|
|
1624
|
+
alias resp_code__capability capability__list
|
1625
|
+
|
1206
1626
|
# capability = ("AUTH=" auth-type) / atom
|
1207
1627
|
# ; New capabilities MUST begin with "X" or be
|
1208
1628
|
# ; registered with IANA as standard or
|
@@ -1325,68 +1745,95 @@ module Net
|
|
1325
1745
|
end
|
1326
1746
|
end
|
1327
1747
|
|
1328
|
-
# See https://www.rfc-editor.org/errata/rfc3501
|
1748
|
+
# RFC3501 (See https://www.rfc-editor.org/errata/rfc3501):
|
1749
|
+
# resp-text-code = "ALERT" /
|
1750
|
+
# "BADCHARSET" [SP "(" charset *(SP charset) ")" ] /
|
1751
|
+
# capability-data / "PARSE" /
|
1752
|
+
# "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" /
|
1753
|
+
# "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
|
1754
|
+
# "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
|
1755
|
+
# "UNSEEN" SP nz-number /
|
1756
|
+
# atom [SP 1*<any TEXT-CHAR except "]">]
|
1757
|
+
# capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev1"
|
1758
|
+
# *(SP capability)
|
1329
1759
|
#
|
1330
|
-
#
|
1331
|
-
#
|
1332
|
-
#
|
1333
|
-
#
|
1334
|
-
#
|
1335
|
-
#
|
1336
|
-
#
|
1337
|
-
#
|
1338
|
-
#
|
1760
|
+
# RFC5530:
|
1761
|
+
# resp-text-code =/ "UNAVAILABLE" / "AUTHENTICATIONFAILED" /
|
1762
|
+
# "AUTHORIZATIONFAILED" / "EXPIRED" /
|
1763
|
+
# "PRIVACYREQUIRED" / "CONTACTADMIN" / "NOPERM" /
|
1764
|
+
# "INUSE" / "EXPUNGEISSUED" / "CORRUPTION" /
|
1765
|
+
# "SERVERBUG" / "CLIENTBUG" / "CANNOT" /
|
1766
|
+
# "LIMIT" / "OVERQUOTA" / "ALREADYEXISTS" /
|
1767
|
+
# "NONEXISTENT"
|
1768
|
+
# RFC9051:
|
1769
|
+
# resp-text-code = "ALERT" /
|
1770
|
+
# "BADCHARSET" [SP "(" charset *(SP charset) ")" ] /
|
1771
|
+
# capability-data / "PARSE" /
|
1772
|
+
# "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" /
|
1773
|
+
# "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
|
1774
|
+
# "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
|
1775
|
+
# resp-code-apnd / resp-code-copy / "UIDNOTSTICKY" /
|
1776
|
+
# "UNAVAILABLE" / "AUTHENTICATIONFAILED" /
|
1777
|
+
# "AUTHORIZATIONFAILED" / "EXPIRED" /
|
1778
|
+
# "PRIVACYREQUIRED" / "CONTACTADMIN" / "NOPERM" /
|
1779
|
+
# "INUSE" / "EXPUNGEISSUED" / "CORRUPTION" /
|
1780
|
+
# "SERVERBUG" / "CLIENTBUG" / "CANNOT" /
|
1781
|
+
# "LIMIT" / "OVERQUOTA" / "ALREADYEXISTS" /
|
1782
|
+
# "NONEXISTENT" / "NOTSAVED" / "HASCHILDREN" /
|
1783
|
+
# "CLOSED" /
|
1784
|
+
# "UNKNOWN-CTE" /
|
1785
|
+
# atom [SP 1*<any TEXT-CHAR except "]">]
|
1786
|
+
# capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev2"
|
1787
|
+
# *(SP capability)
|
1339
1788
|
#
|
1340
|
-
#
|
1341
|
-
# resp-
|
1789
|
+
# RFC4315 (UIDPLUS), RFC9051 (IMAP4rev2):
|
1790
|
+
# resp-code-apnd = "APPENDUID" SP nz-number SP append-uid
|
1791
|
+
# resp-code-copy = "COPYUID" SP nz-number SP uid-set SP uid-set
|
1792
|
+
# resp-text-code =/ resp-code-apnd / resp-code-copy / "UIDNOTSTICKY"
|
1793
|
+
#
|
1794
|
+
# RFC7162 (CONDSTORE):
|
1795
|
+
# resp-text-code =/ "HIGHESTMODSEQ" SP mod-sequence-value /
|
1796
|
+
# "NOMODSEQ" /
|
1797
|
+
# "MODIFIED" SP sequence-set
|
1798
|
+
#
|
1799
|
+
# RFC8474: OBJECTID
|
1800
|
+
# resp-text-code =/ "MAILBOXID" SP "(" objectid ")"
|
1342
1801
|
def resp_text_code
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
else
|
1363
|
-
token = lookahead
|
1364
|
-
if token.symbol == T_SPACE
|
1365
|
-
shift_token
|
1366
|
-
result = ResponseCode.new(name, text_chars_except_rbra)
|
1802
|
+
name = resp_text_code__name
|
1803
|
+
data =
|
1804
|
+
case name
|
1805
|
+
when "CAPABILITY" then resp_code__capability
|
1806
|
+
when "PERMANENTFLAGS" then SP? ? flag_perm__list : []
|
1807
|
+
when "UIDNEXT" then SP!; nz_number
|
1808
|
+
when "UIDVALIDITY" then SP!; nz_number
|
1809
|
+
when "UNSEEN" then SP!; nz_number # rev1 only
|
1810
|
+
when "APPENDUID" then SP!; resp_code_apnd__data # rev2, UIDPLUS
|
1811
|
+
when "COPYUID" then SP!; resp_code_copy__data # rev2, UIDPLUS
|
1812
|
+
when "BADCHARSET" then SP? ? charset__list : []
|
1813
|
+
when "ALERT", "PARSE", "READ-ONLY", "READ-WRITE", "TRYCREATE",
|
1814
|
+
"UNAVAILABLE", "AUTHENTICATIONFAILED", "AUTHORIZATIONFAILED",
|
1815
|
+
"EXPIRED", "PRIVACYREQUIRED", "CONTACTADMIN", "NOPERM", "INUSE",
|
1816
|
+
"EXPUNGEISSUED", "CORRUPTION", "SERVERBUG", "CLIENTBUG", "CANNOT",
|
1817
|
+
"LIMIT", "OVERQUOTA", "ALREADYEXISTS", "NONEXISTENT", "CLOSED",
|
1818
|
+
"NOTSAVED", "UIDNOTSTICKY", "UNKNOWN-CTE", "HASCHILDREN"
|
1819
|
+
when "NOMODSEQ" # CONDSTORE
|
1820
|
+
when "MAILBOXID" then SP!; parens__objectid # RFC8474: OBJECTID
|
1367
1821
|
else
|
1368
|
-
|
1822
|
+
SP? and text_chars_except_rbra
|
1369
1823
|
end
|
1370
|
-
|
1371
|
-
return result
|
1824
|
+
ResponseCode.new(name, data)
|
1372
1825
|
end
|
1373
1826
|
|
1827
|
+
alias resp_text_code__name case_insensitive__atom
|
1828
|
+
|
1374
1829
|
# 1*<any TEXT-CHAR except "]">
|
1375
1830
|
def text_chars_except_rbra
|
1376
1831
|
match_re(CTEXT_REGEXP, '1*<any TEXT-CHAR except "]">')[0]
|
1377
1832
|
end
|
1378
1833
|
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
match(T_LPAR)
|
1383
|
-
result << charset
|
1384
|
-
while accept(T_SPACE)
|
1385
|
-
result << charset
|
1386
|
-
end
|
1387
|
-
match(T_RPAR)
|
1388
|
-
end
|
1389
|
-
result
|
1834
|
+
# "(" charset *(SP charset) ")"
|
1835
|
+
def charset__list
|
1836
|
+
lpar; list = [charset]; while SP? do list << charset end; rpar; list
|
1390
1837
|
end
|
1391
1838
|
|
1392
1839
|
# already matched: "APPENDUID"
|
@@ -1402,8 +1849,8 @@ module Net
|
|
1402
1849
|
# match uid_set even if that returns a single-member array.
|
1403
1850
|
#
|
1404
1851
|
def resp_code_apnd__data
|
1405
|
-
|
1406
|
-
|
1852
|
+
validity = number; SP!
|
1853
|
+
dst_uids = uid_set # uniqueid ⊂ uid-set
|
1407
1854
|
UIDPlusData.new(validity, nil, dst_uids)
|
1408
1855
|
end
|
1409
1856
|
|
@@ -1411,9 +1858,9 @@ module Net
|
|
1411
1858
|
#
|
1412
1859
|
# resp-code-copy = "COPYUID" SP nz-number SP uid-set SP uid-set
|
1413
1860
|
def resp_code_copy__data
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1861
|
+
validity = number; SP!
|
1862
|
+
src_uids = uid_set; SP!
|
1863
|
+
dst_uids = uid_set
|
1417
1864
|
UIDPlusData.new(validity, src_uids, dst_uids)
|
1418
1865
|
end
|
1419
1866
|
|
@@ -1472,36 +1919,78 @@ module Net
|
|
1472
1919
|
return Address.new(name, route, mailbox, host)
|
1473
1920
|
end
|
1474
1921
|
|
1475
|
-
|
1476
|
-
(?# FLAG )\\([^\x80-\xff(){ \x00-\x1f\x7f%"\\]+)|\
|
1477
|
-
(?# ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\]+)/n
|
1478
|
-
|
1922
|
+
# flag-list = "(" [flag *(SP flag)] ")"
|
1479
1923
|
def flag_list
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1924
|
+
match_re(Patterns::FLAG_LIST, "flag-list")[1]
|
1925
|
+
.split(nil)
|
1926
|
+
.map! { _1.start_with?("\\") ? _1[1..].capitalize.to_sym : _1 }
|
1927
|
+
end
|
1928
|
+
|
1929
|
+
# "(" [flag-perm *(SP flag-perm)] ")"
|
1930
|
+
def flag_perm__list
|
1931
|
+
match_re(Patterns::FLAG_PERM_LIST, "PERMANENTFLAGS flag-perm list")[1]
|
1932
|
+
.split(nil)
|
1933
|
+
.map! { _1.start_with?("\\") ? _1[1..].capitalize.to_sym : _1 }
|
1934
|
+
end
|
1935
|
+
|
1936
|
+
# Not checking for max one mbx-list-sflag in the parser.
|
1937
|
+
# >>>
|
1938
|
+
# mbx-list-flags = *(mbx-list-oflag SP) mbx-list-sflag
|
1939
|
+
# *(SP mbx-list-oflag) /
|
1940
|
+
# mbx-list-oflag *(SP mbx-list-oflag)
|
1941
|
+
# mbx-list-oflag = "\Noinferiors" / child-mbox-flag /
|
1942
|
+
# "\Subscribed" / "\Remote" / flag-extension
|
1943
|
+
# ; Other flags; multiple from this list are
|
1944
|
+
# ; possible per LIST response, but each flag
|
1945
|
+
# ; can only appear once per LIST response
|
1946
|
+
# mbx-list-sflag = "\NonExistent" / "\Noselect" / "\Marked" /
|
1947
|
+
# "\Unmarked"
|
1948
|
+
# ; Selectability flags; only one per LIST response
|
1949
|
+
def parens__mbx_list_flags
|
1950
|
+
match_re(Patterns::MBX_LIST_FLAGS, "mbx-list-flags")[1]
|
1951
|
+
.split(nil).map! { _1.capitalize.to_sym }
|
1952
|
+
end
|
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
|
1492
1965
|
end
|
1493
1966
|
|
1494
|
-
|
1495
1967
|
# See https://www.rfc-editor.org/errata/rfc3501
|
1496
1968
|
#
|
1497
1969
|
# charset = atom / quoted
|
1498
|
-
def charset
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1970
|
+
def charset; quoted? || atom end
|
1971
|
+
|
1972
|
+
# RFC7162:
|
1973
|
+
# mod-sequence-value = 1*DIGIT
|
1974
|
+
# ;; Positive unsigned 63-bit integer
|
1975
|
+
# ;; (mod-sequence)
|
1976
|
+
# ;; (1 <= n <= 9,223,372,036,854,775,807).
|
1977
|
+
alias mod_sequence_value nz_number64
|
1978
|
+
|
1979
|
+
# RFC7162:
|
1980
|
+
# permsg-modsequence = mod-sequence-value
|
1981
|
+
# ;; Per-message mod-sequence.
|
1982
|
+
alias permsg_modsequence mod_sequence_value
|
1983
|
+
|
1984
|
+
def parens__modseq; lpar; _ = permsg_modsequence; rpar; _ end
|
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
|
1505
1994
|
|
1506
1995
|
# RFC-4315 (UIDPLUS) or RFC9051 (IMAP4rev2):
|
1507
1996
|
# uid-set = (uniqueid / uid-range) *("," uid-set)
|
@@ -1535,10 +2024,10 @@ module Net
|
|
1535
2024
|
#
|
1536
2025
|
# This advances @pos directly so it's safe before changing @lex_state.
|
1537
2026
|
def accept_spaces
|
1538
|
-
|
1539
|
-
|
2027
|
+
return false unless SP?
|
2028
|
+
@str.index(SPACES_REGEXP, @pos) and
|
1540
2029
|
@pos = $~.end(0)
|
1541
|
-
|
2030
|
+
true
|
1542
2031
|
end
|
1543
2032
|
|
1544
2033
|
def next_token
|
@@ -1548,42 +2037,47 @@ module Net
|
|
1548
2037
|
@pos = $~.end(0)
|
1549
2038
|
if $1
|
1550
2039
|
return Token.new(T_SPACE, $+)
|
1551
|
-
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
|
1552
2046
|
# greedily match ATOM, prefixed with NUMBER, NIL, or PLUS.
|
1553
|
-
return Token.new(T_ATOM, $
|
1554
|
-
elsif $3
|
1555
|
-
return Token.new(T_NIL, $+)
|
2047
|
+
return Token.new(T_ATOM, $3)
|
1556
2048
|
elsif $4
|
1557
|
-
return Token.new(
|
2049
|
+
return Token.new(T_NIL, $+)
|
1558
2050
|
elsif $5
|
2051
|
+
return Token.new(T_NUMBER, $+)
|
2052
|
+
elsif $6
|
1559
2053
|
return Token.new(T_PLUS, $+)
|
1560
|
-
elsif $
|
2054
|
+
elsif $8
|
1561
2055
|
# match ATOM, without a NUMBER, NIL, or PLUS prefix
|
1562
2056
|
return Token.new(T_ATOM, $+)
|
1563
|
-
elsif $8
|
1564
|
-
return Token.new(T_QUOTED, Patterns.unescape_quoted($+))
|
1565
2057
|
elsif $9
|
1566
|
-
return Token.new(
|
2058
|
+
return Token.new(T_QUOTED, Patterns.unescape_quoted($+))
|
1567
2059
|
elsif $10
|
1568
|
-
return Token.new(
|
2060
|
+
return Token.new(T_LPAR, $+)
|
1569
2061
|
elsif $11
|
1570
|
-
return Token.new(
|
2062
|
+
return Token.new(T_RPAR, $+)
|
1571
2063
|
elsif $12
|
1572
|
-
return Token.new(
|
2064
|
+
return Token.new(T_BSLASH, $+)
|
1573
2065
|
elsif $13
|
1574
|
-
return Token.new(
|
2066
|
+
return Token.new(T_STAR, $+)
|
1575
2067
|
elsif $14
|
1576
|
-
return Token.new(
|
2068
|
+
return Token.new(T_LBRA, $+)
|
1577
2069
|
elsif $15
|
2070
|
+
return Token.new(T_RBRA, $+)
|
2071
|
+
elsif $16
|
1578
2072
|
len = $+.to_i
|
1579
2073
|
val = @str[@pos, len]
|
1580
2074
|
@pos += len
|
1581
2075
|
return Token.new(T_LITERAL, val)
|
1582
|
-
elsif $16
|
1583
|
-
return Token.new(T_PERCENT, $+)
|
1584
2076
|
elsif $17
|
1585
|
-
return Token.new(
|
2077
|
+
return Token.new(T_PERCENT, $+)
|
1586
2078
|
elsif $18
|
2079
|
+
return Token.new(T_CRLF, $+)
|
2080
|
+
elsif $19
|
1587
2081
|
return Token.new(T_EOF, $+)
|
1588
2082
|
else
|
1589
2083
|
parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid")
|