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.

@@ -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