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

data/lib/net/imap.rb CHANGED
@@ -288,6 +288,8 @@ module Net
288
288
  # pre-authenticated connection.
289
289
  # - #responses: Yields unhandled UntaggedResponse#data and <em>non-+nil+</em>
290
290
  # ResponseCode#data.
291
+ # - #extract_responses: Removes and returns the responses for which the block
292
+ # returns a true value.
291
293
  # - #clear_responses: Deletes unhandled data from #responses and returns it.
292
294
  # - #add_response_handler: Add a block to be called inside the receiver thread
293
295
  # with every server response.
@@ -717,7 +719,7 @@ module Net
717
719
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
718
720
  #
719
721
  class IMAP < Protocol
720
- VERSION = "0.4.12"
722
+ VERSION = "0.5.1"
721
723
 
722
724
  # Aliases for supported capabilities, to be used with the #enable command.
723
725
  ENABLE_ALIASES = {
@@ -735,14 +737,15 @@ module Net
735
737
  include SSL
736
738
  end
737
739
 
738
- # Returns the debug mode.
739
- def self.debug
740
- return @@debug
741
- end
740
+ # Returns the global Config object
741
+ def self.config; Config.global end
742
+
743
+ # Returns the global debug mode.
744
+ def self.debug; config.debug end
742
745
 
743
- # Sets the debug mode.
746
+ # Sets the global debug mode.
744
747
  def self.debug=(val)
745
- return @@debug = val
748
+ config.debug = val
746
749
  end
747
750
 
748
751
  # The default port for IMAP connections, port 143
@@ -764,13 +767,19 @@ module Net
764
767
  # Returns the initial greeting the server, an UntaggedResponse.
765
768
  attr_reader :greeting
766
769
 
770
+ # The client configuration. See Net::IMAP::Config.
771
+ #
772
+ # By default, the client's local configuration inherits from the global
773
+ # Net::IMAP.config.
774
+ attr_reader :config
775
+
767
776
  # Seconds to wait until a connection is opened.
768
777
  # If the IMAP object cannot open a connection within this time,
769
778
  # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
770
- attr_reader :open_timeout
779
+ def open_timeout; config.open_timeout end
771
780
 
772
781
  # Seconds to wait until an IDLE response is received.
773
- attr_reader :idle_response_timeout
782
+ def idle_response_timeout; config.idle_response_timeout end
774
783
 
775
784
  # The hostname this client connected to
776
785
  attr_reader :host
@@ -809,14 +818,40 @@ module Net
809
818
  # If +ssl+ is a hash, it's passed to
810
819
  # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
811
820
  # the keys are names of attribute assignment methods on
812
- # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
821
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]. For example:
822
+ #
823
+ # [{ca_file}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-ca_file]]
824
+ # The path to a file containing a PEM-format CA certificate.
825
+ # [{ca_path}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-ca_path]]
826
+ # The path to a directory containing CA certificates in PEM format.
827
+ # [{min_version}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-min_version-3D]]
828
+ # Sets the lower bound on the supported SSL/TLS protocol version. Set to
829
+ # an +OpenSSL+ constant such as +OpenSSL::SSL::TLS1_2_VERSION+,
830
+ # [{verify_mode}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-verify_mode]]
831
+ # SSL session verification mode. Valid modes include
832
+ # +OpenSSL::SSL::VERIFY_PEER+ and +OpenSSL::SSL::VERIFY_NONE+.
833
+ #
834
+ # See {OpenSSL::SSL::SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] for other valid SSL context params.
813
835
  #
814
- # [open_timeout]
815
- # Seconds to wait until a connection is opened
816
- # [idle_response_timeout]
817
- # Seconds to wait until an IDLE response is received
836
+ # See DeprecatedClientOptions.new for deprecated SSL arguments.
818
837
  #
819
- # See DeprecatedClientOptions.new for deprecated arguments.
838
+ # [config]
839
+ # A Net::IMAP::Config object to use as the basis for #config. By default,
840
+ # the global Net::IMAP.config is used.
841
+ #
842
+ # >>>
843
+ # *NOTE:* +config+ does not set #config directly---it sets the _parent_
844
+ # config for inheritance. Every client creates its own unique #config.
845
+ #
846
+ # All other keyword arguments are forwarded to Net::IMAP::Config.new, to
847
+ # initialize the client's #config. For example:
848
+ #
849
+ # [{open_timeout}[rdoc-ref:Config#open_timeout]]
850
+ # Seconds to wait until a connection is opened
851
+ # [{idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]]
852
+ # Seconds to wait until an IDLE response is received
853
+ #
854
+ # See Net::IMAP::Config for other valid options.
820
855
  #
821
856
  # ==== Examples
822
857
  #
@@ -872,13 +907,12 @@ module Net
872
907
  # Connected to the host successfully, but it immediately said goodbye.
873
908
  #
874
909
  def initialize(host, port: nil, ssl: nil,
875
- open_timeout: 30, idle_response_timeout: 5)
910
+ config: Config.global, **config_options)
876
911
  super()
877
912
  # Config options
878
913
  @host = host
914
+ @config = Config.new(config, **config_options)
879
915
  @port = port || (ssl ? SSL_PORT : PORT)
880
- @open_timeout = Integer(open_timeout)
881
- @idle_response_timeout = Integer(idle_response_timeout)
882
916
  @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
883
917
 
884
918
  # Basic Client State
@@ -889,7 +923,7 @@ module Net
889
923
  @capabilities = nil
890
924
 
891
925
  # Client Protocol Receiver
892
- @parser = ResponseParser.new
926
+ @parser = ResponseParser.new(config: @config)
893
927
  @responses = Hash.new {|h, k| h[k] = [] }
894
928
  @response_handlers = []
895
929
  @receiver_thread = nil
@@ -912,9 +946,6 @@ module Net
912
946
  @sock = tcp_socket(@host, @port)
913
947
  start_tls_session if ssl_ctx
914
948
  start_imap_connection
915
-
916
- # DEPRECATED: to remove in next version
917
- @client_thread = Thread.current
918
949
  end
919
950
 
920
951
  # Returns true after the TLS negotiation has completed and the remote
@@ -922,11 +953,6 @@ module Net
922
953
  # but peer verification was disabled.
923
954
  def tls_verified?; @tls_verified end
924
955
 
925
- def client_thread # :nodoc:
926
- warn "Net::IMAP#client_thread is deprecated and will be removed soon."
927
- @client_thread
928
- end
929
-
930
956
  # Disconnects from the server.
931
957
  #
932
958
  # Related: #logout, #logout!
@@ -1198,7 +1224,7 @@ module Net
1198
1224
  end
1199
1225
 
1200
1226
  # :call-seq:
1201
- # authenticate(mechanism, *, sasl_ir: true, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp
1227
+ # authenticate(mechanism, *, sasl_ir: config.sasl_ir, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp
1202
1228
  #
1203
1229
  # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
1204
1230
  # to authenticate the client. If successful, the connection enters the
@@ -1207,7 +1233,11 @@ module Net
1207
1233
  # +mechanism+ is the name of the \SASL authentication mechanism to be used.
1208
1234
  #
1209
1235
  # +sasl_ir+ allows or disallows sending an "initial response" (see the
1210
- # +SASL-IR+ capability, below).
1236
+ # +SASL-IR+ capability, below). Defaults to the #config value for
1237
+ # {sasl_ir}[rdoc-ref:Config#sasl_ir], which defaults to +true+.
1238
+ #
1239
+ # The +registry+ kwarg can be used to select the mechanism implementation
1240
+ # from a custom registry. See SASL.authenticator and SASL::Authenticators.
1211
1241
  #
1212
1242
  # All other arguments are forwarded to the registered SASL authenticator for
1213
1243
  # the requested mechanism. <em>The documentation for each individual
@@ -1303,27 +1333,9 @@ module Net
1303
1333
  # Previously cached #capabilities will be cleared when this method
1304
1334
  # completes. If the TaggedResponse to #authenticate includes updated
1305
1335
  # capabilities, they will be cached.
1306
- def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback)
1307
- mechanism = mechanism.to_s.tr("_", "-").upcase
1308
- authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
1309
- cmdargs = ["AUTHENTICATE", mechanism]
1310
- if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
1311
- authenticator.respond_to?(:initial_response?) &&
1312
- authenticator.initial_response?
1313
- response = authenticator.process(nil)
1314
- cmdargs << (response.empty? ? "=" : [response].pack("m0"))
1315
- end
1316
- result = send_command_with_continuations(*cmdargs) {|data|
1317
- challenge = data.unpack1("m")
1318
- response = authenticator.process challenge
1319
- [response].pack("m0")
1320
- }
1321
- if authenticator.respond_to?(:done?) && !authenticator.done?
1322
- logout!
1323
- raise SASL::AuthenticationIncomplete, result
1324
- end
1325
- @capabilities = capabilities_from_resp_code result
1326
- 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 }
1327
1339
  end
1328
1340
 
1329
1341
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1343,13 +1355,9 @@ module Net
1343
1355
  # ===== Capabilities
1344
1356
  #
1345
1357
  # An IMAP client MUST NOT call #login when the server advertises the
1346
- # +LOGINDISABLED+ capability.
1347
- #
1348
- # if imap.capability? "LOGINDISABLED"
1349
- # raise "Remote server has disabled the login command"
1350
- # else
1351
- # imap.login username, password
1352
- # end
1358
+ # +LOGINDISABLED+ capability. By default, Net::IMAP will raise a
1359
+ # LoginDisabledError when that capability is present. See
1360
+ # Config#enforce_logindisabled.
1353
1361
  #
1354
1362
  # Server capabilities may change after #starttls, #login, and #authenticate.
1355
1363
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1357,6 +1365,9 @@ module Net
1357
1365
  # ResponseCode.
1358
1366
  #
1359
1367
  def login(user, password)
1368
+ if enforce_logindisabled? && capability?("LOGINDISABLED")
1369
+ raise LoginDisabledError
1370
+ end
1360
1371
  send_command("LOGIN", user, password)
1361
1372
  .tap { @capabilities = capabilities_from_resp_code _1 }
1362
1373
  end
@@ -1913,82 +1924,274 @@ module Net
1913
1924
  # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]].
1914
1925
  def uid_expunge(uid_set)
1915
1926
  synchronize do
1916
- send_command("UID EXPUNGE", MessageSet.new(uid_set))
1927
+ send_command("UID EXPUNGE", SequenceSet.new(uid_set))
1917
1928
  clear_responses("EXPUNGE")
1918
1929
  end
1919
1930
  end
1920
1931
 
1921
- # Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4]
1922
- # to search the mailbox for messages that match the given searching
1923
- # criteria, and returns message sequence numbers. +keys+ can either be a
1924
- # string holding the entire search string, or a single-dimension array of
1925
- # search keywords and arguments.
1932
+ # :call-seq:
1933
+ # search(criteria, charset = nil) -> result
1926
1934
  #
1927
- # 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
1928
1938
  # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
1929
1939
  # capability has been enabled.
1930
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
+ #
1931
1973
  # Related: #uid_search
1932
1974
  #
1933
- # ===== 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)")
1934
1990
  #
1935
- # For a full list of search criteria,
1991
+ # ===== Search keys
1992
+ #
1993
+ # For full definitions of the standard search +criteria+,
1936
1994
  # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]],
1937
1995
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
1938
1996
  # in addition to documentation for
1939
- # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
1940
- # reported by #capabilities which may define additional search filters, e.g:
1997
+ # any #capabilities which may define additional search filters, such as
1941
1998
  # +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
1942
- # +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.
1943
2099
  #
1944
- # <message set>:: a set of message sequence numbers. "<tt>,</tt>" indicates
1945
- # an interval, "+:+" indicates a range. For instance,
1946
- # "<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.
1947
2103
  #
1948
- # BEFORE <date>:: messages with an internal date strictly before
1949
- # <b><date></b>. The date argument has a format similar
1950
- # to <tt>8-Aug-2002</tt>, and can be formatted using
1951
- # Net::IMAP.format_date.
2104
+ # +NEW+::
2105
+ # Equivalent to <tt>(RECENT UNSEEN)</tt>.
1952
2106
  #
1953
- # BODY <string>:: messages that contain <string> within their body.
2107
+ # ====== Extension search keys
1954
2108
  #
1955
- # CC <string>:: messages containing <string> in their CC field.
2109
+ # The search keys described below are defined by standard \IMAP extensions.
1956
2110
  #
1957
- # 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.
1958
2114
  #
1959
- # 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]
1960
2117
  #
1961
- # 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.
1962
2121
  #
1963
- # 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].
1964
2124
  #
1965
- # ON <date>:: messages with an internal date exactly equal to <date>,
1966
- # 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.
1967
2128
  #
1968
- # 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]
1969
2131
  #
1970
- # SUBJECT <string>:: messages with <string> in their subject.
2132
+ # +FUZZY+ _search-key_::
2133
+ # Uses fuzzy matching for the specified search key.
1971
2134
  #
1972
- # 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].
1973
2137
  #
1974
- # ===== For example:
2138
+ # +MODSEQ+ _modseq_::
2139
+ # Matches when +MODSEQ+ is greater than or equal to _modseq_.
1975
2140
  #
1976
- # p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
1977
- # #=> [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]
1978
2178
  #
1979
2179
  # ===== Capabilities
1980
2180
  #
1981
- # 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
1982
2182
  # and enabled for the selected mailbox, a non-empty SearchResult will
1983
2183
  # include a +MODSEQ+ value.
1984
2184
  # imap.select("mbox", condstore: true)
1985
- # result = imap.search(["SUBJECT", "hi there", "not", "new")
2185
+ # result = imap.search(["SUBJECT", "hi there", "not", "new"])
1986
2186
  # #=> Net::IMAP::SearchResult[1, 6, 7, 8, modseq: 5594]
1987
2187
  # result.modseq # => 5594
1988
- def search(keys, charset = nil)
1989
- return search_internal("SEARCH", keys, charset)
2188
+ def search(...)
2189
+ search_internal("SEARCH", ...)
1990
2190
  end
1991
2191
 
2192
+ # :call-seq:
2193
+ # uid_search(criteria, charset = nil) -> result
2194
+ #
1992
2195
  # Sends a {UID SEARCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
1993
2196
  # to search the mailbox for messages that match the given searching
1994
2197
  # criteria, and returns unique identifiers (<tt>UID</tt>s).
@@ -1997,9 +2200,9 @@ module Net
1997
2200
  # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
1998
2201
  # capability has been enabled.
1999
2202
  #
2000
- # See #search for documentation of search criteria.
2001
- def uid_search(keys, charset = nil)
2002
- return search_internal("UID SEARCH", keys, charset)
2203
+ # See #search for documentation of parameters.
2204
+ def uid_search(...)
2205
+ search_internal("UID SEARCH", ...)
2003
2206
  end
2004
2207
 
2005
2208
  # :call-seq:
@@ -2397,11 +2600,17 @@ module Net
2397
2600
  # checks the connection for each 60 seconds.
2398
2601
  #
2399
2602
  # loop do
2400
- # imap.idle(60) do |res|
2401
- # ...
2603
+ # imap.idle(60) do |response|
2604
+ # do_something_with(response)
2605
+ # imap.idle_done if some_condition?(response)
2402
2606
  # end
2403
2607
  # end
2404
2608
  #
2609
+ # Returns the server's response to indicate the IDLE state has ended.
2610
+ # Returns +nil+ if the server does not respond to #idle_done within
2611
+ # {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]
2612
+ # seconds.
2613
+ #
2405
2614
  # Related: #idle_done, #noop, #check
2406
2615
  #
2407
2616
  # ===== Capabilities
@@ -2429,7 +2638,7 @@ module Net
2429
2638
  unless @receiver_thread_terminating
2430
2639
  remove_response_handler(response_handler)
2431
2640
  put_string("DONE#{CRLF}")
2432
- response = get_tagged_response(tag, "IDLE", @idle_response_timeout)
2641
+ response = get_tagged_response(tag, "IDLE", idle_response_timeout)
2433
2642
  end
2434
2643
  end
2435
2644
  end
@@ -2437,7 +2646,11 @@ module Net
2437
2646
  return response
2438
2647
  end
2439
2648
 
2440
- # Leaves IDLE.
2649
+ # Leaves IDLE, allowing #idle to return.
2650
+ #
2651
+ # If the server does not respond within
2652
+ # {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]
2653
+ # seconds, #idle will return +nil+.
2441
2654
  #
2442
2655
  # Related: #idle
2443
2656
  def idle_done
@@ -2449,40 +2662,98 @@ module Net
2449
2662
  end
2450
2663
  end
2451
2664
 
2665
+ RESPONSES_DEPRECATION_MSG =
2666
+ "Pass a type or block to #responses, " \
2667
+ "set config.responses_without_block to :frozen_dup " \
2668
+ "or :silence_deprecation_warning, " \
2669
+ "or use #extract_responses or #clear_responses."
2670
+ private_constant :RESPONSES_DEPRECATION_MSG
2671
+
2452
2672
  # :call-seq:
2673
+ # responses -> hash of {String => Array} (see config.responses_without_block)
2674
+ # responses(type) -> frozen array
2453
2675
  # responses {|hash| ...} -> block result
2454
2676
  # responses(type) {|array| ...} -> block result
2455
2677
  #
2456
- # Yields unhandled responses and returns the result of the block.
2678
+ # Yields or returns unhandled server responses. Unhandled responses are
2679
+ # stored in a hash, with arrays of UntaggedResponse#data keyed by
2680
+ # UntaggedResponse#name and <em>non-+nil+</em> untagged ResponseCode#data
2681
+ # keyed by ResponseCode#name.
2682
+ #
2683
+ # When a block is given, yields unhandled responses and returns the block's
2684
+ # result. Without a block, returns the unhandled responses.
2685
+ #
2686
+ # [With +type+]
2687
+ # Yield or return only the array of responses for that +type+.
2688
+ # When no block is given, the returned array is a frozen copy.
2689
+ # [Without +type+]
2690
+ # Yield or return the entire responses hash.
2691
+ #
2692
+ # When no block is given, the behavior is determined by
2693
+ # Config#responses_without_block:
2694
+ # >>>
2695
+ # [+:silence_deprecation_warning+ <em>(original behavior)</em>]
2696
+ # Returns the mutable responses hash (without any warnings).
2697
+ # <em>This is not thread-safe.</em>
2698
+ #
2699
+ # [+:warn+ <em>(default since +v0.5+)</em>]
2700
+ # Prints a warning and returns the mutable responses hash.
2701
+ # <em>This is not thread-safe.</em>
2457
2702
  #
2458
- # Unhandled responses are stored in a hash, with arrays of
2459
- # <em>non-+nil+</em> UntaggedResponse#data keyed by UntaggedResponse#name
2460
- # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
2461
- # yield the entire responses hash. Call with +type+ to yield only the array
2462
- # of responses for that type.
2703
+ # [+:frozen_dup+ <em>(planned default for +v0.6+)</em>]
2704
+ # Returns a frozen copy of the unhandled responses hash, with frozen
2705
+ # array values.
2706
+ #
2707
+ # [+:raise+]
2708
+ # Raise an +ArgumentError+ with the deprecation warning.
2463
2709
  #
2464
2710
  # For example:
2465
2711
  #
2466
2712
  # imap.select("inbox")
2467
- # p imap.responses("EXISTS", &:last)
2713
+ # p imap.responses("EXISTS").last
2468
2714
  # #=> 2
2715
+ # p imap.responses("UIDNEXT", &:last)
2716
+ # #=> 123456
2469
2717
  # p imap.responses("UIDVALIDITY", &:last)
2470
2718
  # #=> 968263756
2719
+ # p imap.responses {|responses|
2720
+ # {
2721
+ # exists: responses.delete("EXISTS").last,
2722
+ # uidnext: responses.delete("UIDNEXT").last,
2723
+ # uidvalidity: responses.delete("UIDVALIDITY").last,
2724
+ # }
2725
+ # }
2726
+ # #=> {:exists=>2, :uidnext=>123456, :uidvalidity=>968263756}
2727
+ # # "EXISTS", "UIDNEXT", and "UIDVALIDITY" have been removed:
2728
+ # p imap.responses(&:keys)
2729
+ # #=> ["FLAGS", "OK", "PERMANENTFLAGS", "RECENT", "HIGHESTMODSEQ"]
2730
+ #
2731
+ # Related: #extract_responses, #clear_responses, #response_handlers, #greeting
2471
2732
  #
2733
+ # ===== Thread safety
2472
2734
  # >>>
2473
2735
  # *Note:* Access to the responses hash is synchronized for thread-safety.
2474
2736
  # The receiver thread and response_handlers cannot process new responses
2475
2737
  # until the block completes. Accessing either the response hash or its
2476
- # response type arrays outside of the block is unsafe.
2738
+ # response type arrays outside of the block is unsafe. They can be safely
2739
+ # updated inside the block. Consider using #clear_responses or
2740
+ # #extract_responses instead.
2477
2741
  #
2478
- # Calling without a block is unsafe and deprecated. Future releases will
2479
- # raise ArgumentError unless a block is given.
2742
+ # Net::IMAP will add and remove responses from the responses hash and its
2743
+ # array values, in the calling threads for commands and in the receiver
2744
+ # thread, but will not modify any responses after adding them to the
2745
+ # responses hash.
2746
+ #
2747
+ # ===== Clearing responses
2480
2748
  #
2481
2749
  # Previously unhandled responses are automatically cleared before entering a
2482
2750
  # mailbox with #select or #examine. Long-lived connections can receive many
2483
2751
  # unhandled server responses, which must be pruned or they will continually
2484
2752
  # consume more memory. Update or clear the responses hash or arrays inside
2485
- # the block, or use #clear_responses.
2753
+ # the block, or remove responses with #extract_responses, #clear_responses,
2754
+ # or #add_response_handler.
2755
+ #
2756
+ # ===== Missing responses
2486
2757
  #
2487
2758
  # Only non-+nil+ data is stored. Many important response codes have no data
2488
2759
  # of their own, but are used as "tags" on the ResponseText object they are
@@ -2493,15 +2764,25 @@ module Net
2493
2764
  # ResponseCode#data on tagged responses. Although some command methods do
2494
2765
  # return the TaggedResponse directly, #add_response_handler must be used to
2495
2766
  # handle all response codes.
2496
- #
2497
- # Related: #clear_responses, #response_handlers, #greeting
2498
2767
  def responses(type = nil)
2499
2768
  if block_given?
2500
2769
  synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
2501
2770
  elsif type
2502
- raise ArgumentError, "Pass a block or use #clear_responses"
2771
+ synchronize { @responses[type.to_s.upcase].dup.freeze }
2503
2772
  else
2504
- # warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1)
2773
+ case config.responses_without_block
2774
+ when :raise
2775
+ raise ArgumentError, RESPONSES_DEPRECATION_MSG
2776
+ when :warn
2777
+ warn(RESPONSES_DEPRECATION_MSG, uplevel: 1, category: :deprecated)
2778
+ when :frozen_dup
2779
+ synchronize {
2780
+ responses = @responses.transform_values(&:freeze)
2781
+ responses.default_proc = nil
2782
+ responses.default = [].freeze
2783
+ return responses.freeze
2784
+ }
2785
+ end
2505
2786
  @responses
2506
2787
  end
2507
2788
  end
@@ -2516,7 +2797,7 @@ module Net
2516
2797
  # Clearing responses is synchronized with other threads. The lock is
2517
2798
  # released before returning.
2518
2799
  #
2519
- # Related: #responses, #response_handlers
2800
+ # Related: #extract_responses, #responses, #response_handlers
2520
2801
  def clear_responses(type = nil)
2521
2802
  synchronize {
2522
2803
  if type
@@ -2530,6 +2811,30 @@ module Net
2530
2811
  .freeze
2531
2812
  end
2532
2813
 
2814
+ # :call-seq:
2815
+ # extract_responses(type) {|response| ... } -> array
2816
+ #
2817
+ # Yields all of the unhandled #responses for a single response +type+.
2818
+ # Removes and returns the responses for which the block returns a true
2819
+ # value.
2820
+ #
2821
+ # Extracting responses is synchronized with other threads. The lock is
2822
+ # released before returning.
2823
+ #
2824
+ # Related: #responses, #clear_responses
2825
+ def extract_responses(type)
2826
+ type = String.try_convert(type) or
2827
+ raise ArgumentError, "type must be a string"
2828
+ raise ArgumentError, "must provide a block" unless block_given?
2829
+ extracted = []
2830
+ responses(type) do |all|
2831
+ all.reject! do |response|
2832
+ extracted << response if yield response
2833
+ end
2834
+ end
2835
+ extracted
2836
+ end
2837
+
2533
2838
  # Returns all response handlers, including those that are added internally
2534
2839
  # by commands. Each response handler will be called with every new
2535
2840
  # UntaggedResponse, TaggedResponse, and ContinuationRequest.
@@ -2582,8 +2887,6 @@ module Net
2582
2887
  PORT = 143 # :nodoc:
2583
2888
  SSL_PORT = 993 # :nodoc:
2584
2889
 
2585
- @@debug = false
2586
-
2587
2890
  def start_imap_connection
2588
2891
  @greeting = get_server_greeting
2589
2892
  @capabilities = capabilities_from_resp_code @greeting
@@ -2611,12 +2914,12 @@ module Net
2611
2914
  end
2612
2915
 
2613
2916
  def tcp_socket(host, port)
2614
- s = Socket.tcp(host, port, :connect_timeout => @open_timeout)
2917
+ s = Socket.tcp(host, port, :connect_timeout => open_timeout)
2615
2918
  s.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, true)
2616
2919
  s
2617
2920
  rescue Errno::ETIMEDOUT
2618
2921
  raise Net::OpenTimeout, "Timeout to open TCP connection to " +
2619
- "#{host}:#{port} (exceeds #{@open_timeout} seconds)"
2922
+ "#{host}:#{port} (exceeds #{open_timeout} seconds)"
2620
2923
  end
2621
2924
 
2622
2925
  def receive_responses
@@ -2728,7 +3031,7 @@ module Net
2728
3031
  end
2729
3032
  end
2730
3033
  return nil if buff.length == 0
2731
- if @@debug
3034
+ if config.debug?
2732
3035
  $stderr.print(buff.gsub(/^/n, "S: "))
2733
3036
  end
2734
3037
  return @parser.parse(buff)
@@ -2807,7 +3110,7 @@ module Net
2807
3110
 
2808
3111
  def put_string(str)
2809
3112
  @sock.print(str)
2810
- if @@debug
3113
+ if config.debug?
2811
3114
  if @debug_output_bol
2812
3115
  $stderr.print("C: ")
2813
3116
  end
@@ -2820,18 +3123,19 @@ module Net
2820
3123
  end
2821
3124
  end
2822
3125
 
2823
- def search_internal(cmd, keys, charset)
2824
- if keys.instance_of?(String)
2825
- keys = [RawData.new(keys)]
3126
+ def enforce_logindisabled?
3127
+ if config.enforce_logindisabled == :when_capabilities_cached
3128
+ capabilities_cached?
2826
3129
  else
2827
- normalize_searching_criteria(keys)
3130
+ config.enforce_logindisabled
2828
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
2829
3137
  synchronize do
2830
- if charset
2831
- send_command(cmd, "CHARSET", charset, *keys)
2832
- else
2833
- send_command(cmd, *keys)
2834
- end
3138
+ send_command(cmd, *args)
2835
3139
  clear_responses("SEARCH").last || []
2836
3140
  end
2837
3141
  end
@@ -2853,9 +3157,9 @@ module Net
2853
3157
  synchronize do
2854
3158
  clear_responses("FETCH")
2855
3159
  if mod
2856
- send_command(cmd, MessageSet.new(set), attr, mod)
3160
+ send_command(cmd, SequenceSet.new(set), attr, mod)
2857
3161
  else
2858
- send_command(cmd, MessageSet.new(set), attr)
3162
+ send_command(cmd, SequenceSet.new(set), attr)
2859
3163
  end
2860
3164
  clear_responses("FETCH")
2861
3165
  end
@@ -2863,7 +3167,7 @@ module Net
2863
3167
 
2864
3168
  def store_internal(cmd, set, attr, flags, unchangedsince: nil)
2865
3169
  attr = RawData.new(attr) if attr.instance_of?(String)
2866
- args = [MessageSet.new(set)]
3170
+ args = [SequenceSet.new(set)]
2867
3171
  args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
2868
3172
  args << attr << flags
2869
3173
  synchronize do
@@ -2874,15 +3178,11 @@ module Net
2874
3178
  end
2875
3179
 
2876
3180
  def copy_internal(cmd, set, mailbox)
2877
- send_command(cmd, MessageSet.new(set), mailbox)
3181
+ send_command(cmd, SequenceSet.new(set), mailbox)
2878
3182
  end
2879
3183
 
2880
3184
  def sort_internal(cmd, sort_keys, search_keys, charset)
2881
- if search_keys.instance_of?(String)
2882
- search_keys = [RawData.new(search_keys)]
2883
- else
2884
- normalize_searching_criteria(search_keys)
2885
- end
3185
+ search_keys = normalize_searching_criteria(search_keys)
2886
3186
  synchronize do
2887
3187
  send_command(cmd, sort_keys, charset, *search_keys)
2888
3188
  clear_responses("SORT").last || []
@@ -2890,25 +3190,39 @@ module Net
2890
3190
  end
2891
3191
 
2892
3192
  def thread_internal(cmd, algorithm, search_keys, charset)
2893
- if search_keys.instance_of?(String)
2894
- search_keys = [RawData.new(search_keys)]
2895
- else
2896
- normalize_searching_criteria(search_keys)
2897
- end
3193
+ search_keys = normalize_searching_criteria(search_keys)
2898
3194
  synchronize do
2899
3195
  send_command(cmd, algorithm, charset, *search_keys)
2900
3196
  clear_responses("THREAD").last || []
2901
3197
  end
2902
3198
  end
2903
3199
 
2904
- def normalize_searching_criteria(keys)
2905
- keys.collect! do |i|
2906
- case i
2907
- when -1, Range, Array
2908
- 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]
2909
3205
  else
2910
3206
  i
2911
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)
2912
3226
  end
2913
3227
  end
2914
3228
 
@@ -2934,7 +3248,7 @@ module Net
2934
3248
  @sock = SSLSocket.new(@sock, ssl_ctx)
2935
3249
  @sock.sync_close = true
2936
3250
  @sock.hostname = @host if @sock.respond_to? :hostname=
2937
- ssl_socket_connect(@sock, @open_timeout)
3251
+ ssl_socket_connect(@sock, open_timeout)
2938
3252
  if ssl_ctx.verify_mode != VERIFY_NONE
2939
3253
  @sock.post_connection_check(@host)
2940
3254
  @tls_verified = true
@@ -2959,6 +3273,7 @@ module Net
2959
3273
  end
2960
3274
 
2961
3275
  require_relative "imap/errors"
3276
+ require_relative "imap/config"
2962
3277
  require_relative "imap/command_data"
2963
3278
  require_relative "imap/data_encoding"
2964
3279
  require_relative "imap/flags"