net-imap 0.5.1 → 0.5.4

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.

@@ -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
  #
@@ -185,6 +185,11 @@ module Net
185
185
  @str[@pos, str.length] == str
186
186
  end
187
187
 
188
+ def peek_re?(re)
189
+ assert_no_lookahead if Net::IMAP.debug
190
+ re.match?(@str, @pos)
191
+ end
192
+
188
193
  def peek_re(re)
189
194
  assert_no_lookahead if config.debug?
190
195
  re.match(@str, @pos)
@@ -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