net-imap 0.5.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d271261a2609a31b8c84ae74913bf57d5046e95318db7660b3230940bdbff447
4
- data.tar.gz: 771baa4b8a6c3f83f6ef4f320dda8dc5ea9f2fa2ace15fd9b0c1c1b3a594363a
3
+ metadata.gz: a871e99f51067e3a509aa5a74fe2c49f4d79496bcec2f21ac50c3c5793d2cdbf
4
+ data.tar.gz: 302c03e65954f9a118d877f97ea2296f34a7f50c8b1299e3e4f8b589cb620880
5
5
  SHA512:
6
- metadata.gz: 230d40d8d183b1c69f63050990df140b49aa5731d0868713e44d2da68c2713ede4fd8d4b1aff60103c25ac6586076e220973b774a43cd5a7b3eaad4e49e7bf3b
7
- data.tar.gz: 156e6c86f6fe39a76de89275bfcd3c57b5adcd375de6add45cd00a30bf92bbf04a2b475702db33cae0509fc0b7b3e015518d4991cd5c467f833cacb0f75d3ca6
6
+ metadata.gz: 85d97c9729220265da03588f175fea22bcdd27d40cd06910ad35bc01ed64bd0c27b2769790464712572166761f3e82fc4eb6330fef64f45cceb0b3e1007f7126
7
+ data.tar.gz: aa18d53b81e57ddfbdde2b8295ca7324f965fa744846fdd756ec4ca1c30e6e72092b106ff454d100f70d1078ee239f2b3e643bd4650606a2fc3b4ab5a2d0cc1f
data/docs/styles.css CHANGED
@@ -6,9 +6,7 @@
6
6
 
7
7
  main .method-detail {
8
8
  display: grid;
9
- grid-template-areas: "header controls"
10
- "description description";
11
- grid-template-columns: 1fr min-content;
9
+ grid-template-columns: 1fr auto;
12
10
  justify-content: space-between;
13
11
  }
14
12
 
@@ -20,19 +18,16 @@ main .method-header, main .method-controls {
20
18
  }
21
19
 
22
20
  main .method-header {
23
- grid-area: "header";
24
21
  border-right: none;
25
22
  border-radius: 4px 0 0 4px;
26
23
  }
27
24
 
28
25
  main .method-controls {
29
- grid-area: "controls";
30
26
  border-left: none;
31
27
  border-radius: 0 4px 4px 0;
32
28
  }
33
29
 
34
30
  main .method-description, main .aliases {
35
- grid-area: "description";
36
31
  grid-column: 1 / span 2;
37
32
  padding-left: 1em;
38
33
  }
@@ -153,6 +153,38 @@ module Net
153
153
  end
154
154
  end
155
155
 
156
+ class PartialRange < CommandData # :nodoc:
157
+ uint32_max = 2**32 - 1
158
+ POS_RANGE = 1..uint32_max
159
+ NEG_RANGE = -uint32_max..-1
160
+ Positive = ->{ (_1 in Range) and POS_RANGE.cover?(_1) }
161
+ Negative = ->{ (_1 in Range) and NEG_RANGE.cover?(_1) }
162
+
163
+ def initialize(data:)
164
+ min, max = case data
165
+ in Range
166
+ data.minmax.map { Integer _1 }
167
+ in ResponseParser::Patterns::PARTIAL_RANGE
168
+ data.split(":").map { Integer _1 }.minmax
169
+ else
170
+ raise ArgumentError, "invalid partial range input: %p" % [data]
171
+ end
172
+ data = min..max
173
+ unless data in Positive | Negative
174
+ raise ArgumentError, "invalid partial-range: %p" % [data]
175
+ end
176
+ super
177
+ rescue TypeError, RangeError
178
+ raise ArgumentError, "expected range min/max to be Integers"
179
+ end
180
+
181
+ def formatted = "%d:%d" % data.minmax
182
+
183
+ def send_data(imap, tag)
184
+ imap.__send__(:put_string, formatted)
185
+ end
186
+ end
187
+
156
188
  # *DEPRECATED*. Replaced by SequenceSet.
157
189
  class MessageSet < CommandData # :nodoc:
158
190
  def send_data(imap, tag)
@@ -35,16 +35,16 @@ module Net
35
35
 
36
36
  # :call-seq: to_a -> Array of integers
37
37
  #
38
- # When #all contains a SequenceSet of message sequence
38
+ # When either #all or #partial contains a SequenceSet of message sequence
39
39
  # numbers or UIDs, +to_a+ returns that set as an array of integers.
40
40
  #
41
- # When #all is +nil+, either because the server
42
- # returned no results or because +ALL+ was not included in
41
+ # When both #all and #partial are +nil+, either because the server
42
+ # returned no results or because +ALL+ and +PARTIAL+ were not included in
43
43
  # the IMAP#search +RETURN+ options, #to_a returns an empty array.
44
44
  #
45
45
  # Note that SearchResult also implements +to_a+, so it can be used without
46
46
  # checking if the server returned +SEARCH+ or +ESEARCH+ data.
47
- def to_a; all&.numbers || [] end
47
+ def to_a; all&.numbers || partial&.to_a || [] end
48
48
 
49
49
  ##
50
50
  # attr_reader: tag
@@ -135,6 +135,46 @@ module Net
135
135
  # and +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.2].
136
136
  def modseq; data.assoc("MODSEQ")&.last end
137
137
 
138
+ # Returned by ESearchResult#partial.
139
+ #
140
+ # Requires +PARTIAL+ {[RFC9394]}[https://www.rfc-editor.org/rfc/rfc9394.html]
141
+ # or <tt>CONTEXT=SEARCH</tt>/<tt>CONTEXT=SORT</tt>
142
+ # {[RFC5267]}[https://www.rfc-editor.org/rfc/rfc5267.html]
143
+ #
144
+ # See also: #to_a
145
+ class PartialResult < Data.define(:range, :results)
146
+ def initialize(range:, results:)
147
+ range => Range
148
+ results = SequenceSet[results] unless results.nil?
149
+ super
150
+ end
151
+
152
+ ##
153
+ # method: range
154
+ # :call-seq: range -> range
155
+
156
+ ##
157
+ # method: results
158
+ # :call-seq: results -> sequence set or nil
159
+
160
+ # Converts #results to an array of integers.
161
+ #
162
+ # See also: ESearchResult#to_a.
163
+ def to_a; results&.numbers || [] end
164
+ end
165
+
166
+ # :call-seq: partial -> PartialResult or nil
167
+ #
168
+ # A PartialResult containing a subset of the message sequence numbers or
169
+ # UIDs that satisfy the SEARCH criteria.
170
+ #
171
+ # Requires +PARTIAL+ {[RFC9394]}[https://www.rfc-editor.org/rfc/rfc9394.html]
172
+ # or <tt>CONTEXT=SEARCH</tt>/<tt>CONTEXT=SORT</tt>
173
+ # {[RFC5267]}[https://www.rfc-editor.org/rfc/rfc5267.html]
174
+ #
175
+ # See also: #to_a
176
+ def partial; data.assoc("PARTIAL")&.last end
177
+
138
178
  end
139
179
  end
140
180
  end
@@ -6,6 +6,7 @@ module Net
6
6
  autoload :FetchData, "#{__dir__}/fetch_data"
7
7
  autoload :SearchResult, "#{__dir__}/search_result"
8
8
  autoload :SequenceSet, "#{__dir__}/sequence_set"
9
+ autoload :VanishedData, "#{__dir__}/vanished_data"
9
10
 
10
11
  # Net::IMAP::ContinuationRequest represents command continuation requests.
11
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,7 +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 expunged_resp response_data__unhandled
773
790
  alias uidfetch_resp response_data__unhandled
774
791
  alias listrights_data response_data__unhandled
775
792
  alias myrights_data response_data__unhandled
@@ -841,6 +858,20 @@ module Net
841
858
  alias mailbox_data__exists response_data__simple_numeric
842
859
  alias mailbox_data__recent response_data__simple_numeric
843
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
+
844
875
  # RFC3501 & RFC9051:
845
876
  # msg-att = "(" (msg-att-dynamic / msg-att-static)
846
877
  # *(SP (msg-att-dynamic / msg-att-static)) ")"
@@ -1504,6 +1535,9 @@ module Net
1504
1535
  # From RFC4731 (ESEARCH):
1505
1536
  # search-return-data =/ "MODSEQ" SP mod-sequence-value
1506
1537
  #
1538
+ # From RFC9394 (PARTIAL):
1539
+ # search-return-data =/ ret-data-partial
1540
+ #
1507
1541
  def search_return_data
1508
1542
  label = search_modifier_name; SP!
1509
1543
  value =
@@ -1513,11 +1547,41 @@ module Net
1513
1547
  when "ALL" then sequence_set
1514
1548
  when "COUNT" then number
1515
1549
  when "MODSEQ" then mod_sequence_value # RFC7162: CONDSTORE
1550
+ when "PARTIAL" then ret_data_partial__value # RFC9394: PARTIAL
1516
1551
  else search_return_value
1517
1552
  end
1518
1553
  [label, value]
1519
1554
  end
1520
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
+
1521
1585
  # search-modifier-name = tagged-ext-label
1522
1586
  alias search_modifier_name tagged_ext_label
1523
1587
 
@@ -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
data/lib/net/imap.rb CHANGED
@@ -534,6 +534,11 @@ module Net
534
534
  # See FetchData#emailid and FetchData#emailid.
535
535
  # - Updates #status with support for the +MAILBOXID+ status attribute.
536
536
  #
537
+ # ==== RFC9394: +PARTIAL+
538
+ # - Updates #search, #uid_search with the +PARTIAL+ return option which adds
539
+ # ESearchResult#partial return data.
540
+ # - Updates #uid_fetch with the +partial+ modifier.
541
+ #
537
542
  # == References
538
543
  #
539
544
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
@@ -701,6 +706,11 @@ module Net
701
706
  # Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
702
707
  # RFC 8474, DOI 10.17487/RFC8474, September 2018,
703
708
  # <https://www.rfc-editor.org/info/rfc8474>.
709
+ # [PARTIAL[https://www.rfc-editor.org/info/rfc9394]]::
710
+ # Melnikov, A., Achuthan, A., Nagulakonda, V., and L. Alves,
711
+ # "IMAP PARTIAL Extension for Paged SEARCH and FETCH", RFC 9394,
712
+ # DOI 10.17487/RFC9394, June 2023,
713
+ # <https://www.rfc-editor.org/info/rfc9394>.
704
714
  #
705
715
  # === IANA registries
706
716
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
@@ -723,7 +733,7 @@ module Net
723
733
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
724
734
  #
725
735
  class IMAP < Protocol
726
- VERSION = "0.5.2"
736
+ VERSION = "0.5.4"
727
737
 
728
738
  # Aliases for supported capabilities, to be used with the #enable command.
729
739
  ENABLE_ALIASES = {
@@ -1889,48 +1899,64 @@ module Net
1889
1899
  send_command("UNSELECT")
1890
1900
  end
1891
1901
 
1902
+ # call-seq:
1903
+ # expunge -> array of message sequence numbers
1904
+ # expunge -> VanishedData of UIDs
1905
+ #
1892
1906
  # Sends an {EXPUNGE command [IMAP4rev1 §6.4.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.3]
1893
- # Sends a EXPUNGE command to permanently remove from the currently
1894
- # selected mailbox all messages that have the \Deleted flag set.
1907
+ # to permanently remove all messages with the +\Deleted+ flag from the
1908
+ # currently selected mailbox.
1909
+ #
1910
+ # Returns either an array of expunged message <em>sequence numbers</em> or
1911
+ # (when the appropriate capability is enabled) VanishedData of expunged
1912
+ # UIDs. Previously unhandled +EXPUNGE+ or +VANISHED+ responses are merged
1913
+ # with the direct response to this command. <tt>VANISHED (EARLIER)</tt>
1914
+ # responses will _not_ be merged.
1915
+ #
1916
+ # When no messages have been expunged, an empty array is returned,
1917
+ # regardless of which extensions are enabled. In a future release, an empty
1918
+ # VanishedData may be returned, based on the currently enabled extensions.
1895
1919
  #
1896
1920
  # Related: #uid_expunge
1921
+ #
1922
+ # ==== Capabilities
1923
+ #
1924
+ # When either QRESYNC[https://tools.ietf.org/html/rfc7162] or
1925
+ # UIDONLY[https://tools.ietf.org/html/rfc9586] are enabled, #expunge
1926
+ # returns VanishedData, which contains UIDs---<em>not message sequence
1927
+ # numbers</em>.
1897
1928
  def expunge
1898
- synchronize do
1899
- send_command("EXPUNGE")
1900
- clear_responses("EXPUNGE")
1901
- end
1929
+ expunge_internal("EXPUNGE")
1902
1930
  end
1903
1931
 
1932
+ # call-seq:
1933
+ # uid_expunge{uid_set) -> array of message sequence numbers
1934
+ # uid_expunge{uid_set) -> VanishedData of UIDs
1935
+ #
1904
1936
  # Sends a {UID EXPUNGE command [RFC4315 §2.1]}[https://www.rfc-editor.org/rfc/rfc4315#section-2.1]
1905
1937
  # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9]
1906
1938
  # to permanently remove all messages that have both the <tt>\\Deleted</tt>
1907
1939
  # flag set and a UID that is included in +uid_set+.
1908
1940
  #
1941
+ # Returns the same result type as #expunge.
1942
+ #
1909
1943
  # By using #uid_expunge instead of #expunge when resynchronizing with
1910
1944
  # the server, the client can ensure that it does not inadvertantly
1911
1945
  # remove any messages that have been marked as <tt>\\Deleted</tt> by other
1912
1946
  # clients between the time that the client was last connected and
1913
1947
  # the time the client resynchronizes.
1914
1948
  #
1915
- # *Note:*
1916
- # >>>
1917
- # Although the command takes a set of UIDs for its argument, the
1918
- # server still returns regular EXPUNGE responses, which contain
1919
- # a <em>sequence number</em>. These will be deleted from
1920
- # #responses and this method returns them as an array of
1921
- # <em>sequence number</em> integers.
1922
- #
1923
1949
  # Related: #expunge
1924
1950
  #
1925
1951
  # ==== Capabilities
1926
1952
  #
1927
- # The server's capabilities must include +UIDPLUS+
1953
+ # The server's capabilities must include either +IMAP4rev2+ or +UIDPLUS+
1928
1954
  # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]].
1955
+ #
1956
+ # Otherwise, #uid_expunge is updated by extensions in the same way as
1957
+ # #expunge.
1929
1958
  def uid_expunge(uid_set)
1930
- synchronize do
1931
- send_command("UID EXPUNGE", SequenceSet.new(uid_set))
1932
- clear_responses("EXPUNGE")
1933
- end
1959
+ expunge_internal("UID EXPUNGE", SequenceSet.new(uid_set))
1934
1960
  end
1935
1961
 
1936
1962
  # :call-seq:
@@ -1942,7 +1968,7 @@ module Net
1942
1968
  # and returns either a SearchResult or an ESearchResult. SearchResult
1943
1969
  # inherits from Array (for backward compatibility) but adds
1944
1970
  # SearchResult#modseq when the +CONDSTORE+ capability has been enabled.
1945
- # ESearchResult also implements to_a{rdoc-ref:ESearchResult#to_a}, for
1971
+ # ESearchResult also implements {#to_a}[rdoc-ref:ESearchResult#to_a], for
1946
1972
  # compatibility with SearchResult.
1947
1973
  #
1948
1974
  # +criteria+ is one or more search keys and their arguments, which may be
@@ -1955,8 +1981,9 @@ module Net
1955
1981
  # the server to return an ESearchResult instead of a SearchResult, but some
1956
1982
  # servers disobey this requirement. <em>Requires an extended search
1957
1983
  # capability, such as +ESEARCH+ or +IMAP4rev2+.</em>
1958
- # See {"Argument translation"}[rdoc-ref:#search@Argument+translation]
1959
- # and {"Return options"}[rdoc-ref:#search@Return+options], below.
1984
+ # See {"Argument translation"}[rdoc-ref:#search@Argument+translation] and
1985
+ # {"Supported return options"}[rdoc-ref:#search@Supported+return+options],
1986
+ # below.
1960
1987
  #
1961
1988
  # +charset+ is the name of the {registered character
1962
1989
  # set}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
@@ -2066,33 +2093,58 @@ module Net
2066
2093
  # <em>*WARNING:* This is vulnerable to injection attacks when external
2067
2094
  # inputs are used.</em>
2068
2095
  #
2069
- # ==== Return options
2096
+ # ==== Supported return options
2070
2097
  #
2071
2098
  # For full definitions of the standard return options and return data, see
2072
2099
  # the relevant RFCs.
2073
2100
  #
2074
- # ===== +ESEARCH+ or +IMAP4rev2+
2075
- #
2076
- # The following return options require either +ESEARCH+ or +IMAP4rev2+.
2077
- # See [{RFC4731 §3.1}[https://rfc-editor.org/rfc/rfc4731#section-3.1]] or
2078
- # [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]].
2079
- #
2080
2101
  # [+ALL+]
2081
2102
  # Returns ESearchResult#all with a SequenceSet of all matching sequence
2082
2103
  # numbers or UIDs. This is the default, when return options are empty.
2083
2104
  #
2084
2105
  # For compatibility with SearchResult, ESearchResult#to_a returns an
2085
2106
  # Array of message sequence numbers or UIDs.
2107
+ #
2108
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2109
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2110
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2111
+ #
2086
2112
  # [+COUNT+]
2087
2113
  # Returns ESearchResult#count with the number of matching messages.
2114
+ #
2115
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2116
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2117
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2118
+ #
2088
2119
  # [+MAX+]
2089
2120
  # Returns ESearchResult#max with the highest matching sequence number or
2090
2121
  # UID.
2122
+ #
2123
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2124
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2125
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2126
+ #
2091
2127
  # [+MIN+]
2092
2128
  # Returns ESearchResult#min with the lowest matching sequence number or
2093
2129
  # UID.
2094
2130
  #
2095
- # ===== +CONDSTORE+
2131
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2132
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2133
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2134
+ #
2135
+ # [+PARTIAL+ _range_]
2136
+ # Returns ESearchResult#partial with a SequenceSet of a subset of
2137
+ # matching sequence numbers or UIDs, as selected by _range_. As with
2138
+ # sequence numbers, the first result is +1+: <tt>1..500</tt> selects the
2139
+ # first 500 search results (in mailbox order), <tt>501..1000</tt> the
2140
+ # second 500, and so on. _range_ may also be negative: <tt>-500..-1</tt>
2141
+ # selects the last 500 search results.
2142
+ #
2143
+ # <em>Requires either the <tt>CONTEXT=SEARCH</tt> or +PARTIAL+ capabability.</em>
2144
+ # {[RFC5267]}[https://rfc-editor.org/rfc/rfc5267]
2145
+ # {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394]
2146
+ #
2147
+ # ===== +MODSEQ+ return data
2096
2148
  #
2097
2149
  # ESearchResult#modseq return data does not have a corresponding return
2098
2150
  # option. Instead, it is returned if the +MODSEQ+ search key is used or
@@ -2104,8 +2156,8 @@ module Net
2104
2156
  #
2105
2157
  # {RFC4466 §2.6}[https://www.rfc-editor.org/rfc/rfc4466.html#section-2.6]
2106
2158
  # defines standard syntax for search extensions. Net::IMAP allows sending
2107
- # unknown search return options and will parse unknown search extensions'
2108
- # return values into ExtensionData. Please note that this is an
2159
+ # unsupported search return options and will parse unsupported search
2160
+ # extensions' return values into ExtensionData. Please note that this is an
2109
2161
  # intentionally _unstable_ API. Future releases may return different
2110
2162
  # (incompatible) objects, <em>without deprecation or warning</em>.
2111
2163
  #
@@ -2341,14 +2393,9 @@ module Net
2341
2393
  # Sends a {FETCH command [IMAP4rev1 §6.4.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.5]
2342
2394
  # to retrieve data associated with a message in the mailbox.
2343
2395
  #
2344
- # The +set+ parameter is a number or a range between two numbers,
2345
- # or an array of those. The number is a message sequence number,
2346
- # where -1 represents a '*' for use in range notation like 100..-1
2347
- # being interpreted as '100:*'. Beware that the +exclude_end?+
2348
- # property of a Range object is ignored, and the contents of a
2349
- # range are independent of the order of the range endpoints as per
2350
- # the protocol specification, so 1...5, 5..1 and 5...1 are all
2351
- # equivalent to 1..5.
2396
+ # +set+ is the message sequence numbers to fetch, and may be any valid input
2397
+ # to {SequenceSet[...]}[rdoc-ref:SequenceSet@Creating+sequence+sets].
2398
+ # (For UIDs, use #uid_fetch instead.)
2352
2399
  #
2353
2400
  # +attr+ is a list of attributes to fetch; see the documentation
2354
2401
  # for FetchData for a list of valid attributes.
@@ -2358,7 +2405,7 @@ module Net
2358
2405
  #
2359
2406
  # The return value is an array of FetchData.
2360
2407
  #
2361
- # Related: #uid_search, FetchData
2408
+ # Related: #uid_fetch, FetchData
2362
2409
  #
2363
2410
  # ==== For example:
2364
2411
  #
@@ -2387,30 +2434,66 @@ module Net
2387
2434
  # {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the
2388
2435
  # +changedsince+ argument. Using +changedsince+ implicitly enables the
2389
2436
  # +CONDSTORE+ extension.
2390
- def fetch(set, attr, mod = nil, changedsince: nil)
2391
- fetch_internal("FETCH", set, attr, mod, changedsince: changedsince)
2437
+ def fetch(...)
2438
+ fetch_internal("FETCH", ...)
2392
2439
  end
2393
2440
 
2394
2441
  # :call-seq:
2395
- # uid_fetch(set, attr, changedsince: nil) -> array of FetchData
2442
+ # uid_fetch(set, attr, changedsince: nil, partial: nil) -> array of FetchData
2396
2443
  #
2397
2444
  # Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
2398
2445
  # to retrieve data associated with a message in the mailbox.
2399
2446
  #
2400
- # Similar to #fetch, but the +set+ parameter contains unique identifiers
2401
- # instead of message sequence numbers.
2447
+ # +set+ is the message UIDs to fetch, and may be any valid input to
2448
+ # {SequenceSet[...]}[rdoc-ref:SequenceSet@Creating+sequence+sets].
2449
+ # (For message sequence numbers, use #fetch instead.)
2402
2450
  #
2451
+ # +attr+ behaves the same as with #fetch.
2403
2452
  # >>>
2404
2453
  # *Note:* Servers _MUST_ implicitly include the +UID+ message data item as
2405
2454
  # part of any +FETCH+ response caused by a +UID+ command, regardless of
2406
2455
  # whether a +UID+ was specified as a message data item to the +FETCH+.
2407
2456
  #
2457
+ # +changedsince+ (optional) behaves the same as with #fetch.
2458
+ #
2459
+ # +partial+ is an optional range to limit the number of results returned.
2460
+ # It's useful when +set+ contains an unknown number of messages.
2461
+ # <tt>1..500</tt> returns the first 500 messages in +set+ (in mailbox
2462
+ # order), <tt>501..1000</tt> the second 500, and so on. +partial+ may also
2463
+ # be negative: <tt>-500..-1</tt> selects the last 500 messages in +set+.
2464
+ # <em>Requires the +PARTIAL+ capabability.</em>
2465
+ # {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394]
2466
+ #
2467
+ # For example:
2468
+ #
2469
+ # # Without partial, the size of the results may be unknown beforehand:
2470
+ # results = imap.uid_fetch(next_uid_to_fetch.., %w(UID FLAGS))
2471
+ # # ... maybe wait for a long time ... and allocate a lot of memory ...
2472
+ # results.size # => 0..2**32-1
2473
+ # process results # may also take a long time and use a lot of memory...
2474
+ #
2475
+ # # Using partial, the results may be paginated:
2476
+ # loop do
2477
+ # results = imap.uid_fetch(next_uid_to_fetch.., %w(UID FLAGS),
2478
+ # partial: 1..500)
2479
+ # # fetch should return quickly and allocate little memory
2480
+ # results.size # => 0..500
2481
+ # break if results.empty?
2482
+ # next_uid_to_fetch = results.last.uid + 1
2483
+ # process results
2484
+ # end
2485
+ #
2408
2486
  # Related: #fetch, FetchData
2409
2487
  #
2410
2488
  # ==== Capabilities
2411
- # Same as #fetch.
2412
- def uid_fetch(set, attr, mod = nil, changedsince: nil)
2413
- fetch_internal("UID FETCH", set, attr, mod, changedsince: changedsince)
2489
+ #
2490
+ # The server's capabilities must include +PARTIAL+
2491
+ # {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394] in order to use the
2492
+ # +partial+ argument.
2493
+ #
2494
+ # Otherwise, the same as #fetch.
2495
+ def uid_fetch(...)
2496
+ fetch_internal("UID FETCH", ...)
2414
2497
  end
2415
2498
 
2416
2499
  # :call-seq:
@@ -3261,6 +3344,22 @@ module Net
3261
3344
  end
3262
3345
  end
3263
3346
 
3347
+ def expunge_internal(...)
3348
+ synchronize do
3349
+ send_command(...)
3350
+ expunged_array = clear_responses("EXPUNGE")
3351
+ vanished_array = extract_responses("VANISHED") { !_1.earlier? }
3352
+ if vanished_array.empty?
3353
+ expunged_array
3354
+ elsif vanished_array.length == 1
3355
+ vanished_array.first
3356
+ else
3357
+ merged_uids = SequenceSet[*vanished_array.map(&:uids)]
3358
+ VanishedData[uids: merged_uids, earlier: false]
3359
+ end
3360
+ end
3361
+ end
3362
+
3264
3363
  RETURN_WHOLE = /\ARETURN\z/i
3265
3364
  RETURN_START = /\ARETURN\b/i
3266
3365
  private_constant :RETURN_WHOLE, :RETURN_START
@@ -3306,24 +3405,14 @@ module Net
3306
3405
  ]
3307
3406
  return_opts.map {|opt|
3308
3407
  case opt
3309
- when Symbol then opt.to_s
3310
- when Range then partial_range_last_or_seqset(opt)
3311
- else opt
3408
+ when Symbol then opt.to_s
3409
+ when PartialRange::Negative then PartialRange[opt]
3410
+ when Range then SequenceSet[opt]
3411
+ else opt
3312
3412
  end
3313
3413
  }
3314
3414
  end
3315
3415
 
3316
- def partial_range_last_or_seqset(range)
3317
- case [range.begin, range.end]
3318
- in [Integer => first, Integer => last] if first.negative? && last.negative?
3319
- # partial-range-last [RFC9394]
3320
- first <= last or raise DataFormatError, "empty range: %p" % [range]
3321
- "#{first}:#{last}"
3322
- else
3323
- SequenceSet[range]
3324
- end
3325
- end
3326
-
3327
3416
  def search_internal(cmd, ...)
3328
3417
  args, esearch = search_args(...)
3329
3418
  synchronize do
@@ -3350,7 +3439,12 @@ module Net
3350
3439
  end
3351
3440
  end
3352
3441
 
3353
- def fetch_internal(cmd, set, attr, mod = nil, changedsince: nil)
3442
+ def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
3443
+ set = SequenceSet[set]
3444
+ if partial
3445
+ mod ||= []
3446
+ mod << "PARTIAL" << PartialRange[partial]
3447
+ end
3354
3448
  if changedsince
3355
3449
  mod ||= []
3356
3450
  mod << "CHANGEDSINCE" << Integer(changedsince)
@@ -3367,9 +3461,9 @@ module Net
3367
3461
  synchronize do
3368
3462
  clear_responses("FETCH")
3369
3463
  if mod
3370
- send_command(cmd, SequenceSet.new(set), attr, mod)
3464
+ send_command(cmd, set, attr, mod)
3371
3465
  else
3372
- send_command(cmd, SequenceSet.new(set), attr)
3466
+ send_command(cmd, set, attr)
3373
3467
  end
3374
3468
  clear_responses("FETCH")
3375
3469
  end
data/rakelib/rfcs.rake CHANGED
@@ -145,6 +145,7 @@ RFCS = {
145
145
  8514 => "IMAP SAVEDATE",
146
146
  8970 => "IMAP PREVIEW",
147
147
  9208 => "IMAP QUOTA, QUOTA=, QUOTASET",
148
+ 9394 => "IMAP PARTIAL",
148
149
 
149
150
  # etc...
150
151
  3629 => "UTF8",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-imap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2024-12-16 00:00:00.000000000 Z
12
+ date: 2024-12-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-protocol
@@ -97,6 +97,7 @@ files:
97
97
  - lib/net/imap/stringprep/saslprep_tables.rb
98
98
  - lib/net/imap/stringprep/tables.rb
99
99
  - lib/net/imap/stringprep/trace.rb
100
+ - lib/net/imap/vanished_data.rb
100
101
  - net-imap.gemspec
101
102
  - rakelib/benchmarks.rake
102
103
  - rakelib/rdoc.rake