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.
- checksums.yaml +4 -4
- data/Gemfile +7 -1
- data/lib/net/imap/authenticators.rb +2 -2
- data/lib/net/imap/command_data.rb +11 -0
- data/lib/net/imap/config.rb +36 -10
- data/lib/net/imap/data_encoding.rb +3 -3
- data/lib/net/imap/deprecated_client_options.rb +6 -3
- data/lib/net/imap/errors.rb +6 -0
- data/lib/net/imap/response_data.rb +6 -93
- data/lib/net/imap/response_parser.rb +5 -17
- data/lib/net/imap/sasl/authentication_exchange.rb +52 -20
- data/lib/net/imap/sasl/authenticators.rb +8 -4
- data/lib/net/imap/sasl/client_adapter.rb +77 -26
- data/lib/net/imap/sasl/cram_md5_authenticator.rb +1 -1
- data/lib/net/imap/sasl/digest_md5_authenticator.rb +213 -51
- data/lib/net/imap/sasl/login_authenticator.rb +2 -1
- data/lib/net/imap/sasl/protocol_adapters.rb +60 -4
- data/lib/net/imap/sasl.rb +6 -3
- data/lib/net/imap/sasl_adapter.rb +0 -1
- data/lib/net/imap/sequence_set.rb +18 -23
- data/lib/net/imap.rb +285 -108
- data/net-imap.gemspec +1 -1
- data/rakelib/string_prep_tables_generator.rb +2 -0
- metadata +7 -7
@@ -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,
|
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 :
|
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
|
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
|
-
|
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
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
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.
|
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(
|
1342
|
-
|
1343
|
-
|
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
|
-
#
|
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",
|
1927
|
+
send_command("UID EXPUNGE", SequenceSet.new(uid_set))
|
1954
1928
|
clear_responses("EXPUNGE")
|
1955
1929
|
end
|
1956
1930
|
end
|
1957
1931
|
|
1958
|
-
#
|
1959
|
-
#
|
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
|
-
#
|
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
|
-
# =====
|
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
|
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
|
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+.
|
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
|
-
#
|
1982
|
-
#
|
1983
|
-
#
|
2100
|
+
# +RECENT+::
|
2101
|
+
# +UNRECENT+::
|
2102
|
+
# Matches messages with or without the <tt>\\Recent</tt> flag.
|
1984
2103
|
#
|
1985
|
-
#
|
1986
|
-
#
|
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
|
-
#
|
2107
|
+
# ====== Extension search keys
|
1991
2108
|
#
|
1992
|
-
#
|
2109
|
+
# The search keys described below are defined by standard \IMAP extensions.
|
1993
2110
|
#
|
1994
|
-
#
|
2111
|
+
# +OLDER+ _interval_::
|
2112
|
+
# +YOUNGER+ _interval_::
|
2113
|
+
# Matches when +INTERNALDATE+ is more/less than _interval_ seconds ago.
|
1995
2114
|
#
|
1996
|
-
#
|
2115
|
+
# <em>Requires the +WITHIN+ capability</em>.
|
2116
|
+
# {[RFC5032]}[https://www.rfc-editor.org/rfc/rfc5032.html]
|
1997
2117
|
#
|
1998
|
-
#
|
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
|
-
#
|
2122
|
+
# <em>Requires the +ANNOTATE-EXPERIMENT-1+ capability</em>.
|
2123
|
+
# {[RFC5257]}[https://www.rfc-editor.org/rfc/rfc5257.html].
|
2001
2124
|
#
|
2002
|
-
#
|
2003
|
-
#
|
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
|
-
#
|
2129
|
+
# <em>Requires the +FILTERS+ capability</em>.
|
2130
|
+
# {[RFC5466]}[https://www.rfc-editor.org/rfc/rfc5466.html#section-3.1]
|
2006
2131
|
#
|
2007
|
-
#
|
2132
|
+
# +FUZZY+ _search-key_::
|
2133
|
+
# Uses fuzzy matching for the specified search key.
|
2008
2134
|
#
|
2009
|
-
#
|
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
|
-
#
|
2138
|
+
# +MODSEQ+ _modseq_::
|
2139
|
+
# Matches when +MODSEQ+ is greater than or equal to _modseq_.
|
2012
2140
|
#
|
2013
|
-
#
|
2014
|
-
#
|
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
|
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(
|
2026
|
-
|
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
|
2038
|
-
def uid_search(
|
2039
|
-
|
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
|
2961
|
-
if
|
2962
|
-
|
3126
|
+
def enforce_logindisabled?
|
3127
|
+
if config.enforce_logindisabled == :when_capabilities_cached
|
3128
|
+
capabilities_cached?
|
2963
3129
|
else
|
2964
|
-
|
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
|
-
|
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,
|
3160
|
+
send_command(cmd, SequenceSet.new(set), attr, mod)
|
2994
3161
|
else
|
2995
|
-
send_command(cmd,
|
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 = [
|
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,
|
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
|
-
|
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
|
-
|
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(
|
3042
|
-
|
3043
|
-
|
3044
|
-
|
3045
|
-
|
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(">=
|
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
|