net-imap 0.4.17 → 0.5.1

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.

@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set" unless defined?(::Set)
4
+
3
5
  module Net
4
6
  class IMAP
5
7
 
@@ -14,13 +16,6 @@ module Net
14
16
  # receive a SequenceSet as an argument, for example IMAP#search, IMAP#fetch,
15
17
  # and IMAP#store.
16
18
  #
17
- # == EXPERIMENTAL API
18
- #
19
- # SequenceSet is currently experimental. Only two methods, ::[] and
20
- # #valid_string, are considered stable. Although the API isn't expected to
21
- # change much, any other methods may be removed or changed without
22
- # deprecation.
23
- #
24
19
  # == Creating sequence sets
25
20
  #
26
21
  # SequenceSet.new with no arguments creates an empty sequence set. Note
@@ -37,7 +32,8 @@ module Net
37
32
  #
38
33
  # SequenceSet.new may receive a single optional argument: a non-zero 32 bit
39
34
  # unsigned integer, a range, a <tt>sequence-set</tt> formatted string,
40
- # another sequence set, or an enumerable containing any of these.
35
+ # another sequence set, a Set (containing only numbers or <tt>*</tt>), or an
36
+ # Array containing any of these (array inputs may be nested).
41
37
  #
42
38
  # set = Net::IMAP::SequenceSet.new(1)
43
39
  # set.valid_string #=> "1"
@@ -286,11 +282,7 @@ module Net
286
282
 
287
283
  # valid inputs for "*"
288
284
  STARS = [:*, ?*, -1].freeze
289
- private_constant :STAR_INT, :STARS
290
-
291
- COERCIBLE = ->{ _1.respond_to? :to_sequence_set }
292
- ENUMABLE = ->{ _1.respond_to?(:each) && _1.respond_to?(:empty?) }
293
- private_constant :COERCIBLE, :ENUMABLE
285
+ private_constant :STARS
294
286
 
295
287
  class << self
296
288
 
@@ -325,7 +317,7 @@ module Net
325
317
  # raised.
326
318
  def try_convert(obj)
327
319
  return obj if obj.is_a?(SequenceSet)
328
- return nil unless respond_to?(:to_sequence_set)
320
+ return nil unless obj.respond_to?(:to_sequence_set)
329
321
  obj = obj.to_sequence_set
330
322
  return obj if obj.is_a?(SequenceSet)
331
323
  raise DataFormatError, "invalid object returned from to_sequence_set"
@@ -389,6 +381,10 @@ module Net
389
381
  # Related: #valid_string, #normalized_string, #to_s
390
382
  def string; @string ||= normalized_string if valid? end
391
383
 
384
+ # Returns an array with #normalized_string when valid and an empty array
385
+ # otherwise.
386
+ def deconstruct; valid? ? [normalized_string] : [] end
387
+
392
388
  # Assigns a new string to #string and resets #elements to match. It
393
389
  # cannot be set to an empty string—assign +nil+ or use #clear instead.
394
390
  # The string is validated but not normalized.
@@ -1272,7 +1268,8 @@ module Net
1272
1268
  when *STARS, Integer, Range then [input_to_tuple(obj)]
1273
1269
  when String then str_to_tuples obj
1274
1270
  when SequenceSet then obj.tuples
1275
- when ENUMABLE then obj.flat_map { input_to_tuples _1 }
1271
+ when Set then obj.map { [to_tuple_int(_1)] * 2 }
1272
+ when Array then obj.flat_map { input_to_tuples _1 }
1276
1273
  when nil then []
1277
1274
  else
1278
1275
  raise DataFormatError,
@@ -1285,8 +1282,7 @@ module Net
1285
1282
  # String, Set, Array, or... any type of object.
1286
1283
  def input_try_convert(input)
1287
1284
  SequenceSet.try_convert(input) ||
1288
- # Integer.try_convert(input) || # ruby 3.1+
1289
- input.respond_to?(:to_int) && Integer(input.to_int) ||
1285
+ Integer.try_convert(input) ||
1290
1286
  String.try_convert(input) ||
1291
1287
  input
1292
1288
  end
@@ -1416,12 +1412,11 @@ module Net
1416
1412
  end
1417
1413
 
1418
1414
  def nz_number(num)
1419
- case num
1420
- when Integer, /\A[1-9]\d*\z/ then num = Integer(num)
1421
- else raise DataFormatError, "%p is not a valid nz-number" % [num]
1422
- end
1423
- NumValidator.ensure_nz_number(num)
1424
- num
1415
+ String === num && !/\A[1-9]\d*\z/.match?(num) and
1416
+ raise DataFormatError, "%p is not a valid nz-number" % [num]
1417
+ NumValidator.ensure_nz_number Integer num
1418
+ rescue TypeError # To catch errors from Integer()
1419
+ raise DataFormatError, $!.message
1425
1420
  end
1426
1421
 
1427
1422
  # intentionally defined after the class implementation
data/lib/net/imap.rb CHANGED
@@ -719,7 +719,7 @@ module Net
719
719
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
720
720
  #
721
721
  class IMAP < Protocol
722
- VERSION = "0.4.17"
722
+ VERSION = "0.5.1"
723
723
 
724
724
  # Aliases for supported capabilities, to be used with the #enable command.
725
725
  ENABLE_ALIASES = {
@@ -946,9 +946,6 @@ module Net
946
946
  @sock = tcp_socket(@host, @port)
947
947
  start_tls_session if ssl_ctx
948
948
  start_imap_connection
949
-
950
- # DEPRECATED: to remove in next version
951
- @client_thread = Thread.current
952
949
  end
953
950
 
954
951
  # Returns true after the TLS negotiation has completed and the remote
@@ -956,11 +953,6 @@ module Net
956
953
  # but peer verification was disabled.
957
954
  def tls_verified?; @tls_verified end
958
955
 
959
- def client_thread # :nodoc:
960
- warn "Net::IMAP#client_thread is deprecated and will be removed soon."
961
- @client_thread
962
- end
963
-
964
956
  # Disconnects from the server.
965
957
  #
966
958
  # Related: #logout, #logout!
@@ -1244,6 +1236,9 @@ module Net
1244
1236
  # +SASL-IR+ capability, below). Defaults to the #config value for
1245
1237
  # {sasl_ir}[rdoc-ref:Config#sasl_ir], which defaults to +true+.
1246
1238
  #
1239
+ # The +registry+ kwarg can be used to select the mechanism implementation
1240
+ # from a custom registry. See SASL.authenticator and SASL::Authenticators.
1241
+ #
1247
1242
  # All other arguments are forwarded to the registered SASL authenticator for
1248
1243
  # the requested mechanism. <em>The documentation for each individual
1249
1244
  # mechanism must be consulted for its specific parameters.</em>
@@ -1338,29 +1333,9 @@ module Net
1338
1333
  # Previously cached #capabilities will be cleared when this method
1339
1334
  # completes. If the TaggedResponse to #authenticate includes updated
1340
1335
  # capabilities, they will be cached.
1341
- def authenticate(mechanism, *creds,
1342
- sasl_ir: config.sasl_ir,
1343
- **props, &callback)
1344
- mechanism = mechanism.to_s.tr("_", "-").upcase
1345
- authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
1346
- cmdargs = ["AUTHENTICATE", mechanism]
1347
- if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
1348
- authenticator.respond_to?(:initial_response?) &&
1349
- authenticator.initial_response?
1350
- response = authenticator.process(nil)
1351
- cmdargs << (response.empty? ? "=" : [response].pack("m0"))
1352
- end
1353
- result = send_command_with_continuations(*cmdargs) {|data|
1354
- challenge = data.unpack1("m")
1355
- response = authenticator.process challenge
1356
- [response].pack("m0")
1357
- }
1358
- if authenticator.respond_to?(:done?) && !authenticator.done?
1359
- logout!
1360
- raise SASL::AuthenticationIncomplete, result
1361
- end
1362
- @capabilities = capabilities_from_resp_code result
1363
- result
1336
+ def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback)
1337
+ sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback)
1338
+ .tap { @capabilities = capabilities_from_resp_code _1 }
1364
1339
  end
1365
1340
 
1366
1341
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1380,13 +1355,9 @@ module Net
1380
1355
  # ===== Capabilities
1381
1356
  #
1382
1357
  # An IMAP client MUST NOT call #login when the server advertises the
1383
- # +LOGINDISABLED+ capability.
1384
- #
1385
- # if imap.capability? "LOGINDISABLED"
1386
- # raise "Remote server has disabled the login command"
1387
- # else
1388
- # imap.login username, password
1389
- # end
1358
+ # +LOGINDISABLED+ capability. By default, Net::IMAP will raise a
1359
+ # LoginDisabledError when that capability is present. See
1360
+ # Config#enforce_logindisabled.
1390
1361
  #
1391
1362
  # Server capabilities may change after #starttls, #login, and #authenticate.
1392
1363
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1394,6 +1365,9 @@ module Net
1394
1365
  # ResponseCode.
1395
1366
  #
1396
1367
  def login(user, password)
1368
+ if enforce_logindisabled? && capability?("LOGINDISABLED")
1369
+ raise LoginDisabledError
1370
+ end
1397
1371
  send_command("LOGIN", user, password)
1398
1372
  .tap { @capabilities = capabilities_from_resp_code _1 }
1399
1373
  end
@@ -1950,82 +1924,274 @@ module Net
1950
1924
  # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]].
1951
1925
  def uid_expunge(uid_set)
1952
1926
  synchronize do
1953
- send_command("UID EXPUNGE", MessageSet.new(uid_set))
1927
+ send_command("UID EXPUNGE", SequenceSet.new(uid_set))
1954
1928
  clear_responses("EXPUNGE")
1955
1929
  end
1956
1930
  end
1957
1931
 
1958
- # Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4]
1959
- # to search the mailbox for messages that match the given searching
1960
- # criteria, and returns message sequence numbers. +keys+ can either be a
1961
- # string holding the entire search string, or a single-dimension array of
1962
- # search keywords and arguments.
1932
+ # :call-seq:
1933
+ # search(criteria, charset = nil) -> result
1963
1934
  #
1964
- # Returns a SearchResult object. SearchResult inherits from Array (for
1935
+ # Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4]
1936
+ # to search the mailbox for messages that match the given search +criteria+,
1937
+ # and returns a SearchResult. SearchResult inherits from Array (for
1965
1938
  # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
1966
1939
  # capability has been enabled.
1967
1940
  #
1941
+ # +criteria+ is one or more search keys and their arguments, which may be
1942
+ # provided as an array or a string.
1943
+ # See {"Search criteria"}[rdoc-ref:#search@Search+criteria], below.
1944
+ #
1945
+ # * When +criteria+ is an array, each member is a +SEARCH+ command argument:
1946
+ # * Any SequenceSet sends SequenceSet#valid_string.
1947
+ # These types are converted to SequenceSet for validation and encoding:
1948
+ # * +Set+
1949
+ # * +Range+
1950
+ # * <tt>-1</tt> and +:*+ -- both translate to <tt>*</tt>
1951
+ # * responds to +#to_sequence_set+
1952
+ # * +Array+, when each element is one of the above types, a positive
1953
+ # +Integer+, a sequence-set formatted +String+, or a deeply nested
1954
+ # +Array+ of these same types.
1955
+ # * Any +String+ is sent verbatim when it is a valid \IMAP atom,
1956
+ # and encoded as an \IMAP quoted or literal string otherwise.
1957
+ # * Any other nested +Array+ is encoded as a parenthesized list, to group
1958
+ # multiple search keys (e.g., for use with +OR+ and +NOT+).
1959
+ # * Any other +Integer+ (besides <tt>-1</tt>) will be sent as +#to_s+.
1960
+ # * +Date+ objects will be encoded as an \IMAP date (see ::encode_date).
1961
+ #
1962
+ # * When +criteria+ is a string, it will be sent directly to the server
1963
+ # <em>without any validation or encoding</em>. *WARNING:* This is
1964
+ # vulnerable to injection attacks when external inputs are used.
1965
+ #
1966
+ # +charset+ is the name of the {registered character
1967
+ # set}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
1968
+ # used by strings in the search +criteria+. When +charset+ isn't specified,
1969
+ # either <tt>"US-ASCII"</tt> or <tt>"UTF-8"</tt> is assumed, depending on
1970
+ # the server's capabilities. +charset+ may be sent inside +criteria+
1971
+ # instead of as a separate argument.
1972
+ #
1968
1973
  # Related: #uid_search
1969
1974
  #
1970
- # ===== Search criteria
1975
+ # ===== For example:
1976
+ #
1977
+ # p imap.search(["SUBJECT", "hello", "NOT", "SEEN"])
1978
+ # #=> [1, 6, 7, 8]
1979
+ #
1980
+ # The following searches send the exact same command to the server:
1981
+ #
1982
+ # # criteria array, charset arg
1983
+ # imap.search(["OR", "UNSEEN", %w(FLAGGED SUBJECT foo)], "UTF-8")
1984
+ # # criteria string, charset arg
1985
+ # imap.search("OR UNSEEN (FLAGGED SUBJECT foo)", "UTF-8")
1986
+ # # criteria array contains charset arg
1987
+ # imap.search([*%w[CHARSET UTF-8], "OR", "UNSEEN", %w(FLAGGED SUBJECT foo)])
1988
+ # # criteria string contains charset arg
1989
+ # imap.search("CHARSET UTF-8 OR UNSEEN (FLAGGED SUBJECT foo)")
1990
+ #
1991
+ # ===== Search keys
1971
1992
  #
1972
- # For a full list of search criteria,
1993
+ # For full definitions of the standard search +criteria+,
1973
1994
  # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]],
1974
1995
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
1975
1996
  # in addition to documentation for
1976
- # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
1977
- # reported by #capabilities which may define additional search filters, e.g:
1997
+ # any #capabilities which may define additional search filters, such as
1978
1998
  # +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
1979
- # +SAVEDATE+. The following are some common search criteria:
1999
+ # +SAVEDATE+.
2000
+ #
2001
+ # With the exception of <em>sequence-set</em> and <em>parenthesized
2002
+ # list</em>, all search keys are composed of prefix label with zero or more
2003
+ # arguments. The number and type of arguments is specific to each search
2004
+ # key.
2005
+ #
2006
+ # +ALL+::
2007
+ # Matches every message in the mailbox.
2008
+ #
2009
+ # (_search-key_ _search-key_...)::
2010
+ # Combines one or more _search-key_ arguments to match
2011
+ # messages which match all contained search keys. Useful for +OR+, +NOT+,
2012
+ # and other search keys with _search-key_ arguments.
2013
+ #
2014
+ # _Note:_ this search key has no label.
2015
+ #
2016
+ # +OR+ _search-key_ _search-key_::
2017
+ # Matches messages which match either _search-key_ argument.
2018
+ #
2019
+ # +NOT+ _search-key_::
2020
+ # Matches messages which do not match _search-key_.
2021
+ #
2022
+ # _sequence-set_::
2023
+ # Matches messages with message sequence numbers in _sequence-set_.
2024
+ #
2025
+ # _Note:_ this search key has no label.
2026
+ #
2027
+ # <em>+UIDONLY+ must *not* be enabled.</em>
2028
+ # {[RFC9586]}[https://www.rfc-editor.org/rfc/rfc9586.html]
2029
+ #
2030
+ # +UID+ _sequence-set_::
2031
+ # Matches messages with a UID in _sequence-set_.
2032
+ #
2033
+ # +ANSWERED+::
2034
+ # +UNANSWERED+::
2035
+ # Matches messages with or without the <tt>\\Answered</tt> flag.
2036
+ # +DELETED+::
2037
+ # +UNDELETED+::
2038
+ # Matches messages with or without the <tt>\\Deleted</tt> flag.
2039
+ # +DRAFT+::
2040
+ # +UNDRAFT+::
2041
+ # Matches messages with or without the <tt>\\Draft</tt> flag.
2042
+ # +FLAGGED+::
2043
+ # +UNFLAGGED+::
2044
+ # Matches messages with or without the <tt>\\Flagged</tt> flag.
2045
+ # +SEEN+::
2046
+ # +UNSEEN+::
2047
+ # Matches messages with or without the <tt>\\Seen</tt> flag.
2048
+ #
2049
+ # +KEYWORD+ _keyword_::
2050
+ # +UNKEYWORD+ _keyword_::
2051
+ # Matches messages with or without the specified _keyword_.
2052
+ #
2053
+ # +BCC+ _substring_::
2054
+ # Matches when _substring_ is in the envelope's BCC field.
2055
+ # +CC+ _substring_::
2056
+ # Matches when _substring_ is in the envelope's CC field.
2057
+ # +FROM+ _substring_::
2058
+ # Matches when _substring_ is in the envelope's FROM field.
2059
+ # +SUBJECT+ _substring_::
2060
+ # Matches when _substring_ is in the envelope's SUBJECT field.
2061
+ # +TO+ _substring_::
2062
+ # Matches when _substring_ is in the envelope's TO field.
2063
+ #
2064
+ # +HEADER+ _field_ _substring_::
2065
+ # Matches when _substring_ is in the specified header _field_.
2066
+ #
2067
+ # +BODY+ _string_::
2068
+ # Matches when _string_ is in the body of the message.
2069
+ # Does not match on header fields.
2070
+ #
2071
+ # The server _may_ use flexible matching, rather than simple substring
2072
+ # matches. For example, this may use stemming or match only full words.
2073
+ #
2074
+ # +TEXT+ _string_::
2075
+ # Matches when _string_ is in the header or body of the message.
2076
+ #
2077
+ # The server _may_ use flexible matching, rather than simple substring
2078
+ # matches. For example, this may use stemming or match only full words.
2079
+ #
2080
+ # +BEFORE+ _date_::
2081
+ # +ON+ _date_::
2082
+ # +SINCE+ _date_::
2083
+ # Matches when the +INTERNALDATE+ is earlier than, on, or later than
2084
+ # _date_.
2085
+ #
2086
+ # +SENTBEFORE+ _date_::
2087
+ # +SENTON+ _date_::
2088
+ # +SENTSINCE+ _date_::
2089
+ # Matches when the +Date+ header is earlier than, on, or later than _date_.
2090
+ #
2091
+ # +SMALLER+ _bytes_::
2092
+ # +LARGER+ _bytes_::
2093
+ # Matches when +RFC822.SIZE+ is smaller/larger than _bytes_.
2094
+ #
2095
+ # ====== Removed from +IMAP4rev2+
2096
+ #
2097
+ # The <tt>\\Recent</tt> flag has been removed from +IMAP4rev2+. So these
2098
+ # search keys require the +IMAP4rev1+ capability.
1980
2099
  #
1981
- # <message set>:: a set of message sequence numbers. "<tt>,</tt>" indicates
1982
- # an interval, "+:+" indicates a range. For instance,
1983
- # "<tt>2,10:12,15</tt>" means "<tt>2,10,11,12,15</tt>".
2100
+ # +RECENT+::
2101
+ # +UNRECENT+::
2102
+ # Matches messages with or without the <tt>\\Recent</tt> flag.
1984
2103
  #
1985
- # BEFORE <date>:: messages with an internal date strictly before
1986
- # <b><date></b>. The date argument has a format similar
1987
- # to <tt>8-Aug-2002</tt>, and can be formatted using
1988
- # Net::IMAP.format_date.
2104
+ # +NEW+::
2105
+ # Equivalent to <tt>(RECENT UNSEEN)</tt>.
1989
2106
  #
1990
- # BODY <string>:: messages that contain <string> within their body.
2107
+ # ====== Extension search keys
1991
2108
  #
1992
- # CC <string>:: messages containing <string> in their CC field.
2109
+ # The search keys described below are defined by standard \IMAP extensions.
1993
2110
  #
1994
- # FROM <string>:: messages that contain <string> in their FROM field.
2111
+ # +OLDER+ _interval_::
2112
+ # +YOUNGER+ _interval_::
2113
+ # Matches when +INTERNALDATE+ is more/less than _interval_ seconds ago.
1995
2114
  #
1996
- # NEW:: messages with the \Recent, but not the \Seen, flag set.
2115
+ # <em>Requires the +WITHIN+ capability</em>.
2116
+ # {[RFC5032]}[https://www.rfc-editor.org/rfc/rfc5032.html]
1997
2117
  #
1998
- # NOT <search-key>:: negate the following search key.
2118
+ # +ANNOTATION+ _entry_ _attr_ _value_::
2119
+ # Matches messages that have annotations with entries matching _entry_,
2120
+ # attributes matching _attr_, and _value_ in the attribute's values.
1999
2121
  #
2000
- # OR <search-key> <search-key>:: "or" two search keys together.
2122
+ # <em>Requires the +ANNOTATE-EXPERIMENT-1+ capability</em>.
2123
+ # {[RFC5257]}[https://www.rfc-editor.org/rfc/rfc5257.html].
2001
2124
  #
2002
- # ON <date>:: messages with an internal date exactly equal to <date>,
2003
- # which has a format similar to 8-Aug-2002.
2125
+ # +FILTER+ _filter_::
2126
+ # References a _filter_ that is stored on the server and matches all
2127
+ # messages which would be matched by that filter's search criteria.
2004
2128
  #
2005
- # SINCE <date>:: messages with an internal date on or after <date>.
2129
+ # <em>Requires the +FILTERS+ capability</em>.
2130
+ # {[RFC5466]}[https://www.rfc-editor.org/rfc/rfc5466.html#section-3.1]
2006
2131
  #
2007
- # SUBJECT <string>:: messages with <string> in their subject.
2132
+ # +FUZZY+ _search-key_::
2133
+ # Uses fuzzy matching for the specified search key.
2008
2134
  #
2009
- # TO <string>:: messages with <string> in their TO field.
2135
+ # <em>Requires the <tt>SEARCH=FUZZY</tt> capability.</em>
2136
+ # {[RFC6203]}[https://www.rfc-editor.org/rfc/rfc6203.html#section-6].
2010
2137
  #
2011
- # ===== For example:
2138
+ # +MODSEQ+ _modseq_::
2139
+ # Matches when +MODSEQ+ is greater than or equal to _modseq_.
2012
2140
  #
2013
- # p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
2014
- # #=> [1, 6, 7, 8]
2141
+ # <em>Requires the +CONDSTORE+ capability</em>.
2142
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.5].
2143
+ #
2144
+ # +MODSEQ+ _entry_ _entry-type_ _modseq_::
2145
+ # Matches when a specific metadata _entry_ has been updated since
2146
+ # _modseq_.
2147
+ #
2148
+ # For flags, the corresponding _entry_ name is
2149
+ # <tt>"/flags/#{flag_name}"</tt>, where _flag_name_ includes the
2150
+ # <tt>\\</tt> prefix. _entry-type_ can be one of <tt>"shared"</tt>,
2151
+ # <tt>"priv"</tt> (private), or <tt>"all"</tt>.
2152
+ #
2153
+ # <em>Requires the +CONDSTORE+ capability</em>.
2154
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.5].
2155
+ #
2156
+ # +EMAILID+ _objectid_::
2157
+ # +THREADID+ _objectid_::
2158
+ # Matches when +EMAILID+/+THREADID+ is equal to _objectid_
2159
+ # (substring matches are not supported).
2160
+ #
2161
+ # <em>Requires the +OBJECTID+ capability</em>.
2162
+ # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html#section-6]
2163
+ #
2164
+ # +SAVEDATESUPPORTED+::
2165
+ # Matches every message in the mailbox when the mailbox supports the save
2166
+ # date attribute. Otherwise, it matches no messages.
2167
+ #
2168
+ # <em>Requires the +SAVEDATE+ capability</em>.
2169
+ # {[RFC8514]}[https://www.rfc-editor.org/rfc/rfc8514.html#section-4.3]
2170
+ #
2171
+ # +SAVEDBEFORE+ _date_::
2172
+ # +SAVEDON+ _date_::
2173
+ # +SAVEDSINCE+ _date_::
2174
+ # Matches when the save date is earlier than, on, or later than _date_.
2175
+ #
2176
+ # <em>Requires the +SAVEDATE+ capability.</em>
2177
+ # {[RFC8514]}[https://www.rfc-editor.org/rfc/rfc8514.html#section-4.3]
2015
2178
  #
2016
2179
  # ===== Capabilities
2017
2180
  #
2018
- # If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported
2181
+ # If CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html] is supported
2019
2182
  # and enabled for the selected mailbox, a non-empty SearchResult will
2020
2183
  # include a +MODSEQ+ value.
2021
2184
  # imap.select("mbox", condstore: true)
2022
- # result = imap.search(["SUBJECT", "hi there", "not", "new")
2185
+ # result = imap.search(["SUBJECT", "hi there", "not", "new"])
2023
2186
  # #=> Net::IMAP::SearchResult[1, 6, 7, 8, modseq: 5594]
2024
2187
  # result.modseq # => 5594
2025
- def search(keys, charset = nil)
2026
- return search_internal("SEARCH", keys, charset)
2188
+ def search(...)
2189
+ search_internal("SEARCH", ...)
2027
2190
  end
2028
2191
 
2192
+ # :call-seq:
2193
+ # uid_search(criteria, charset = nil) -> result
2194
+ #
2029
2195
  # Sends a {UID SEARCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
2030
2196
  # to search the mailbox for messages that match the given searching
2031
2197
  # criteria, and returns unique identifiers (<tt>UID</tt>s).
@@ -2034,9 +2200,9 @@ module Net
2034
2200
  # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
2035
2201
  # capability has been enabled.
2036
2202
  #
2037
- # See #search for documentation of search criteria.
2038
- def uid_search(keys, charset = nil)
2039
- return search_internal("UID SEARCH", keys, charset)
2203
+ # See #search for documentation of parameters.
2204
+ def uid_search(...)
2205
+ search_internal("UID SEARCH", ...)
2040
2206
  end
2041
2207
 
2042
2208
  # :call-seq:
@@ -2608,7 +2774,7 @@ module Net
2608
2774
  when :raise
2609
2775
  raise ArgumentError, RESPONSES_DEPRECATION_MSG
2610
2776
  when :warn
2611
- warn(RESPONSES_DEPRECATION_MSG, uplevel: 1)
2777
+ warn(RESPONSES_DEPRECATION_MSG, uplevel: 1, category: :deprecated)
2612
2778
  when :frozen_dup
2613
2779
  synchronize {
2614
2780
  responses = @responses.transform_values(&:freeze)
@@ -2957,18 +3123,19 @@ module Net
2957
3123
  end
2958
3124
  end
2959
3125
 
2960
- def search_internal(cmd, keys, charset)
2961
- if keys.instance_of?(String)
2962
- keys = [RawData.new(keys)]
3126
+ def enforce_logindisabled?
3127
+ if config.enforce_logindisabled == :when_capabilities_cached
3128
+ capabilities_cached?
2963
3129
  else
2964
- normalize_searching_criteria(keys)
3130
+ config.enforce_logindisabled
2965
3131
  end
3132
+ end
3133
+
3134
+ def search_internal(cmd, keys, charset = nil)
3135
+ keys = normalize_searching_criteria(keys)
3136
+ args = charset ? ["CHARSET", charset, *keys] : keys
2966
3137
  synchronize do
2967
- if charset
2968
- send_command(cmd, "CHARSET", charset, *keys)
2969
- else
2970
- send_command(cmd, *keys)
2971
- end
3138
+ send_command(cmd, *args)
2972
3139
  clear_responses("SEARCH").last || []
2973
3140
  end
2974
3141
  end
@@ -2990,9 +3157,9 @@ module Net
2990
3157
  synchronize do
2991
3158
  clear_responses("FETCH")
2992
3159
  if mod
2993
- send_command(cmd, MessageSet.new(set), attr, mod)
3160
+ send_command(cmd, SequenceSet.new(set), attr, mod)
2994
3161
  else
2995
- send_command(cmd, MessageSet.new(set), attr)
3162
+ send_command(cmd, SequenceSet.new(set), attr)
2996
3163
  end
2997
3164
  clear_responses("FETCH")
2998
3165
  end
@@ -3000,7 +3167,7 @@ module Net
3000
3167
 
3001
3168
  def store_internal(cmd, set, attr, flags, unchangedsince: nil)
3002
3169
  attr = RawData.new(attr) if attr.instance_of?(String)
3003
- args = [MessageSet.new(set)]
3170
+ args = [SequenceSet.new(set)]
3004
3171
  args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
3005
3172
  args << attr << flags
3006
3173
  synchronize do
@@ -3011,15 +3178,11 @@ module Net
3011
3178
  end
3012
3179
 
3013
3180
  def copy_internal(cmd, set, mailbox)
3014
- send_command(cmd, MessageSet.new(set), mailbox)
3181
+ send_command(cmd, SequenceSet.new(set), mailbox)
3015
3182
  end
3016
3183
 
3017
3184
  def sort_internal(cmd, sort_keys, search_keys, charset)
3018
- if search_keys.instance_of?(String)
3019
- search_keys = [RawData.new(search_keys)]
3020
- else
3021
- normalize_searching_criteria(search_keys)
3022
- end
3185
+ search_keys = normalize_searching_criteria(search_keys)
3023
3186
  synchronize do
3024
3187
  send_command(cmd, sort_keys, charset, *search_keys)
3025
3188
  clear_responses("SORT").last || []
@@ -3027,25 +3190,39 @@ module Net
3027
3190
  end
3028
3191
 
3029
3192
  def thread_internal(cmd, algorithm, search_keys, charset)
3030
- if search_keys.instance_of?(String)
3031
- search_keys = [RawData.new(search_keys)]
3032
- else
3033
- normalize_searching_criteria(search_keys)
3034
- end
3193
+ search_keys = normalize_searching_criteria(search_keys)
3035
3194
  synchronize do
3036
3195
  send_command(cmd, algorithm, charset, *search_keys)
3037
3196
  clear_responses("THREAD").last || []
3038
3197
  end
3039
3198
  end
3040
3199
 
3041
- def normalize_searching_criteria(keys)
3042
- keys.collect! do |i|
3043
- case i
3044
- when -1, Range, Array
3045
- MessageSet.new(i)
3200
+ def normalize_searching_criteria(criteria)
3201
+ return RawData.new(criteria) if criteria.is_a?(String)
3202
+ criteria.map {|i|
3203
+ if coerce_search_arg_to_seqset?(i)
3204
+ SequenceSet[i]
3046
3205
  else
3047
3206
  i
3048
3207
  end
3208
+ }
3209
+ end
3210
+
3211
+ def coerce_search_arg_to_seqset?(obj)
3212
+ case obj
3213
+ when Set, -1, :* then true
3214
+ when Range then true
3215
+ when Array then obj.all? { coerce_search_array_arg_to_seqset? _1 }
3216
+ else obj.respond_to?(:to_sequence_set)
3217
+ end
3218
+ end
3219
+
3220
+ def coerce_search_array_arg_to_seqset?(obj)
3221
+ case obj
3222
+ when Integer then obj.positive? || obj == -1
3223
+ when String then ResponseParser::Patterns::SEQUENCE_SET_STR.match?(obj.b)
3224
+ else
3225
+ coerce_search_arg_to_seqset?(obj)
3049
3226
  end
3050
3227
  end
3051
3228
 
data/net-imap.gemspec CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.summary = %q{Ruby client api for Internet Message Access Protocol}
17
17
  spec.description = %q{Ruby client api for Internet Message Access Protocol}
18
18
  spec.homepage = "https://github.com/ruby/net-imap"
19
- spec.required_ruby_version = Gem::Requirement.new(">= 2.7.3")
19
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0")
20
20
  spec.licenses = ["Ruby", "BSD-2-Clause"]
21
21
 
22
22
  spec.metadata["homepage_uri"] = spec.homepage
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set" unless defined?(::Set)
4
+
3
5
  # Generator for stringprep regexps.
4
6
  #
5
7
  # Combines Unicode character classes with generated tables. Generated regexps