net-imap 0.5.2 → 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.

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