net-imap 0.5.5 → 0.5.7
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 +1 -0
- data/lib/net/imap/config/attr_type_coercion.rb +20 -23
- data/lib/net/imap/config.rb +168 -18
- data/lib/net/imap/connection_state.rb +48 -0
- data/lib/net/imap/errors.rb +33 -0
- data/lib/net/imap/response_data.rb +3 -49
- data/lib/net/imap/response_parser.rb +28 -13
- data/lib/net/imap/response_reader.rb +73 -0
- data/lib/net/imap/sequence_set.rb +267 -118
- data/lib/net/imap/uidplus_data.rb +244 -0
- data/lib/net/imap.rb +227 -48
- metadata +6 -3
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
|
#
|
@@ -317,37 +362,36 @@ module Net
|
|
317
362
|
# <em>In general, #capable? should be used rather than explicitly sending a
|
318
363
|
# +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.5.
|
791
|
+
VERSION = "0.5.7"
|
748
792
|
|
749
793
|
# Aliases for supported capabilities, to be used with the #enable command.
|
750
794
|
ENABLE_ALIASES = {
|
@@ -752,9 +796,12 @@ 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 :StringPrep, "#{dir}/stringprep"
|
758
805
|
|
759
806
|
include MonitorMixin
|
760
807
|
if defined?(OpenSSL::SSL)
|
@@ -766,9 +813,11 @@ module Net
|
|
766
813
|
def self.config; Config.global end
|
767
814
|
|
768
815
|
# Returns the global debug mode.
|
816
|
+
# Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
|
769
817
|
def self.debug; config.debug end
|
770
818
|
|
771
819
|
# Sets the global debug mode.
|
820
|
+
# Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
|
772
821
|
def self.debug=(val)
|
773
822
|
config.debug = val
|
774
823
|
end
|
@@ -789,7 +838,7 @@ module Net
|
|
789
838
|
alias default_ssl_port default_tls_port
|
790
839
|
end
|
791
840
|
|
792
|
-
# Returns the initial greeting the server, an UntaggedResponse.
|
841
|
+
# Returns the initial greeting sent by the server, an UntaggedResponse.
|
793
842
|
attr_reader :greeting
|
794
843
|
|
795
844
|
# The client configuration. See Net::IMAP::Config.
|
@@ -798,13 +847,28 @@ module Net
|
|
798
847
|
# Net::IMAP.config.
|
799
848
|
attr_reader :config
|
800
849
|
|
801
|
-
|
802
|
-
#
|
803
|
-
#
|
804
|
-
|
850
|
+
##
|
851
|
+
# :attr_reader: open_timeout
|
852
|
+
# Seconds to wait until a connection is opened. Also used by #starttls.
|
853
|
+
# Delegates to {config.open_timeout}[rdoc-ref:Config#open_timeout].
|
805
854
|
|
855
|
+
##
|
856
|
+
# :attr_reader: idle_response_timeout
|
806
857
|
# Seconds to wait until an IDLE response is received.
|
807
|
-
|
858
|
+
# Delegates to {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout].
|
859
|
+
|
860
|
+
##
|
861
|
+
# :attr_accessor: max_response_size
|
862
|
+
#
|
863
|
+
# The maximum allowed server response size, in bytes.
|
864
|
+
# Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size].
|
865
|
+
|
866
|
+
# :stopdoc:
|
867
|
+
def open_timeout; config.open_timeout end
|
868
|
+
def idle_response_timeout; config.idle_response_timeout end
|
869
|
+
def max_response_size; config.max_response_size end
|
870
|
+
def max_response_size=(val) config.max_response_size = val end
|
871
|
+
# :startdoc:
|
808
872
|
|
809
873
|
# The hostname this client connected to
|
810
874
|
attr_reader :host
|
@@ -827,6 +891,67 @@ module Net
|
|
827
891
|
# Returns +false+ for a plaintext connection.
|
828
892
|
attr_reader :ssl_ctx_params
|
829
893
|
|
894
|
+
# Returns the current connection state.
|
895
|
+
#
|
896
|
+
# Once an IMAP connection is established, the connection is in one of four
|
897
|
+
# states: +not_authenticated+, +authenticated+, +selected+, and +logout+.
|
898
|
+
# Most commands are valid only in certain states.
|
899
|
+
#
|
900
|
+
# The connection state object responds to +to_sym+ and +name+ with the name
|
901
|
+
# of the current connection state, as a Symbol or String. Future versions
|
902
|
+
# of +net-imap+ may store additional information on the state object.
|
903
|
+
#
|
904
|
+
# From {RFC9051}[https://www.rfc-editor.org/rfc/rfc9051#section-3]:
|
905
|
+
# +----------------------+
|
906
|
+
# |connection established|
|
907
|
+
# +----------------------+
|
908
|
+
# ||
|
909
|
+
# \/
|
910
|
+
# +--------------------------------------+
|
911
|
+
# | server greeting |
|
912
|
+
# +--------------------------------------+
|
913
|
+
# || (1) || (2) || (3)
|
914
|
+
# \/ || ||
|
915
|
+
# +-----------------+ || ||
|
916
|
+
# |Not Authenticated| || ||
|
917
|
+
# +-----------------+ || ||
|
918
|
+
# || (7) || (4) || ||
|
919
|
+
# || \/ \/ ||
|
920
|
+
# || +----------------+ ||
|
921
|
+
# || | Authenticated |<=++ ||
|
922
|
+
# || +----------------+ || ||
|
923
|
+
# || || (7) || (5) || (6) ||
|
924
|
+
# || || \/ || ||
|
925
|
+
# || || +--------+ || ||
|
926
|
+
# || || |Selected|==++ ||
|
927
|
+
# || || +--------+ ||
|
928
|
+
# || || || (7) ||
|
929
|
+
# \/ \/ \/ \/
|
930
|
+
# +--------------------------------------+
|
931
|
+
# | Logout |
|
932
|
+
# +--------------------------------------+
|
933
|
+
# ||
|
934
|
+
# \/
|
935
|
+
# +-------------------------------+
|
936
|
+
# |both sides close the connection|
|
937
|
+
# +-------------------------------+
|
938
|
+
#
|
939
|
+
# >>>
|
940
|
+
# Legend for the above diagram:
|
941
|
+
#
|
942
|
+
# 1. connection without pre-authentication (+OK+ #greeting)
|
943
|
+
# 2. pre-authenticated connection (+PREAUTH+ #greeting)
|
944
|
+
# 3. rejected connection (+BYE+ #greeting)
|
945
|
+
# 4. successful #login or #authenticate command
|
946
|
+
# 5. successful #select or #examine command
|
947
|
+
# 6. #close or #unselect command, unsolicited +CLOSED+ response code, or
|
948
|
+
# failed #select or #examine command
|
949
|
+
# 7. #logout command, server shutdown, or connection closed
|
950
|
+
#
|
951
|
+
# Before the server greeting, the state is +not_authenticated+.
|
952
|
+
# After the connection closes, the state remains +logout+.
|
953
|
+
attr_reader :connection_state
|
954
|
+
|
830
955
|
# Creates a new Net::IMAP object and connects it to the specified
|
831
956
|
# +host+.
|
832
957
|
#
|
@@ -860,6 +985,12 @@ module Net
|
|
860
985
|
#
|
861
986
|
# See DeprecatedClientOptions.new for deprecated SSL arguments.
|
862
987
|
#
|
988
|
+
# [response_handlers]
|
989
|
+
# A list of response handlers to be added before the receiver thread is
|
990
|
+
# started. This ensures every server response is handled, including the
|
991
|
+
# #greeting. Note that the greeting is handled in the current thread, but
|
992
|
+
# all other responses are handled in the receiver thread.
|
993
|
+
#
|
863
994
|
# [config]
|
864
995
|
# A Net::IMAP::Config object to use as the basis for #config. By default,
|
865
996
|
# the global Net::IMAP.config is used.
|
@@ -931,7 +1062,7 @@ module Net
|
|
931
1062
|
# [Net::IMAP::ByeResponseError]
|
932
1063
|
# Connected to the host successfully, but it immediately said goodbye.
|
933
1064
|
#
|
934
|
-
def initialize(host, port: nil, ssl:
|
1065
|
+
def initialize(host, port: nil, ssl: nil, response_handlers: nil,
|
935
1066
|
config: Config.global, **config_options)
|
936
1067
|
super()
|
937
1068
|
# Config options
|
@@ -946,6 +1077,8 @@ module Net
|
|
946
1077
|
@exception = nil
|
947
1078
|
@greeting = nil
|
948
1079
|
@capabilities = nil
|
1080
|
+
@tls_verified = false
|
1081
|
+
@connection_state = ConnectionState::NotAuthenticated.new
|
949
1082
|
|
950
1083
|
# Client Protocol Receiver
|
951
1084
|
@parser = ResponseParser.new(config: @config)
|
@@ -954,6 +1087,7 @@ module Net
|
|
954
1087
|
@receiver_thread = nil
|
955
1088
|
@receiver_thread_exception = nil
|
956
1089
|
@receiver_thread_terminating = false
|
1090
|
+
response_handlers&.each do add_response_handler(_1) end
|
957
1091
|
|
958
1092
|
# Client Protocol Sender (including state for currently running commands)
|
959
1093
|
@tag_prefix = "RUBY"
|
@@ -967,8 +1101,8 @@ module Net
|
|
967
1101
|
@logout_command_tag = nil
|
968
1102
|
|
969
1103
|
# Connection
|
970
|
-
@tls_verified = false
|
971
1104
|
@sock = tcp_socket(@host, @port)
|
1105
|
+
@reader = ResponseReader.new(self, @sock)
|
972
1106
|
start_tls_session if ssl_ctx
|
973
1107
|
start_imap_connection
|
974
1108
|
end
|
@@ -983,6 +1117,7 @@ module Net
|
|
983
1117
|
# Related: #logout, #logout!
|
984
1118
|
def disconnect
|
985
1119
|
return if disconnected?
|
1120
|
+
state_logout!
|
986
1121
|
begin
|
987
1122
|
begin
|
988
1123
|
# try to call SSL::SSLSocket#io.
|
@@ -1221,6 +1356,10 @@ module Net
|
|
1221
1356
|
# both successful. Any error indicates that the connection has not been
|
1222
1357
|
# secured.
|
1223
1358
|
#
|
1359
|
+
# After the server agrees to start a TLS connection, this method waits up to
|
1360
|
+
# {config.open_timeout}[rdoc-ref:Config#open_timeout] before raising
|
1361
|
+
# +Net::OpenTimeout+.
|
1362
|
+
#
|
1224
1363
|
# *Note:*
|
1225
1364
|
# >>>
|
1226
1365
|
# Any #response_handlers added before STARTTLS should be aware that the
|
@@ -1239,13 +1378,21 @@ module Net
|
|
1239
1378
|
#
|
1240
1379
|
def starttls(**options)
|
1241
1380
|
@ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
|
1242
|
-
|
1381
|
+
error = nil
|
1382
|
+
ok = send_command("STARTTLS") do |resp|
|
1243
1383
|
if resp.kind_of?(TaggedResponse) && resp.name == "OK"
|
1244
1384
|
clear_cached_capabilities
|
1245
1385
|
clear_responses
|
1246
1386
|
start_tls_session
|
1247
1387
|
end
|
1388
|
+
rescue Exception => error
|
1389
|
+
raise # note that the error backtrace is in the receiver_thread
|
1248
1390
|
end
|
1391
|
+
if error
|
1392
|
+
disconnect
|
1393
|
+
raise error
|
1394
|
+
end
|
1395
|
+
ok
|
1249
1396
|
end
|
1250
1397
|
|
1251
1398
|
# :call-seq:
|
@@ -1360,7 +1507,7 @@ module Net
|
|
1360
1507
|
# capabilities, they will be cached.
|
1361
1508
|
def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback)
|
1362
1509
|
sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback)
|
1363
|
-
.tap
|
1510
|
+
.tap do state_authenticated! _1 end
|
1364
1511
|
end
|
1365
1512
|
|
1366
1513
|
# Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
|
@@ -1394,7 +1541,7 @@ module Net
|
|
1394
1541
|
raise LoginDisabledError
|
1395
1542
|
end
|
1396
1543
|
send_command("LOGIN", user, password)
|
1397
|
-
.tap
|
1544
|
+
.tap do state_authenticated! _1 end
|
1398
1545
|
end
|
1399
1546
|
|
1400
1547
|
# Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
|
@@ -1434,8 +1581,10 @@ module Net
|
|
1434
1581
|
args = ["SELECT", mailbox]
|
1435
1582
|
args << ["CONDSTORE"] if condstore
|
1436
1583
|
synchronize do
|
1584
|
+
state_unselected! # implicitly closes current mailbox
|
1437
1585
|
@responses.clear
|
1438
1586
|
send_command(*args)
|
1587
|
+
.tap do state_selected! end
|
1439
1588
|
end
|
1440
1589
|
end
|
1441
1590
|
|
@@ -1452,8 +1601,10 @@ module Net
|
|
1452
1601
|
args = ["EXAMINE", mailbox]
|
1453
1602
|
args << ["CONDSTORE"] if condstore
|
1454
1603
|
synchronize do
|
1604
|
+
state_unselected! # implicitly closes current mailbox
|
1455
1605
|
@responses.clear
|
1456
1606
|
send_command(*args)
|
1607
|
+
.tap do state_selected! end
|
1457
1608
|
end
|
1458
1609
|
end
|
1459
1610
|
|
@@ -1892,6 +2043,7 @@ module Net
|
|
1892
2043
|
# Related: #unselect
|
1893
2044
|
def close
|
1894
2045
|
send_command("CLOSE")
|
2046
|
+
.tap do state_authenticated! end
|
1895
2047
|
end
|
1896
2048
|
|
1897
2049
|
# Sends an {UNSELECT command [RFC3691 §2]}[https://www.rfc-editor.org/rfc/rfc3691#section-3]
|
@@ -1908,6 +2060,7 @@ module Net
|
|
1908
2060
|
# [RFC3691[https://www.rfc-editor.org/rfc/rfc3691]].
|
1909
2061
|
def unselect
|
1910
2062
|
send_command("UNSELECT")
|
2063
|
+
.tap do state_authenticated! end
|
1911
2064
|
end
|
1912
2065
|
|
1913
2066
|
# call-seq:
|
@@ -3138,6 +3291,10 @@ module Net
|
|
3138
3291
|
# end
|
3139
3292
|
# }
|
3140
3293
|
#
|
3294
|
+
# Response handlers can also be added when the client is created before the
|
3295
|
+
# receiver thread is started, by the +response_handlers+ argument to ::new.
|
3296
|
+
# This ensures every server response is handled, including the #greeting.
|
3297
|
+
#
|
3141
3298
|
# Related: #remove_response_handler, #response_handlers
|
3142
3299
|
def add_response_handler(handler = nil, &block)
|
3143
3300
|
raise ArgumentError, "two Procs are passed" if handler && block
|
@@ -3164,8 +3321,10 @@ module Net
|
|
3164
3321
|
def start_imap_connection
|
3165
3322
|
@greeting = get_server_greeting
|
3166
3323
|
@capabilities = capabilities_from_resp_code @greeting
|
3324
|
+
@response_handlers.each do |handler| handler.call(@greeting) end
|
3167
3325
|
@receiver_thread = start_receiver_thread
|
3168
3326
|
rescue Exception
|
3327
|
+
state_logout!
|
3169
3328
|
@sock.close
|
3170
3329
|
raise
|
3171
3330
|
end
|
@@ -3174,7 +3333,10 @@ module Net
|
|
3174
3333
|
greeting = get_response
|
3175
3334
|
raise Error, "No server greeting - connection closed" unless greeting
|
3176
3335
|
record_untagged_response_code greeting
|
3177
|
-
|
3336
|
+
case greeting.name
|
3337
|
+
when "PREAUTH" then state_authenticated!
|
3338
|
+
when "BYE" then state_logout!; raise ByeResponseError, greeting
|
3339
|
+
end
|
3178
3340
|
greeting
|
3179
3341
|
end
|
3180
3342
|
|
@@ -3184,6 +3346,8 @@ module Net
|
|
3184
3346
|
rescue Exception => ex
|
3185
3347
|
@receiver_thread_exception = ex
|
3186
3348
|
# don't exit the thread with an exception
|
3349
|
+
ensure
|
3350
|
+
state_logout!
|
3187
3351
|
end
|
3188
3352
|
end
|
3189
3353
|
|
@@ -3206,6 +3370,7 @@ module Net
|
|
3206
3370
|
resp = get_response
|
3207
3371
|
rescue Exception => e
|
3208
3372
|
synchronize do
|
3373
|
+
state_logout!
|
3209
3374
|
@sock.close
|
3210
3375
|
@exception = e
|
3211
3376
|
end
|
@@ -3225,6 +3390,7 @@ module Net
|
|
3225
3390
|
@tagged_response_arrival.broadcast
|
3226
3391
|
case resp.tag
|
3227
3392
|
when @logout_command_tag
|
3393
|
+
state_logout!
|
3228
3394
|
return
|
3229
3395
|
when @continued_command_tag
|
3230
3396
|
@continuation_request_exception =
|
@@ -3234,6 +3400,7 @@ module Net
|
|
3234
3400
|
when UntaggedResponse
|
3235
3401
|
record_untagged_response(resp)
|
3236
3402
|
if resp.name == "BYE" && @logout_command_tag.nil?
|
3403
|
+
state_logout!
|
3237
3404
|
@sock.close
|
3238
3405
|
@exception = ByeResponseError.new(resp)
|
3239
3406
|
connection_closed = true
|
@@ -3241,6 +3408,7 @@ module Net
|
|
3241
3408
|
when ContinuationRequest
|
3242
3409
|
@continuation_request_arrival.signal
|
3243
3410
|
end
|
3411
|
+
state_unselected! if resp in {data: {code: {name: "CLOSED"}}}
|
3244
3412
|
@response_handlers.each do |handler|
|
3245
3413
|
handler.call(resp)
|
3246
3414
|
end
|
@@ -3292,23 +3460,10 @@ module Net
|
|
3292
3460
|
end
|
3293
3461
|
|
3294
3462
|
def get_response
|
3295
|
-
buff =
|
3296
|
-
while true
|
3297
|
-
s = @sock.gets(CRLF)
|
3298
|
-
break unless s
|
3299
|
-
buff.concat(s)
|
3300
|
-
if /\{(\d+)\}\r\n/n =~ s
|
3301
|
-
s = @sock.read($1.to_i)
|
3302
|
-
buff.concat(s)
|
3303
|
-
else
|
3304
|
-
break
|
3305
|
-
end
|
3306
|
-
end
|
3463
|
+
buff = @reader.read_response_buffer
|
3307
3464
|
return nil if buff.length == 0
|
3308
|
-
if config.debug?
|
3309
|
-
|
3310
|
-
end
|
3311
|
-
return @parser.parse(buff)
|
3465
|
+
$stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
|
3466
|
+
@parser.parse(buff)
|
3312
3467
|
end
|
3313
3468
|
|
3314
3469
|
#############################
|
@@ -3612,6 +3767,7 @@ module Net
|
|
3612
3767
|
raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
|
3613
3768
|
raise "cannot start TLS without SSLContext" unless ssl_ctx
|
3614
3769
|
@sock = SSLSocket.new(@sock, ssl_ctx)
|
3770
|
+
@reader = ResponseReader.new(self, @sock)
|
3615
3771
|
@sock.sync_close = true
|
3616
3772
|
@sock.hostname = @host if @sock.respond_to? :hostname=
|
3617
3773
|
ssl_socket_connect(@sock, open_timeout)
|
@@ -3621,6 +3777,29 @@ module Net
|
|
3621
3777
|
end
|
3622
3778
|
end
|
3623
3779
|
|
3780
|
+
def state_authenticated!(resp = nil)
|
3781
|
+
synchronize do
|
3782
|
+
@capabilities = capabilities_from_resp_code resp if resp
|
3783
|
+
@connection_state = ConnectionState::Authenticated.new
|
3784
|
+
end
|
3785
|
+
end
|
3786
|
+
|
3787
|
+
def state_selected!
|
3788
|
+
synchronize do
|
3789
|
+
@connection_state = ConnectionState::Selected.new
|
3790
|
+
end
|
3791
|
+
end
|
3792
|
+
|
3793
|
+
def state_unselected!
|
3794
|
+
state_authenticated! if connection_state.to_sym == :selected
|
3795
|
+
end
|
3796
|
+
|
3797
|
+
def state_logout!
|
3798
|
+
synchronize do
|
3799
|
+
@connection_state = ConnectionState::Logout.new
|
3800
|
+
end
|
3801
|
+
end
|
3802
|
+
|
3624
3803
|
def sasl_adapter
|
3625
3804
|
SASLAdapter.new(self, &method(:send_command_with_continuations))
|
3626
3805
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: net-imap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shugo Maeda
|
8
8
|
- nicholas a. evans
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-protocol
|
@@ -60,6 +60,7 @@ files:
|
|
60
60
|
- lib/net/imap/config/attr_accessors.rb
|
61
61
|
- lib/net/imap/config/attr_inheritance.rb
|
62
62
|
- lib/net/imap/config/attr_type_coercion.rb
|
63
|
+
- lib/net/imap/connection_state.rb
|
63
64
|
- lib/net/imap/data_encoding.rb
|
64
65
|
- lib/net/imap/data_lite.rb
|
65
66
|
- lib/net/imap/deprecated_client_options.rb
|
@@ -70,6 +71,7 @@ files:
|
|
70
71
|
- lib/net/imap/response_data.rb
|
71
72
|
- lib/net/imap/response_parser.rb
|
72
73
|
- lib/net/imap/response_parser/parser_utils.rb
|
74
|
+
- lib/net/imap/response_reader.rb
|
73
75
|
- lib/net/imap/sasl.rb
|
74
76
|
- lib/net/imap/sasl/anonymous_authenticator.rb
|
75
77
|
- lib/net/imap/sasl/authentication_exchange.rb
|
@@ -96,6 +98,7 @@ files:
|
|
96
98
|
- lib/net/imap/stringprep/saslprep_tables.rb
|
97
99
|
- lib/net/imap/stringprep/tables.rb
|
98
100
|
- lib/net/imap/stringprep/trace.rb
|
101
|
+
- lib/net/imap/uidplus_data.rb
|
99
102
|
- lib/net/imap/vanished_data.rb
|
100
103
|
- net-imap.gemspec
|
101
104
|
- rakelib/benchmarks.rake
|
@@ -126,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
129
|
- !ruby/object:Gem::Version
|
127
130
|
version: '0'
|
128
131
|
requirements: []
|
129
|
-
rubygems_version: 3.6.
|
132
|
+
rubygems_version: 3.6.7
|
130
133
|
specification_version: 4
|
131
134
|
summary: Ruby client api for Internet Message Access Protocol
|
132
135
|
test_files: []
|