net-imap 0.5.6 → 0.6.0
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.
- checksums.yaml +4 -4
- data/Gemfile +7 -4
- data/lib/net/imap/command_data.rb +0 -68
- data/lib/net/imap/config/attr_inheritance.rb +14 -1
- data/lib/net/imap/config/attr_type_coercion.rb +35 -22
- data/lib/net/imap/config/attr_version_defaults.rb +93 -0
- data/lib/net/imap/config.rb +265 -95
- data/lib/net/imap/connection_state.rb +48 -0
- data/lib/net/imap/data_encoding.rb +77 -28
- data/lib/net/imap/errors.rb +33 -0
- data/lib/net/imap/esearch_result.rb +48 -3
- data/lib/net/imap/flags.rb +1 -1
- data/lib/net/imap/response_data.rb +2 -4
- data/lib/net/imap/response_parser.rb +8 -13
- data/lib/net/imap/response_reader.rb +73 -0
- data/lib/net/imap/search_result.rb +8 -3
- data/lib/net/imap/sequence_set.rb +1113 -431
- data/lib/net/imap/uidplus_data.rb +2 -63
- data/lib/net/imap/vanished_data.rb +10 -1
- data/lib/net/imap.rb +291 -81
- data/net-imap.gemspec +1 -1
- data/rakelib/string_prep_tables_generator.rb +4 -2
- metadata +7 -5
- data/lib/net/imap/data_lite.rb +0 -226
data/lib/net/imap.rb
CHANGED
|
@@ -43,10 +43,18 @@ module Net
|
|
|
43
43
|
# To work on the messages within a mailbox, the client must
|
|
44
44
|
# first select that mailbox, using either #select or #examine
|
|
45
45
|
# (for read-only access). Once the client has successfully
|
|
46
|
-
# selected a mailbox, they enter the
|
|
46
|
+
# selected a mailbox, they enter the +selected+ state, and that
|
|
47
47
|
# mailbox becomes the _current_ mailbox, on which mail-item
|
|
48
48
|
# related commands implicitly operate.
|
|
49
49
|
#
|
|
50
|
+
# === Connection state
|
|
51
|
+
#
|
|
52
|
+
# Once an IMAP connection is established, the connection is in one of four
|
|
53
|
+
# states: <tt>not authenticated</tt>, +authenticated+, +selected+, and
|
|
54
|
+
# +logout+. Most commands are valid only in certain states.
|
|
55
|
+
#
|
|
56
|
+
# See #connection_state.
|
|
57
|
+
#
|
|
50
58
|
# === Sequence numbers and UIDs
|
|
51
59
|
#
|
|
52
60
|
# Messages have two sorts of identifiers: message sequence
|
|
@@ -199,6 +207,42 @@ module Net
|
|
|
199
207
|
#
|
|
200
208
|
# This script invokes the FETCH command and the SEARCH command concurrently.
|
|
201
209
|
#
|
|
210
|
+
# When running multiple commands, care must be taken to avoid ambiguity. For
|
|
211
|
+
# example, SEARCH responses are ambiguous about which command they are
|
|
212
|
+
# responding to, so search commands should not run simultaneously, unless the
|
|
213
|
+
# server supports +ESEARCH+ {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731] or
|
|
214
|
+
# IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]. See {RFC9051
|
|
215
|
+
# §5.5}[https://www.rfc-editor.org/rfc/rfc9051.html#section-5.5] for
|
|
216
|
+
# other examples of command sequences which should not be pipelined.
|
|
217
|
+
#
|
|
218
|
+
# == Unbounded memory use
|
|
219
|
+
#
|
|
220
|
+
# Net::IMAP reads server responses in a separate receiver thread per client.
|
|
221
|
+
# Unhandled response data is saved to #responses, and response_handlers run
|
|
222
|
+
# inside the receiver thread. See the list of methods for {handling server
|
|
223
|
+
# responses}[rdoc-ref:Net::IMAP@Handling+server+responses], below.
|
|
224
|
+
#
|
|
225
|
+
# Because the receiver thread continuously reads and saves new responses, some
|
|
226
|
+
# scenarios must be careful to avoid unbounded memory use:
|
|
227
|
+
#
|
|
228
|
+
# * Commands such as #list or #fetch can have an enormous number of responses.
|
|
229
|
+
# * Commands such as #fetch can result in an enormous size per response.
|
|
230
|
+
# * Long-lived connections will gradually accumulate unsolicited server
|
|
231
|
+
# responses, especially +EXISTS+, +FETCH+, and +EXPUNGE+ responses.
|
|
232
|
+
# * A buggy or untrusted server could send inappropriate responses, which
|
|
233
|
+
# could be very numerous, very large, and very rapid.
|
|
234
|
+
#
|
|
235
|
+
# Use paginated or limited versions of commands whenever possible.
|
|
236
|
+
#
|
|
237
|
+
# Use Config#max_response_size to impose a limit on incoming server responses
|
|
238
|
+
# as they are being read. <em>This is especially important for untrusted
|
|
239
|
+
# servers.</em>
|
|
240
|
+
#
|
|
241
|
+
# Use #add_response_handler to handle responses after each one is received.
|
|
242
|
+
# Use the +response_handlers+ argument to ::new to assign response handlers
|
|
243
|
+
# before the receiver thread is started. Use #extract_responses,
|
|
244
|
+
# #clear_responses, or #responses (with a block) to prune responses.
|
|
245
|
+
#
|
|
202
246
|
# == Errors
|
|
203
247
|
#
|
|
204
248
|
# An \IMAP server can send three different types of responses to indicate
|
|
@@ -260,8 +304,9 @@ module Net
|
|
|
260
304
|
#
|
|
261
305
|
# - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
|
|
262
306
|
# waits for a successful server greeting before the method returns.
|
|
307
|
+
# - #connection_state: Returns the connection state.
|
|
263
308
|
# - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
|
|
264
|
-
# - #logout: Tells the server to end the session.
|
|
309
|
+
# - #logout: Tells the server to end the session. Enters the +logout+ state.
|
|
265
310
|
# - #disconnect: Disconnects the connection (without sending #logout first).
|
|
266
311
|
# - #disconnected?: True if the connection has been closed.
|
|
267
312
|
#
|
|
@@ -314,40 +359,39 @@ module Net
|
|
|
314
359
|
#
|
|
315
360
|
# - #capability: Returns the server's capabilities as an array of strings.
|
|
316
361
|
#
|
|
317
|
-
# <em>In general
|
|
318
|
-
# +CAPABILITY+ command to the server.</em>
|
|
362
|
+
# <em>In general,</em> #capable? <em>should be used rather than explicitly
|
|
363
|
+
# sending a +CAPABILITY+ command to the server.</em>
|
|
319
364
|
# - #noop: Allows the server to send unsolicited untagged #responses.
|
|
320
|
-
# - #logout: Tells the server to end the session. Enters the
|
|
365
|
+
# - #logout: Tells the server to end the session. Enters the +logout+ state.
|
|
321
366
|
#
|
|
322
367
|
# ==== Not Authenticated state
|
|
323
368
|
#
|
|
324
369
|
# In addition to the commands for any state, the following commands are valid
|
|
325
|
-
# in the
|
|
370
|
+
# in the +not_authenticated+ state:
|
|
326
371
|
#
|
|
327
372
|
# - #starttls: Upgrades a clear-text connection to use TLS.
|
|
328
373
|
#
|
|
329
374
|
# <em>Requires the +STARTTLS+ capability.</em>
|
|
330
375
|
# - #authenticate: Identifies the client to the server using the given
|
|
331
376
|
# {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
|
332
|
-
# and credentials. Enters the
|
|
377
|
+
# and credentials. Enters the +authenticated+ state.
|
|
333
378
|
#
|
|
334
379
|
# <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
|
|
335
380
|
# supported mechanisms.</em>
|
|
336
381
|
# - #login: Identifies the client to the server using a plain text password.
|
|
337
|
-
# Using #authenticate is
|
|
338
|
-
# state.
|
|
382
|
+
# Using #authenticate is preferred. Enters the +authenticated+ state.
|
|
339
383
|
#
|
|
340
384
|
# <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
|
|
341
385
|
#
|
|
342
386
|
# ==== Authenticated state
|
|
343
387
|
#
|
|
344
388
|
# In addition to the commands for any state, the following commands are valid
|
|
345
|
-
# in the
|
|
389
|
+
# in the +authenticated+ state:
|
|
346
390
|
#
|
|
347
391
|
# - #enable: Enables backwards incompatible server extensions.
|
|
348
392
|
# <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
|
|
349
|
-
# - #select: Open a mailbox and enter the
|
|
350
|
-
# - #examine: Open a mailbox read-only, and enter the
|
|
393
|
+
# - #select: Open a mailbox and enter the +selected+ state.
|
|
394
|
+
# - #examine: Open a mailbox read-only, and enter the +selected+ state.
|
|
351
395
|
# - #create: Creates a new mailbox.
|
|
352
396
|
# - #delete: Permanently remove a mailbox.
|
|
353
397
|
# - #rename: Change the name of a mailbox.
|
|
@@ -369,12 +413,12 @@ module Net
|
|
|
369
413
|
#
|
|
370
414
|
# ==== Selected state
|
|
371
415
|
#
|
|
372
|
-
# In addition to the commands for any state and the
|
|
373
|
-
# commands, the following commands are valid in the
|
|
416
|
+
# In addition to the commands for any state and the +authenticated+
|
|
417
|
+
# commands, the following commands are valid in the +selected+ state:
|
|
374
418
|
#
|
|
375
|
-
# - #close: Closes the mailbox and returns to the
|
|
419
|
+
# - #close: Closes the mailbox and returns to the +authenticated+ state,
|
|
376
420
|
# expunging deleted messages, unless the mailbox was opened as read-only.
|
|
377
|
-
# - #unselect: Closes the mailbox and returns to the
|
|
421
|
+
# - #unselect: Closes the mailbox and returns to the +authenticated+ state,
|
|
378
422
|
# without expunging any messages.
|
|
379
423
|
# <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
|
|
380
424
|
# - #expunge: Permanently removes messages which have the Deleted flag set.
|
|
@@ -395,7 +439,7 @@ module Net
|
|
|
395
439
|
#
|
|
396
440
|
# ==== Logout state
|
|
397
441
|
#
|
|
398
|
-
# No \IMAP commands are valid in the
|
|
442
|
+
# No \IMAP commands are valid in the +logout+ state. If the socket is still
|
|
399
443
|
# open, Net::IMAP will close it after receiving server confirmation.
|
|
400
444
|
# Exceptions will be raised by \IMAP commands that have already started and
|
|
401
445
|
# are waiting for a response, as well as any that are called after logout.
|
|
@@ -449,7 +493,7 @@ module Net
|
|
|
449
493
|
# ==== RFC3691: +UNSELECT+
|
|
450
494
|
# Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
|
|
451
495
|
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
|
|
452
|
-
# - #unselect: Closes the mailbox and returns to the
|
|
496
|
+
# - #unselect: Closes the mailbox and returns to the +authenticated+ state,
|
|
453
497
|
# without expunging any messages.
|
|
454
498
|
#
|
|
455
499
|
# ==== RFC4314: +ACL+
|
|
@@ -744,7 +788,7 @@ module Net
|
|
|
744
788
|
# * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
|
|
745
789
|
#
|
|
746
790
|
class IMAP < Protocol
|
|
747
|
-
VERSION = "0.
|
|
791
|
+
VERSION = "0.6.0"
|
|
748
792
|
|
|
749
793
|
# Aliases for supported capabilities, to be used with the #enable command.
|
|
750
794
|
ENABLE_ALIASES = {
|
|
@@ -752,23 +796,41 @@ module Net
|
|
|
752
796
|
"UTF8=ONLY" => "UTF8=ACCEPT",
|
|
753
797
|
}.freeze
|
|
754
798
|
|
|
755
|
-
|
|
756
|
-
autoload :
|
|
757
|
-
autoload :
|
|
799
|
+
dir = File.expand_path("imap", __dir__)
|
|
800
|
+
autoload :ConnectionState, "#{dir}/connection_state"
|
|
801
|
+
autoload :ResponseReader, "#{dir}/response_reader"
|
|
802
|
+
autoload :SASL, "#{dir}/sasl"
|
|
803
|
+
autoload :SASLAdapter, "#{dir}/sasl_adapter"
|
|
804
|
+
autoload :SequenceSet, "#{dir}/sequence_set"
|
|
805
|
+
autoload :StringPrep, "#{dir}/stringprep"
|
|
758
806
|
|
|
759
807
|
include MonitorMixin
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
808
|
+
|
|
809
|
+
# :call-seq:
|
|
810
|
+
# Net::IMAP::SequenceSet(set = nil) -> SequenceSet
|
|
811
|
+
#
|
|
812
|
+
# Coerces +set+ into a SequenceSet, using either SequenceSet.try_convert or
|
|
813
|
+
# SequenceSet.new.
|
|
814
|
+
#
|
|
815
|
+
# * When +set+ is a SequenceSet, that same set is returned.
|
|
816
|
+
# * When +set+ responds to +to_sequence_set+, +set.to_sequence_set+ is
|
|
817
|
+
# returned.
|
|
818
|
+
# * Otherwise, returns the result from calling SequenceSet.new with +set+.
|
|
819
|
+
#
|
|
820
|
+
# Related: SequenceSet.try_convert, SequenceSet.new, SequenceSet::[]
|
|
821
|
+
def self.SequenceSet(set = nil)
|
|
822
|
+
SequenceSet.try_convert(set) || SequenceSet.new(set)
|
|
763
823
|
end
|
|
764
824
|
|
|
765
825
|
# Returns the global Config object
|
|
766
826
|
def self.config; Config.global end
|
|
767
827
|
|
|
768
828
|
# Returns the global debug mode.
|
|
829
|
+
# Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
|
|
769
830
|
def self.debug; config.debug end
|
|
770
831
|
|
|
771
832
|
# Sets the global debug mode.
|
|
833
|
+
# Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
|
|
772
834
|
def self.debug=(val)
|
|
773
835
|
config.debug = val
|
|
774
836
|
end
|
|
@@ -789,7 +851,7 @@ module Net
|
|
|
789
851
|
alias default_ssl_port default_tls_port
|
|
790
852
|
end
|
|
791
853
|
|
|
792
|
-
# Returns the initial greeting the server, an UntaggedResponse.
|
|
854
|
+
# Returns the initial greeting sent by the server, an UntaggedResponse.
|
|
793
855
|
attr_reader :greeting
|
|
794
856
|
|
|
795
857
|
# The client configuration. See Net::IMAP::Config.
|
|
@@ -798,13 +860,28 @@ module Net
|
|
|
798
860
|
# Net::IMAP.config.
|
|
799
861
|
attr_reader :config
|
|
800
862
|
|
|
801
|
-
|
|
802
|
-
#
|
|
803
|
-
#
|
|
804
|
-
|
|
863
|
+
##
|
|
864
|
+
# :attr_reader: open_timeout
|
|
865
|
+
# Seconds to wait until a connection is opened. Also used by #starttls.
|
|
866
|
+
# Delegates to {config.open_timeout}[rdoc-ref:Config#open_timeout].
|
|
805
867
|
|
|
868
|
+
##
|
|
869
|
+
# :attr_reader: idle_response_timeout
|
|
806
870
|
# Seconds to wait until an IDLE response is received.
|
|
807
|
-
|
|
871
|
+
# Delegates to {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout].
|
|
872
|
+
|
|
873
|
+
##
|
|
874
|
+
# :attr_accessor: max_response_size
|
|
875
|
+
#
|
|
876
|
+
# The maximum allowed server response size, in bytes.
|
|
877
|
+
# Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size].
|
|
878
|
+
|
|
879
|
+
# :stopdoc:
|
|
880
|
+
def open_timeout; config.open_timeout end
|
|
881
|
+
def idle_response_timeout; config.idle_response_timeout end
|
|
882
|
+
def max_response_size; config.max_response_size end
|
|
883
|
+
def max_response_size=(val) config.max_response_size = val end
|
|
884
|
+
# :startdoc:
|
|
808
885
|
|
|
809
886
|
# The hostname this client connected to
|
|
810
887
|
attr_reader :host
|
|
@@ -827,6 +904,67 @@ module Net
|
|
|
827
904
|
# Returns +false+ for a plaintext connection.
|
|
828
905
|
attr_reader :ssl_ctx_params
|
|
829
906
|
|
|
907
|
+
# Returns the current connection state.
|
|
908
|
+
#
|
|
909
|
+
# Once an IMAP connection is established, the connection is in one of four
|
|
910
|
+
# states: +not_authenticated+, +authenticated+, +selected+, and +logout+.
|
|
911
|
+
# Most commands are valid only in certain states.
|
|
912
|
+
#
|
|
913
|
+
# The connection state object responds to +to_sym+ and +name+ with the name
|
|
914
|
+
# of the current connection state, as a Symbol or String. Future versions
|
|
915
|
+
# of +net-imap+ may store additional information on the state object.
|
|
916
|
+
#
|
|
917
|
+
# From {RFC9051}[https://www.rfc-editor.org/rfc/rfc9051#section-3]:
|
|
918
|
+
# +----------------------+
|
|
919
|
+
# |connection established|
|
|
920
|
+
# +----------------------+
|
|
921
|
+
# ||
|
|
922
|
+
# \/
|
|
923
|
+
# +--------------------------------------+
|
|
924
|
+
# | server greeting |
|
|
925
|
+
# +--------------------------------------+
|
|
926
|
+
# || (1) || (2) || (3)
|
|
927
|
+
# \/ || ||
|
|
928
|
+
# +-----------------+ || ||
|
|
929
|
+
# |Not Authenticated| || ||
|
|
930
|
+
# +-----------------+ || ||
|
|
931
|
+
# || (7) || (4) || ||
|
|
932
|
+
# || \/ \/ ||
|
|
933
|
+
# || +----------------+ ||
|
|
934
|
+
# || | Authenticated |<=++ ||
|
|
935
|
+
# || +----------------+ || ||
|
|
936
|
+
# || || (7) || (5) || (6) ||
|
|
937
|
+
# || || \/ || ||
|
|
938
|
+
# || || +--------+ || ||
|
|
939
|
+
# || || |Selected|==++ ||
|
|
940
|
+
# || || +--------+ ||
|
|
941
|
+
# || || || (7) ||
|
|
942
|
+
# \/ \/ \/ \/
|
|
943
|
+
# +--------------------------------------+
|
|
944
|
+
# | Logout |
|
|
945
|
+
# +--------------------------------------+
|
|
946
|
+
# ||
|
|
947
|
+
# \/
|
|
948
|
+
# +-------------------------------+
|
|
949
|
+
# |both sides close the connection|
|
|
950
|
+
# +-------------------------------+
|
|
951
|
+
#
|
|
952
|
+
# >>>
|
|
953
|
+
# Legend for the above diagram:
|
|
954
|
+
#
|
|
955
|
+
# 1. connection without pre-authentication (+OK+ #greeting)
|
|
956
|
+
# 2. pre-authenticated connection (+PREAUTH+ #greeting)
|
|
957
|
+
# 3. rejected connection (+BYE+ #greeting)
|
|
958
|
+
# 4. successful #login or #authenticate command
|
|
959
|
+
# 5. successful #select or #examine command
|
|
960
|
+
# 6. #close or #unselect command, unsolicited +CLOSED+ response code, or
|
|
961
|
+
# failed #select or #examine command
|
|
962
|
+
# 7. #logout command, server shutdown, or connection closed
|
|
963
|
+
#
|
|
964
|
+
# Before the server greeting, the state is +not_authenticated+.
|
|
965
|
+
# After the connection closes, the state remains +logout+.
|
|
966
|
+
attr_reader :connection_state
|
|
967
|
+
|
|
830
968
|
# Creates a new Net::IMAP object and connects it to the specified
|
|
831
969
|
# +host+.
|
|
832
970
|
#
|
|
@@ -860,6 +998,12 @@ module Net
|
|
|
860
998
|
#
|
|
861
999
|
# See DeprecatedClientOptions.new for deprecated SSL arguments.
|
|
862
1000
|
#
|
|
1001
|
+
# [response_handlers]
|
|
1002
|
+
# A list of response handlers to be added before the receiver thread is
|
|
1003
|
+
# started. This ensures every server response is handled, including the
|
|
1004
|
+
# #greeting. Note that the greeting is handled in the current thread, but
|
|
1005
|
+
# all other responses are handled in the receiver thread.
|
|
1006
|
+
#
|
|
863
1007
|
# [config]
|
|
864
1008
|
# A Net::IMAP::Config object to use as the basis for #config. By default,
|
|
865
1009
|
# the global Net::IMAP.config is used.
|
|
@@ -931,7 +1075,7 @@ module Net
|
|
|
931
1075
|
# [Net::IMAP::ByeResponseError]
|
|
932
1076
|
# Connected to the host successfully, but it immediately said goodbye.
|
|
933
1077
|
#
|
|
934
|
-
def initialize(host, port: nil, ssl:
|
|
1078
|
+
def initialize(host, port: nil, ssl: nil, response_handlers: nil,
|
|
935
1079
|
config: Config.global, **config_options)
|
|
936
1080
|
super()
|
|
937
1081
|
# Config options
|
|
@@ -946,6 +1090,8 @@ module Net
|
|
|
946
1090
|
@exception = nil
|
|
947
1091
|
@greeting = nil
|
|
948
1092
|
@capabilities = nil
|
|
1093
|
+
@tls_verified = false
|
|
1094
|
+
@connection_state = ConnectionState::NotAuthenticated.new
|
|
949
1095
|
|
|
950
1096
|
# Client Protocol Receiver
|
|
951
1097
|
@parser = ResponseParser.new(config: @config)
|
|
@@ -954,6 +1100,7 @@ module Net
|
|
|
954
1100
|
@receiver_thread = nil
|
|
955
1101
|
@receiver_thread_exception = nil
|
|
956
1102
|
@receiver_thread_terminating = false
|
|
1103
|
+
response_handlers&.each do add_response_handler(_1) end
|
|
957
1104
|
|
|
958
1105
|
# Client Protocol Sender (including state for currently running commands)
|
|
959
1106
|
@tag_prefix = "RUBY"
|
|
@@ -967,8 +1114,8 @@ module Net
|
|
|
967
1114
|
@logout_command_tag = nil
|
|
968
1115
|
|
|
969
1116
|
# Connection
|
|
970
|
-
@tls_verified = false
|
|
971
1117
|
@sock = tcp_socket(@host, @port)
|
|
1118
|
+
@reader = ResponseReader.new(self, @sock)
|
|
972
1119
|
start_tls_session if ssl_ctx
|
|
973
1120
|
start_imap_connection
|
|
974
1121
|
end
|
|
@@ -980,27 +1127,27 @@ module Net
|
|
|
980
1127
|
|
|
981
1128
|
# Disconnects from the server.
|
|
982
1129
|
#
|
|
1130
|
+
# Waits for receiver thread to close before returning. Slow or stuck
|
|
1131
|
+
# response handlers can cause #disconnect to hang until they complete.
|
|
1132
|
+
#
|
|
983
1133
|
# Related: #logout, #logout!
|
|
984
1134
|
def disconnect
|
|
1135
|
+
in_logout_state = try_state_logout?
|
|
985
1136
|
return if disconnected?
|
|
986
1137
|
begin
|
|
987
|
-
|
|
988
|
-
# try to call SSL::SSLSocket#io.
|
|
989
|
-
@sock.io.shutdown
|
|
990
|
-
rescue NoMethodError
|
|
991
|
-
# @sock is not an SSL::SSLSocket.
|
|
992
|
-
@sock.shutdown
|
|
993
|
-
end
|
|
1138
|
+
@sock.to_io.shutdown
|
|
994
1139
|
rescue Errno::ENOTCONN
|
|
995
1140
|
# ignore `Errno::ENOTCONN: Socket is not connected' on some platforms.
|
|
996
1141
|
rescue Exception => e
|
|
997
1142
|
@receiver_thread.raise(e)
|
|
998
1143
|
end
|
|
1144
|
+
@sock.close
|
|
999
1145
|
@receiver_thread.join
|
|
1000
|
-
synchronize do
|
|
1001
|
-
@sock.close
|
|
1002
|
-
end
|
|
1003
1146
|
raise e if e
|
|
1147
|
+
ensure
|
|
1148
|
+
# Try again after shutting down the receiver thread. With no reciever
|
|
1149
|
+
# left to wait for, any remaining locks should be _very_ brief.
|
|
1150
|
+
state_logout! unless in_logout_state
|
|
1004
1151
|
end
|
|
1005
1152
|
|
|
1006
1153
|
# Returns true if disconnected from the server.
|
|
@@ -1221,6 +1368,10 @@ module Net
|
|
|
1221
1368
|
# both successful. Any error indicates that the connection has not been
|
|
1222
1369
|
# secured.
|
|
1223
1370
|
#
|
|
1371
|
+
# After the server agrees to start a TLS connection, this method waits up to
|
|
1372
|
+
# {config.open_timeout}[rdoc-ref:Config#open_timeout] before raising
|
|
1373
|
+
# +Net::OpenTimeout+.
|
|
1374
|
+
#
|
|
1224
1375
|
# *Note:*
|
|
1225
1376
|
# >>>
|
|
1226
1377
|
# Any #response_handlers added before STARTTLS should be aware that the
|
|
@@ -1367,8 +1518,9 @@ module Net
|
|
|
1367
1518
|
# completes. If the TaggedResponse to #authenticate includes updated
|
|
1368
1519
|
# capabilities, they will be cached.
|
|
1369
1520
|
def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback)
|
|
1521
|
+
sasl_ir = may_depend_on_capabilities_cached?(sasl_ir)
|
|
1370
1522
|
sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback)
|
|
1371
|
-
.tap
|
|
1523
|
+
.tap do state_authenticated! _1 end
|
|
1372
1524
|
end
|
|
1373
1525
|
|
|
1374
1526
|
# Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
|
|
@@ -1402,7 +1554,7 @@ module Net
|
|
|
1402
1554
|
raise LoginDisabledError
|
|
1403
1555
|
end
|
|
1404
1556
|
send_command("LOGIN", user, password)
|
|
1405
|
-
.tap
|
|
1557
|
+
.tap do state_authenticated! _1 end
|
|
1406
1558
|
end
|
|
1407
1559
|
|
|
1408
1560
|
# Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
|
|
@@ -1418,7 +1570,7 @@ module Net
|
|
|
1418
1570
|
# When the +condstore+ keyword argument is true, the server is told to
|
|
1419
1571
|
# enable the extension. If +mailbox+ supports persistence of mod-sequences,
|
|
1420
1572
|
# the +HIGHESTMODSEQ+ ResponseCode will be sent as an untagged response to
|
|
1421
|
-
# #select and all
|
|
1573
|
+
# #select and all +FETCH+ responses will include FetchData#modseq.
|
|
1422
1574
|
# Otherwise, the +NOMODSEQ+ ResponseCode will be sent.
|
|
1423
1575
|
#
|
|
1424
1576
|
# A Net::IMAP::NoResponseError is raised if the mailbox does not
|
|
@@ -1442,8 +1594,10 @@ module Net
|
|
|
1442
1594
|
args = ["SELECT", mailbox]
|
|
1443
1595
|
args << ["CONDSTORE"] if condstore
|
|
1444
1596
|
synchronize do
|
|
1597
|
+
state_unselected! # implicitly closes current mailbox
|
|
1445
1598
|
@responses.clear
|
|
1446
1599
|
send_command(*args)
|
|
1600
|
+
.tap do state_selected! end
|
|
1447
1601
|
end
|
|
1448
1602
|
end
|
|
1449
1603
|
|
|
@@ -1460,8 +1614,10 @@ module Net
|
|
|
1460
1614
|
args = ["EXAMINE", mailbox]
|
|
1461
1615
|
args << ["CONDSTORE"] if condstore
|
|
1462
1616
|
synchronize do
|
|
1617
|
+
state_unselected! # implicitly closes current mailbox
|
|
1463
1618
|
@responses.clear
|
|
1464
1619
|
send_command(*args)
|
|
1620
|
+
.tap do state_selected! end
|
|
1465
1621
|
end
|
|
1466
1622
|
end
|
|
1467
1623
|
|
|
@@ -1865,7 +2021,7 @@ module Net
|
|
|
1865
2021
|
#
|
|
1866
2022
|
# If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
|
|
1867
2023
|
# supported and the destination supports persistent UIDs, the server's
|
|
1868
|
-
# response should include an +APPENDUID+ response code with
|
|
2024
|
+
# response should include an +APPENDUID+ response code with AppendUIDData.
|
|
1869
2025
|
# This will report the UIDVALIDITY of the destination mailbox and the
|
|
1870
2026
|
# assigned UID of the appended message.
|
|
1871
2027
|
#
|
|
@@ -1900,6 +2056,7 @@ module Net
|
|
|
1900
2056
|
# Related: #unselect
|
|
1901
2057
|
def close
|
|
1902
2058
|
send_command("CLOSE")
|
|
2059
|
+
.tap do state_authenticated! end
|
|
1903
2060
|
end
|
|
1904
2061
|
|
|
1905
2062
|
# Sends an {UNSELECT command [RFC3691 §2]}[https://www.rfc-editor.org/rfc/rfc3691#section-3]
|
|
@@ -1916,6 +2073,7 @@ module Net
|
|
|
1916
2073
|
# [RFC3691[https://www.rfc-editor.org/rfc/rfc3691]].
|
|
1917
2074
|
def unselect
|
|
1918
2075
|
send_command("UNSELECT")
|
|
2076
|
+
.tap do state_authenticated! end
|
|
1919
2077
|
end
|
|
1920
2078
|
|
|
1921
2079
|
# call-seq:
|
|
@@ -1949,8 +2107,8 @@ module Net
|
|
|
1949
2107
|
end
|
|
1950
2108
|
|
|
1951
2109
|
# call-seq:
|
|
1952
|
-
# uid_expunge
|
|
1953
|
-
# uid_expunge
|
|
2110
|
+
# uid_expunge(uid_set) -> array of message sequence numbers
|
|
2111
|
+
# uid_expunge(uid_set) -> VanishedData of UIDs
|
|
1954
2112
|
#
|
|
1955
2113
|
# Sends a {UID EXPUNGE command [RFC4315 §2.1]}[https://www.rfc-editor.org/rfc/rfc4315#section-2.1]
|
|
1956
2114
|
# {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9]
|
|
@@ -2514,6 +2672,7 @@ module Net
|
|
|
2514
2672
|
# # fetch should return quickly and allocate little memory
|
|
2515
2673
|
# results.size # => 0..500
|
|
2516
2674
|
# break if results.empty?
|
|
2675
|
+
# results.sort_by!(&:uid) # server may return results out of order
|
|
2517
2676
|
# next_uid_to_fetch = results.last.uid + 1
|
|
2518
2677
|
# process results
|
|
2519
2678
|
# end
|
|
@@ -2619,7 +2778,7 @@ module Net
|
|
|
2619
2778
|
#
|
|
2620
2779
|
# If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
|
|
2621
2780
|
# supported, the server's response should include a +COPYUID+ response code
|
|
2622
|
-
# with
|
|
2781
|
+
# with CopyUIDData. This will report the UIDVALIDITY of the destination
|
|
2623
2782
|
# mailbox, the UID set of the source messages, and the assigned UID set of
|
|
2624
2783
|
# the moved messages.
|
|
2625
2784
|
#
|
|
@@ -2660,7 +2819,7 @@ module Net
|
|
|
2660
2819
|
#
|
|
2661
2820
|
# If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
|
|
2662
2821
|
# supported, the server's response should include a +COPYUID+ response code
|
|
2663
|
-
# with
|
|
2822
|
+
# with CopyUIDData. This will report the UIDVALIDITY of the destination
|
|
2664
2823
|
# mailbox, the UID set of the source messages, and the assigned UID set of
|
|
2665
2824
|
# the moved messages.
|
|
2666
2825
|
#
|
|
@@ -2800,6 +2959,18 @@ module Net
|
|
|
2800
2959
|
# command parameters defined by the extension will implicitly enable it.
|
|
2801
2960
|
# See {[RFC7162 §3.1]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1].
|
|
2802
2961
|
#
|
|
2962
|
+
# [+QRESYNC+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]]
|
|
2963
|
+
# *NOTE:* Enabling QRESYNC will replace +EXPUNGE+ with +VANISHED+, but
|
|
2964
|
+
# the extension arguments to #select, #examine, and #uid_fetch are not
|
|
2965
|
+
# supported yet.
|
|
2966
|
+
#
|
|
2967
|
+
# Adds quick resynchronization options to #select, #examine, and
|
|
2968
|
+
# #uid_fetch. +QRESYNC+ _must_ be explicitly enabled before using any of
|
|
2969
|
+
# the extension's command parameters. All +EXPUNGE+ responses will be
|
|
2970
|
+
# replaced with +VANISHED+ responses. Enabling +QRESYNC+ implicitly
|
|
2971
|
+
# enables +CONDSTORE+ as well.
|
|
2972
|
+
# See {[RFC7162 §3.2]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.2].
|
|
2973
|
+
#
|
|
2803
2974
|
# [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
|
|
2804
2975
|
#
|
|
2805
2976
|
# In a future release, <tt>enable(:utf8)</tt> will enable either
|
|
@@ -2917,8 +3088,8 @@ module Net
|
|
|
2917
3088
|
raise @exception || Net::IMAP::Error.new("connection closed")
|
|
2918
3089
|
end
|
|
2919
3090
|
ensure
|
|
3091
|
+
remove_response_handler(response_handler)
|
|
2920
3092
|
unless @receiver_thread_terminating
|
|
2921
|
-
remove_response_handler(response_handler)
|
|
2922
3093
|
put_string("DONE#{CRLF}")
|
|
2923
3094
|
response = get_tagged_response(tag, "IDLE", idle_response_timeout)
|
|
2924
3095
|
end
|
|
@@ -3146,6 +3317,10 @@ module Net
|
|
|
3146
3317
|
# end
|
|
3147
3318
|
# }
|
|
3148
3319
|
#
|
|
3320
|
+
# Response handlers can also be added when the client is created before the
|
|
3321
|
+
# receiver thread is started, by the +response_handlers+ argument to ::new.
|
|
3322
|
+
# This ensures every server response is handled, including the #greeting.
|
|
3323
|
+
#
|
|
3149
3324
|
# Related: #remove_response_handler, #response_handlers
|
|
3150
3325
|
def add_response_handler(handler = nil, &block)
|
|
3151
3326
|
raise ArgumentError, "two Procs are passed" if handler && block
|
|
@@ -3172,8 +3347,10 @@ module Net
|
|
|
3172
3347
|
def start_imap_connection
|
|
3173
3348
|
@greeting = get_server_greeting
|
|
3174
3349
|
@capabilities = capabilities_from_resp_code @greeting
|
|
3350
|
+
@response_handlers.each do |handler| handler.call(@greeting) end
|
|
3175
3351
|
@receiver_thread = start_receiver_thread
|
|
3176
3352
|
rescue Exception
|
|
3353
|
+
state_logout!
|
|
3177
3354
|
@sock.close
|
|
3178
3355
|
raise
|
|
3179
3356
|
end
|
|
@@ -3182,7 +3359,10 @@ module Net
|
|
|
3182
3359
|
greeting = get_response
|
|
3183
3360
|
raise Error, "No server greeting - connection closed" unless greeting
|
|
3184
3361
|
record_untagged_response_code greeting
|
|
3185
|
-
|
|
3362
|
+
case greeting.name
|
|
3363
|
+
when "PREAUTH" then state_authenticated!
|
|
3364
|
+
when "BYE" then state_logout!; raise ByeResponseError, greeting
|
|
3365
|
+
end
|
|
3186
3366
|
greeting
|
|
3187
3367
|
end
|
|
3188
3368
|
|
|
@@ -3214,6 +3394,7 @@ module Net
|
|
|
3214
3394
|
resp = get_response
|
|
3215
3395
|
rescue Exception => e
|
|
3216
3396
|
synchronize do
|
|
3397
|
+
state_logout!
|
|
3217
3398
|
@sock.close
|
|
3218
3399
|
@exception = e
|
|
3219
3400
|
end
|
|
@@ -3233,6 +3414,7 @@ module Net
|
|
|
3233
3414
|
@tagged_response_arrival.broadcast
|
|
3234
3415
|
case resp.tag
|
|
3235
3416
|
when @logout_command_tag
|
|
3417
|
+
state_logout!
|
|
3236
3418
|
return
|
|
3237
3419
|
when @continued_command_tag
|
|
3238
3420
|
@continuation_request_exception =
|
|
@@ -3242,6 +3424,7 @@ module Net
|
|
|
3242
3424
|
when UntaggedResponse
|
|
3243
3425
|
record_untagged_response(resp)
|
|
3244
3426
|
if resp.name == "BYE" && @logout_command_tag.nil?
|
|
3427
|
+
state_logout!
|
|
3245
3428
|
@sock.close
|
|
3246
3429
|
@exception = ByeResponseError.new(resp)
|
|
3247
3430
|
connection_closed = true
|
|
@@ -3249,6 +3432,7 @@ module Net
|
|
|
3249
3432
|
when ContinuationRequest
|
|
3250
3433
|
@continuation_request_arrival.signal
|
|
3251
3434
|
end
|
|
3435
|
+
state_unselected! if resp in {data: {code: {name: "CLOSED"}}}
|
|
3252
3436
|
@response_handlers.each do |handler|
|
|
3253
3437
|
handler.call(resp)
|
|
3254
3438
|
end
|
|
@@ -3269,6 +3453,8 @@ module Net
|
|
|
3269
3453
|
@idle_done_cond.signal
|
|
3270
3454
|
end
|
|
3271
3455
|
end
|
|
3456
|
+
ensure
|
|
3457
|
+
state_logout!
|
|
3272
3458
|
end
|
|
3273
3459
|
|
|
3274
3460
|
def get_tagged_response(tag, cmd, timeout = nil)
|
|
@@ -3300,23 +3486,10 @@ module Net
|
|
|
3300
3486
|
end
|
|
3301
3487
|
|
|
3302
3488
|
def get_response
|
|
3303
|
-
buff =
|
|
3304
|
-
while true
|
|
3305
|
-
s = @sock.gets(CRLF)
|
|
3306
|
-
break unless s
|
|
3307
|
-
buff.concat(s)
|
|
3308
|
-
if /\{(\d+)\}\r\n/n =~ s
|
|
3309
|
-
s = @sock.read($1.to_i)
|
|
3310
|
-
buff.concat(s)
|
|
3311
|
-
else
|
|
3312
|
-
break
|
|
3313
|
-
end
|
|
3314
|
-
end
|
|
3489
|
+
buff = @reader.read_response_buffer
|
|
3315
3490
|
return nil if buff.length == 0
|
|
3316
|
-
if config.debug?
|
|
3317
|
-
|
|
3318
|
-
end
|
|
3319
|
-
return @parser.parse(buff)
|
|
3491
|
+
$stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
|
|
3492
|
+
@parser.parse(buff)
|
|
3320
3493
|
end
|
|
3321
3494
|
|
|
3322
3495
|
#############################
|
|
@@ -3406,11 +3579,11 @@ module Net
|
|
|
3406
3579
|
end
|
|
3407
3580
|
|
|
3408
3581
|
def enforce_logindisabled?
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3582
|
+
may_depend_on_capabilities_cached?(config.enforce_logindisabled)
|
|
3583
|
+
end
|
|
3584
|
+
|
|
3585
|
+
def may_depend_on_capabilities_cached?(value)
|
|
3586
|
+
value == :when_capabilities_cached ? capabilities_cached? : value
|
|
3414
3587
|
end
|
|
3415
3588
|
|
|
3416
3589
|
def expunge_internal(...)
|
|
@@ -3509,6 +3682,9 @@ module Net
|
|
|
3509
3682
|
end
|
|
3510
3683
|
|
|
3511
3684
|
def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
|
|
3685
|
+
if partial && !cmd.start_with?("UID ")
|
|
3686
|
+
raise ArgumentError, "partial can only be used with uid_fetch"
|
|
3687
|
+
end
|
|
3512
3688
|
set = SequenceSet[set]
|
|
3513
3689
|
if partial
|
|
3514
3690
|
mod ||= []
|
|
@@ -3603,11 +3779,8 @@ module Net
|
|
|
3603
3779
|
def build_ssl_ctx(ssl)
|
|
3604
3780
|
if ssl
|
|
3605
3781
|
params = (Hash.try_convert(ssl) || {}).freeze
|
|
3606
|
-
context = SSLContext.new
|
|
3782
|
+
context = OpenSSL::SSL::SSLContext.new
|
|
3607
3783
|
context.set_params(params)
|
|
3608
|
-
if defined?(VerifyCallbackProc)
|
|
3609
|
-
context.verify_callback = VerifyCallbackProc
|
|
3610
|
-
end
|
|
3611
3784
|
context.freeze
|
|
3612
3785
|
[params, context]
|
|
3613
3786
|
else
|
|
@@ -3619,16 +3792,54 @@ module Net
|
|
|
3619
3792
|
raise "SSL extension not installed" unless defined?(OpenSSL::SSL)
|
|
3620
3793
|
raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
|
|
3621
3794
|
raise "cannot start TLS without SSLContext" unless ssl_ctx
|
|
3622
|
-
@sock = SSLSocket.new(@sock, ssl_ctx)
|
|
3795
|
+
@sock = OpenSSL::SSL::SSLSocket.new(@sock, ssl_ctx)
|
|
3796
|
+
@reader = ResponseReader.new(self, @sock)
|
|
3623
3797
|
@sock.sync_close = true
|
|
3624
3798
|
@sock.hostname = @host if @sock.respond_to? :hostname=
|
|
3625
3799
|
ssl_socket_connect(@sock, open_timeout)
|
|
3626
|
-
if ssl_ctx.verify_mode != VERIFY_NONE
|
|
3800
|
+
if ssl_ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE
|
|
3627
3801
|
@sock.post_connection_check(@host)
|
|
3628
3802
|
@tls_verified = true
|
|
3629
3803
|
end
|
|
3630
3804
|
end
|
|
3631
3805
|
|
|
3806
|
+
def state_authenticated!(resp = nil)
|
|
3807
|
+
synchronize do
|
|
3808
|
+
@capabilities = capabilities_from_resp_code resp if resp
|
|
3809
|
+
@connection_state = ConnectionState::Authenticated.new
|
|
3810
|
+
end
|
|
3811
|
+
end
|
|
3812
|
+
|
|
3813
|
+
def state_selected!
|
|
3814
|
+
synchronize do
|
|
3815
|
+
@connection_state = ConnectionState::Selected.new
|
|
3816
|
+
end
|
|
3817
|
+
end
|
|
3818
|
+
|
|
3819
|
+
def state_unselected!
|
|
3820
|
+
synchronize do
|
|
3821
|
+
state_authenticated! if connection_state.to_sym == :selected
|
|
3822
|
+
end
|
|
3823
|
+
end
|
|
3824
|
+
|
|
3825
|
+
def state_logout!
|
|
3826
|
+
return true if connection_state in [:logout, *]
|
|
3827
|
+
synchronize do
|
|
3828
|
+
return true if connection_state in [:logout, *]
|
|
3829
|
+
@connection_state = ConnectionState::Logout.new
|
|
3830
|
+
end
|
|
3831
|
+
end
|
|
3832
|
+
|
|
3833
|
+
# don't wait to aqcuire the lock
|
|
3834
|
+
def try_state_logout?
|
|
3835
|
+
return true if connection_state in [:logout, *]
|
|
3836
|
+
return false unless acquired_lock = mon_try_enter
|
|
3837
|
+
state_logout!
|
|
3838
|
+
true
|
|
3839
|
+
ensure
|
|
3840
|
+
mon_exit if acquired_lock
|
|
3841
|
+
end
|
|
3842
|
+
|
|
3632
3843
|
def sasl_adapter
|
|
3633
3844
|
SASLAdapter.new(self, &method(:send_command_with_continuations))
|
|
3634
3845
|
end
|
|
@@ -3650,7 +3861,6 @@ require_relative "imap/errors"
|
|
|
3650
3861
|
require_relative "imap/config"
|
|
3651
3862
|
require_relative "imap/command_data"
|
|
3652
3863
|
require_relative "imap/data_encoding"
|
|
3653
|
-
require_relative "imap/data_lite"
|
|
3654
3864
|
require_relative "imap/flags"
|
|
3655
3865
|
require_relative "imap/response_data"
|
|
3656
3866
|
require_relative "imap/response_parser"
|