net-imap 0.4.17 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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