net-imap 0.5.1 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of net-imap might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +7 -3
- data/docs/styles.css +65 -14
- data/lib/net/imap/command_data.rb +48 -46
- data/lib/net/imap/data_lite.rb +225 -0
- data/lib/net/imap/esearch_result.rb +180 -0
- data/lib/net/imap/response_data.rb +2 -0
- data/lib/net/imap/response_parser/parser_utils.rb +5 -0
- data/lib/net/imap/response_parser.rb +137 -2
- data/lib/net/imap/vanished_data.rb +56 -0
- data/lib/net/imap.rb +531 -226
- data/rakelib/rfcs.rake +1 -0
- metadata +8 -5
@@ -2,9 +2,11 @@
|
|
2
2
|
|
3
3
|
module Net
|
4
4
|
class IMAP < Protocol
|
5
|
+
autoload :ESearchResult, "#{__dir__}/esearch_result"
|
5
6
|
autoload :FetchData, "#{__dir__}/fetch_data"
|
6
7
|
autoload :SearchResult, "#{__dir__}/search_result"
|
7
8
|
autoload :SequenceSet, "#{__dir__}/sequence_set"
|
9
|
+
autoload :VanishedData, "#{__dir__}/vanished_data"
|
8
10
|
|
9
11
|
# Net::IMAP::ContinuationRequest represents command continuation requests.
|
10
12
|
#
|
@@ -321,6 +321,24 @@ module Net
|
|
321
321
|
SEQUENCE_SET = /#{SEQUENCE_SET_ITEM}(?:,#{SEQUENCE_SET_ITEM})*/n
|
322
322
|
SEQUENCE_SET_STR = /\A#{SEQUENCE_SET}\z/n
|
323
323
|
|
324
|
+
# partial-range-first = nz-number ":" nz-number
|
325
|
+
# ;; Request to search from oldest (lowest UIDs) to
|
326
|
+
# ;; more recent messages.
|
327
|
+
# ;; A range 500:400 is the same as 400:500.
|
328
|
+
# ;; This is similar to <seq-range> from [RFC3501]
|
329
|
+
# ;; but cannot contain "*".
|
330
|
+
PARTIAL_RANGE_FIRST = /\A(#{NZ_NUMBER}):(#{NZ_NUMBER})\z/n
|
331
|
+
|
332
|
+
# partial-range-last = MINUS nz-number ":" MINUS nz-number
|
333
|
+
# ;; Request to search from newest (highest UIDs) to
|
334
|
+
# ;; oldest messages.
|
335
|
+
# ;; A range -500:-400 is the same as -400:-500.
|
336
|
+
PARTIAL_RANGE_LAST = /\A(-#{NZ_NUMBER}):(-#{NZ_NUMBER})\z/n
|
337
|
+
|
338
|
+
# partial-range = partial-range-first / partial-range-last
|
339
|
+
PARTIAL_RANGE = Regexp.union(PARTIAL_RANGE_FIRST,
|
340
|
+
PARTIAL_RANGE_LAST)
|
341
|
+
|
324
342
|
# RFC3501:
|
325
343
|
# literal = "{" number "}" CRLF *CHAR8
|
326
344
|
# ; Number represents the number of CHAR8s
|
@@ -769,8 +787,6 @@ module Net
|
|
769
787
|
def response_data__ignored; response_data__unhandled(IgnoredResponse) end
|
770
788
|
alias response_data__noop response_data__ignored
|
771
789
|
|
772
|
-
alias esearch_response response_data__unhandled
|
773
|
-
alias expunged_resp response_data__unhandled
|
774
790
|
alias uidfetch_resp response_data__unhandled
|
775
791
|
alias listrights_data response_data__unhandled
|
776
792
|
alias myrights_data response_data__unhandled
|
@@ -842,6 +858,20 @@ module Net
|
|
842
858
|
alias mailbox_data__exists response_data__simple_numeric
|
843
859
|
alias mailbox_data__recent response_data__simple_numeric
|
844
860
|
|
861
|
+
# The name for this is confusing, because it *replaces* EXPUNGE
|
862
|
+
# >>>
|
863
|
+
# expunged-resp = "VANISHED" [SP "(EARLIER)"] SP known-uids
|
864
|
+
def expunged_resp
|
865
|
+
name = label "VANISHED"; SP!
|
866
|
+
earlier = if lpar? then label("EARLIER"); rpar; SP!; true else false end
|
867
|
+
uids = known_uids
|
868
|
+
data = VanishedData[uids, earlier]
|
869
|
+
UntaggedResponse.new name, data, @str
|
870
|
+
end
|
871
|
+
|
872
|
+
# TODO: replace with uid_set
|
873
|
+
alias known_uids sequence_set
|
874
|
+
|
845
875
|
# RFC3501 & RFC9051:
|
846
876
|
# msg-att = "(" (msg-att-dynamic / msg-att-static)
|
847
877
|
# *(SP (msg-att-dynamic / msg-att-static)) ")"
|
@@ -1468,6 +1498,111 @@ module Net
|
|
1468
1498
|
end
|
1469
1499
|
alias sort_data mailbox_data__search
|
1470
1500
|
|
1501
|
+
# esearch-response = "ESEARCH" [search-correlator] [SP "UID"]
|
1502
|
+
# *(SP search-return-data)
|
1503
|
+
# ;; Note that SEARCH and ESEARCH responses
|
1504
|
+
# ;; SHOULD be mutually exclusive,
|
1505
|
+
# ;; i.e., only one of the response types
|
1506
|
+
# ;; should be
|
1507
|
+
# ;; returned as a result of a command.
|
1508
|
+
# esearch-response = "ESEARCH" [search-correlator] [SP "UID"]
|
1509
|
+
# *(SP search-return-data)
|
1510
|
+
# ; ESEARCH response replaces SEARCH response
|
1511
|
+
# ; from IMAP4rev1.
|
1512
|
+
# search-correlator = SP "(" "TAG" SP tag-string ")"
|
1513
|
+
def esearch_response
|
1514
|
+
name = label("ESEARCH")
|
1515
|
+
tag = search_correlator if peek_str?(" (")
|
1516
|
+
uid = peek_re?(/\G UID\b/i) && (SP!; label("UID"); true)
|
1517
|
+
data = []
|
1518
|
+
data << search_return_data while SP?
|
1519
|
+
esearch = ESearchResult.new(tag, uid, data)
|
1520
|
+
UntaggedResponse.new(name, esearch, @str)
|
1521
|
+
end
|
1522
|
+
|
1523
|
+
# From RFC4731 (ESEARCH):
|
1524
|
+
# search-return-data = "MIN" SP nz-number /
|
1525
|
+
# "MAX" SP nz-number /
|
1526
|
+
# "ALL" SP sequence-set /
|
1527
|
+
# "COUNT" SP number /
|
1528
|
+
# search-ret-data-ext
|
1529
|
+
# ; All return data items conform to
|
1530
|
+
# ; search-ret-data-ext syntax.
|
1531
|
+
# search-ret-data-ext = search-modifier-name SP search-return-value
|
1532
|
+
# search-modifier-name = tagged-ext-label
|
1533
|
+
# search-return-value = tagged-ext-val
|
1534
|
+
#
|
1535
|
+
# From RFC4731 (ESEARCH):
|
1536
|
+
# search-return-data =/ "MODSEQ" SP mod-sequence-value
|
1537
|
+
#
|
1538
|
+
# From RFC9394 (PARTIAL):
|
1539
|
+
# search-return-data =/ ret-data-partial
|
1540
|
+
#
|
1541
|
+
def search_return_data
|
1542
|
+
label = search_modifier_name; SP!
|
1543
|
+
value =
|
1544
|
+
case label
|
1545
|
+
when "MIN" then nz_number
|
1546
|
+
when "MAX" then nz_number
|
1547
|
+
when "ALL" then sequence_set
|
1548
|
+
when "COUNT" then number
|
1549
|
+
when "MODSEQ" then mod_sequence_value # RFC7162: CONDSTORE
|
1550
|
+
when "PARTIAL" then ret_data_partial__value # RFC9394: PARTIAL
|
1551
|
+
else search_return_value
|
1552
|
+
end
|
1553
|
+
[label, value]
|
1554
|
+
end
|
1555
|
+
|
1556
|
+
# From RFC5267 (CONTEXT=SEARCH, CONTEXT=SORT) and RFC9394 (PARTIAL):
|
1557
|
+
# ret-data-partial = "PARTIAL"
|
1558
|
+
# SP "(" partial-range SP partial-results ")"
|
1559
|
+
def ret_data_partial__value
|
1560
|
+
lpar
|
1561
|
+
range = partial_range; SP!
|
1562
|
+
results = partial_results
|
1563
|
+
rpar
|
1564
|
+
ESearchResult::PartialResult.new(range, results)
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
# partial-range = partial-range-first / partial-range-last
|
1568
|
+
# tagged-ext-simple =/ partial-range-last
|
1569
|
+
def partial_range
|
1570
|
+
case (str = atom)
|
1571
|
+
when Patterns::PARTIAL_RANGE_FIRST, Patterns::PARTIAL_RANGE_LAST
|
1572
|
+
min, max = [Integer($1), Integer($2)].minmax
|
1573
|
+
min..max
|
1574
|
+
else
|
1575
|
+
parse_error("unexpected atom %p, expected partial-range", str)
|
1576
|
+
end
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
# partial-results = sequence-set / "NIL"
|
1580
|
+
# ;; <sequence-set> from [RFC3501].
|
1581
|
+
# ;; NIL indicates that no results correspond to
|
1582
|
+
# ;; the requested range.
|
1583
|
+
def partial_results; NIL? ? nil : sequence_set end
|
1584
|
+
|
1585
|
+
# search-modifier-name = tagged-ext-label
|
1586
|
+
alias search_modifier_name tagged_ext_label
|
1587
|
+
|
1588
|
+
# search-return-value = tagged-ext-val
|
1589
|
+
# ; Data for the returned search option.
|
1590
|
+
# ; A single "nz-number"/"number"/"number64" value
|
1591
|
+
# ; can be returned as an atom (i.e., without
|
1592
|
+
# ; quoting). A sequence-set can be returned
|
1593
|
+
# ; as an atom as well.
|
1594
|
+
def search_return_value; ExtensionData.new(tagged_ext_val) end
|
1595
|
+
|
1596
|
+
# search-correlator = SP "(" "TAG" SP tag-string ")"
|
1597
|
+
def search_correlator
|
1598
|
+
SP!; lpar; label("TAG"); SP!; tag = tag_string; rpar
|
1599
|
+
tag
|
1600
|
+
end
|
1601
|
+
|
1602
|
+
# tag-string = astring
|
1603
|
+
# ; <tag> represented as <astring>
|
1604
|
+
alias tag_string astring
|
1605
|
+
|
1471
1606
|
# RFC5256: THREAD
|
1472
1607
|
# thread-data = "THREAD" [SP 1*thread-list]
|
1473
1608
|
def thread_data
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP < Protocol
|
5
|
+
|
6
|
+
# Net::IMAP::VanishedData represents the contents of a +VANISHED+ response,
|
7
|
+
# which is described by the
|
8
|
+
# {QRESYNC}[https://www.rfc-editor.org/rfc/rfc7162.html] extension.
|
9
|
+
# [{RFC7162 §3.2.10}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.2.10]].
|
10
|
+
#
|
11
|
+
# +VANISHED+ responses replace +EXPUNGE+ responses when either the
|
12
|
+
# {QRESYNC}[https://www.rfc-editor.org/rfc/rfc7162.html] or the
|
13
|
+
# {UIDONLY}[https://www.rfc-editor.org/rfc/rfc9586.html] extension has been
|
14
|
+
# enabled.
|
15
|
+
class VanishedData < Data.define(:uids, :earlier)
|
16
|
+
|
17
|
+
# Returns a new VanishedData object.
|
18
|
+
#
|
19
|
+
# * +uids+ will be converted by SequenceSet.[].
|
20
|
+
# * +earlier+ will be converted to +true+ or +false+
|
21
|
+
def initialize(uids:, earlier:)
|
22
|
+
uids = SequenceSet[uids]
|
23
|
+
earlier = !!earlier
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# :attr_reader: uids
|
29
|
+
#
|
30
|
+
# SequenceSet of UIDs that have been permanently removed from the mailbox.
|
31
|
+
|
32
|
+
##
|
33
|
+
# :attr_reader: earlier
|
34
|
+
#
|
35
|
+
# +true+ when the response was caused by Net::IMAP#uid_fetch with
|
36
|
+
# <tt>vanished: true</tt> or Net::IMAP#select/Net::IMAP#examine with
|
37
|
+
# <tt>qresync: true</tt>.
|
38
|
+
#
|
39
|
+
# +false+ when the response is used to announce message removals within an
|
40
|
+
# already selected mailbox.
|
41
|
+
|
42
|
+
# rdoc doesn't handle attr aliases nicely. :(
|
43
|
+
alias earlier? earlier # :nodoc:
|
44
|
+
##
|
45
|
+
# :attr_reader: earlier?
|
46
|
+
#
|
47
|
+
# Alias for #earlier.
|
48
|
+
|
49
|
+
# Returns an Array of all of the UIDs in #uids.
|
50
|
+
#
|
51
|
+
# See SequenceSet#numbers.
|
52
|
+
def to_a; uids.numbers end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|