net-imap 0.5.6 → 0.5.9

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.
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 "_selected_" state, and that
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. Enters the "_logout_" state.
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 "_logout_" state.
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 "<em>not authenticated</em>" state:
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 "_authenticated_" state.
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 generally preferred. Enters the "_authenticated_"
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 "_authenticated_" state:
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 "_selected_" state.
350
- # - #examine: Open a mailbox read-only, and enter the "_selected_" state.
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 "_authenticated_"
373
- # commands, the following commands are valid in the "_selected_" state:
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 "_authenticated_" state,
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 "_authenticated_" state,
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 "_logout_" state. If the socket is still
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 "_authenticated_" state,
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.6"
791
+ VERSION = "0.5.9"
748
792
 
749
793
  # Aliases for supported capabilities, to be used with the #enable command.
750
794
  ENABLE_ALIASES = {
@@ -752,9 +796,13 @@ module Net
752
796
  "UTF8=ONLY" => "UTF8=ACCEPT",
753
797
  }.freeze
754
798
 
755
- autoload :SASL, File.expand_path("imap/sasl", __dir__)
756
- autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
757
- autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
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
808
  if defined?(OpenSSL::SSL)
@@ -762,13 +810,31 @@ module Net
762
810
  include SSL
763
811
  end
764
812
 
813
+ # :call-seq:
814
+ # Net::IMAP::SequenceSet(set = nil) -> SequenceSet
815
+ #
816
+ # Coerces +set+ into a SequenceSet, using either SequenceSet.try_convert or
817
+ # SequenceSet.new.
818
+ #
819
+ # * When +set+ is a SequenceSet, that same set is returned.
820
+ # * When +set+ responds to +to_sequence_set+, +set.to_sequence_set+ is
821
+ # returned.
822
+ # * Otherwise, returns the result from calling SequenceSet.new with +set+.
823
+ #
824
+ # Related: SequenceSet.try_convert, SequenceSet.new, SequenceSet::[]
825
+ def self.SequenceSet(set = nil)
826
+ SequenceSet.try_convert(set) || SequenceSet.new(set)
827
+ end
828
+
765
829
  # Returns the global Config object
766
830
  def self.config; Config.global end
767
831
 
768
832
  # Returns the global debug mode.
833
+ # Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
769
834
  def self.debug; config.debug end
770
835
 
771
836
  # Sets the global debug mode.
837
+ # Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
772
838
  def self.debug=(val)
773
839
  config.debug = val
774
840
  end
@@ -789,7 +855,7 @@ module Net
789
855
  alias default_ssl_port default_tls_port
790
856
  end
791
857
 
792
- # Returns the initial greeting the server, an UntaggedResponse.
858
+ # Returns the initial greeting sent by the server, an UntaggedResponse.
793
859
  attr_reader :greeting
794
860
 
795
861
  # The client configuration. See Net::IMAP::Config.
@@ -798,13 +864,28 @@ module Net
798
864
  # Net::IMAP.config.
799
865
  attr_reader :config
800
866
 
801
- # Seconds to wait until a connection is opened.
802
- # If the IMAP object cannot open a connection within this time,
803
- # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
804
- def open_timeout; config.open_timeout end
867
+ ##
868
+ # :attr_reader: open_timeout
869
+ # Seconds to wait until a connection is opened. Also used by #starttls.
870
+ # Delegates to {config.open_timeout}[rdoc-ref:Config#open_timeout].
805
871
 
872
+ ##
873
+ # :attr_reader: idle_response_timeout
806
874
  # Seconds to wait until an IDLE response is received.
807
- def idle_response_timeout; config.idle_response_timeout end
875
+ # Delegates to {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout].
876
+
877
+ ##
878
+ # :attr_accessor: max_response_size
879
+ #
880
+ # The maximum allowed server response size, in bytes.
881
+ # Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size].
882
+
883
+ # :stopdoc:
884
+ def open_timeout; config.open_timeout end
885
+ def idle_response_timeout; config.idle_response_timeout end
886
+ def max_response_size; config.max_response_size end
887
+ def max_response_size=(val) config.max_response_size = val end
888
+ # :startdoc:
808
889
 
809
890
  # The hostname this client connected to
810
891
  attr_reader :host
@@ -827,6 +908,67 @@ module Net
827
908
  # Returns +false+ for a plaintext connection.
828
909
  attr_reader :ssl_ctx_params
829
910
 
911
+ # Returns the current connection state.
912
+ #
913
+ # Once an IMAP connection is established, the connection is in one of four
914
+ # states: +not_authenticated+, +authenticated+, +selected+, and +logout+.
915
+ # Most commands are valid only in certain states.
916
+ #
917
+ # The connection state object responds to +to_sym+ and +name+ with the name
918
+ # of the current connection state, as a Symbol or String. Future versions
919
+ # of +net-imap+ may store additional information on the state object.
920
+ #
921
+ # From {RFC9051}[https://www.rfc-editor.org/rfc/rfc9051#section-3]:
922
+ # +----------------------+
923
+ # |connection established|
924
+ # +----------------------+
925
+ # ||
926
+ # \/
927
+ # +--------------------------------------+
928
+ # | server greeting |
929
+ # +--------------------------------------+
930
+ # || (1) || (2) || (3)
931
+ # \/ || ||
932
+ # +-----------------+ || ||
933
+ # |Not Authenticated| || ||
934
+ # +-----------------+ || ||
935
+ # || (7) || (4) || ||
936
+ # || \/ \/ ||
937
+ # || +----------------+ ||
938
+ # || | Authenticated |<=++ ||
939
+ # || +----------------+ || ||
940
+ # || || (7) || (5) || (6) ||
941
+ # || || \/ || ||
942
+ # || || +--------+ || ||
943
+ # || || |Selected|==++ ||
944
+ # || || +--------+ ||
945
+ # || || || (7) ||
946
+ # \/ \/ \/ \/
947
+ # +--------------------------------------+
948
+ # | Logout |
949
+ # +--------------------------------------+
950
+ # ||
951
+ # \/
952
+ # +-------------------------------+
953
+ # |both sides close the connection|
954
+ # +-------------------------------+
955
+ #
956
+ # >>>
957
+ # Legend for the above diagram:
958
+ #
959
+ # 1. connection without pre-authentication (+OK+ #greeting)
960
+ # 2. pre-authenticated connection (+PREAUTH+ #greeting)
961
+ # 3. rejected connection (+BYE+ #greeting)
962
+ # 4. successful #login or #authenticate command
963
+ # 5. successful #select or #examine command
964
+ # 6. #close or #unselect command, unsolicited +CLOSED+ response code, or
965
+ # failed #select or #examine command
966
+ # 7. #logout command, server shutdown, or connection closed
967
+ #
968
+ # Before the server greeting, the state is +not_authenticated+.
969
+ # After the connection closes, the state remains +logout+.
970
+ attr_reader :connection_state
971
+
830
972
  # Creates a new Net::IMAP object and connects it to the specified
831
973
  # +host+.
832
974
  #
@@ -860,6 +1002,12 @@ module Net
860
1002
  #
861
1003
  # See DeprecatedClientOptions.new for deprecated SSL arguments.
862
1004
  #
1005
+ # [response_handlers]
1006
+ # A list of response handlers to be added before the receiver thread is
1007
+ # started. This ensures every server response is handled, including the
1008
+ # #greeting. Note that the greeting is handled in the current thread, but
1009
+ # all other responses are handled in the receiver thread.
1010
+ #
863
1011
  # [config]
864
1012
  # A Net::IMAP::Config object to use as the basis for #config. By default,
865
1013
  # the global Net::IMAP.config is used.
@@ -931,7 +1079,7 @@ module Net
931
1079
  # [Net::IMAP::ByeResponseError]
932
1080
  # Connected to the host successfully, but it immediately said goodbye.
933
1081
  #
934
- def initialize(host, port: nil, ssl: nil,
1082
+ def initialize(host, port: nil, ssl: nil, response_handlers: nil,
935
1083
  config: Config.global, **config_options)
936
1084
  super()
937
1085
  # Config options
@@ -946,6 +1094,8 @@ module Net
946
1094
  @exception = nil
947
1095
  @greeting = nil
948
1096
  @capabilities = nil
1097
+ @tls_verified = false
1098
+ @connection_state = ConnectionState::NotAuthenticated.new
949
1099
 
950
1100
  # Client Protocol Receiver
951
1101
  @parser = ResponseParser.new(config: @config)
@@ -954,6 +1104,7 @@ module Net
954
1104
  @receiver_thread = nil
955
1105
  @receiver_thread_exception = nil
956
1106
  @receiver_thread_terminating = false
1107
+ response_handlers&.each do add_response_handler(_1) end
957
1108
 
958
1109
  # Client Protocol Sender (including state for currently running commands)
959
1110
  @tag_prefix = "RUBY"
@@ -967,8 +1118,8 @@ module Net
967
1118
  @logout_command_tag = nil
968
1119
 
969
1120
  # Connection
970
- @tls_verified = false
971
1121
  @sock = tcp_socket(@host, @port)
1122
+ @reader = ResponseReader.new(self, @sock)
972
1123
  start_tls_session if ssl_ctx
973
1124
  start_imap_connection
974
1125
  end
@@ -980,27 +1131,27 @@ module Net
980
1131
 
981
1132
  # Disconnects from the server.
982
1133
  #
1134
+ # Waits for receiver thread to close before returning. Slow or stuck
1135
+ # response handlers can cause #disconnect to hang until they complete.
1136
+ #
983
1137
  # Related: #logout, #logout!
984
1138
  def disconnect
1139
+ in_logout_state = try_state_logout?
985
1140
  return if disconnected?
986
1141
  begin
987
- begin
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
1142
+ @sock.to_io.shutdown
994
1143
  rescue Errno::ENOTCONN
995
1144
  # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms.
996
1145
  rescue Exception => e
997
1146
  @receiver_thread.raise(e)
998
1147
  end
1148
+ @sock.close
999
1149
  @receiver_thread.join
1000
- synchronize do
1001
- @sock.close
1002
- end
1003
1150
  raise e if e
1151
+ ensure
1152
+ # Try again after shutting down the receiver thread. With no reciever
1153
+ # left to wait for, any remaining locks should be _very_ brief.
1154
+ state_logout! unless in_logout_state
1004
1155
  end
1005
1156
 
1006
1157
  # Returns true if disconnected from the server.
@@ -1221,6 +1372,10 @@ module Net
1221
1372
  # both successful. Any error indicates that the connection has not been
1222
1373
  # secured.
1223
1374
  #
1375
+ # After the server agrees to start a TLS connection, this method waits up to
1376
+ # {config.open_timeout}[rdoc-ref:Config#open_timeout] before raising
1377
+ # +Net::OpenTimeout+.
1378
+ #
1224
1379
  # *Note:*
1225
1380
  # >>>
1226
1381
  # Any #response_handlers added before STARTTLS should be aware that the
@@ -1368,7 +1523,7 @@ module Net
1368
1523
  # capabilities, they will be cached.
1369
1524
  def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback)
1370
1525
  sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback)
1371
- .tap { @capabilities = capabilities_from_resp_code _1 }
1526
+ .tap do state_authenticated! _1 end
1372
1527
  end
1373
1528
 
1374
1529
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1402,7 +1557,7 @@ module Net
1402
1557
  raise LoginDisabledError
1403
1558
  end
1404
1559
  send_command("LOGIN", user, password)
1405
- .tap { @capabilities = capabilities_from_resp_code _1 }
1560
+ .tap do state_authenticated! _1 end
1406
1561
  end
1407
1562
 
1408
1563
  # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
@@ -1442,8 +1597,10 @@ module Net
1442
1597
  args = ["SELECT", mailbox]
1443
1598
  args << ["CONDSTORE"] if condstore
1444
1599
  synchronize do
1600
+ state_unselected! # implicitly closes current mailbox
1445
1601
  @responses.clear
1446
1602
  send_command(*args)
1603
+ .tap do state_selected! end
1447
1604
  end
1448
1605
  end
1449
1606
 
@@ -1460,8 +1617,10 @@ module Net
1460
1617
  args = ["EXAMINE", mailbox]
1461
1618
  args << ["CONDSTORE"] if condstore
1462
1619
  synchronize do
1620
+ state_unselected! # implicitly closes current mailbox
1463
1621
  @responses.clear
1464
1622
  send_command(*args)
1623
+ .tap do state_selected! end
1465
1624
  end
1466
1625
  end
1467
1626
 
@@ -1900,6 +2059,7 @@ module Net
1900
2059
  # Related: #unselect
1901
2060
  def close
1902
2061
  send_command("CLOSE")
2062
+ .tap do state_authenticated! end
1903
2063
  end
1904
2064
 
1905
2065
  # Sends an {UNSELECT command [RFC3691 §2]}[https://www.rfc-editor.org/rfc/rfc3691#section-3]
@@ -1916,6 +2076,7 @@ module Net
1916
2076
  # [RFC3691[https://www.rfc-editor.org/rfc/rfc3691]].
1917
2077
  def unselect
1918
2078
  send_command("UNSELECT")
2079
+ .tap do state_authenticated! end
1919
2080
  end
1920
2081
 
1921
2082
  # call-seq:
@@ -2917,8 +3078,8 @@ module Net
2917
3078
  raise @exception || Net::IMAP::Error.new("connection closed")
2918
3079
  end
2919
3080
  ensure
3081
+ remove_response_handler(response_handler)
2920
3082
  unless @receiver_thread_terminating
2921
- remove_response_handler(response_handler)
2922
3083
  put_string("DONE#{CRLF}")
2923
3084
  response = get_tagged_response(tag, "IDLE", idle_response_timeout)
2924
3085
  end
@@ -3146,6 +3307,10 @@ module Net
3146
3307
  # end
3147
3308
  # }
3148
3309
  #
3310
+ # Response handlers can also be added when the client is created before the
3311
+ # receiver thread is started, by the +response_handlers+ argument to ::new.
3312
+ # This ensures every server response is handled, including the #greeting.
3313
+ #
3149
3314
  # Related: #remove_response_handler, #response_handlers
3150
3315
  def add_response_handler(handler = nil, &block)
3151
3316
  raise ArgumentError, "two Procs are passed" if handler && block
@@ -3172,8 +3337,10 @@ module Net
3172
3337
  def start_imap_connection
3173
3338
  @greeting = get_server_greeting
3174
3339
  @capabilities = capabilities_from_resp_code @greeting
3340
+ @response_handlers.each do |handler| handler.call(@greeting) end
3175
3341
  @receiver_thread = start_receiver_thread
3176
3342
  rescue Exception
3343
+ state_logout!
3177
3344
  @sock.close
3178
3345
  raise
3179
3346
  end
@@ -3182,7 +3349,10 @@ module Net
3182
3349
  greeting = get_response
3183
3350
  raise Error, "No server greeting - connection closed" unless greeting
3184
3351
  record_untagged_response_code greeting
3185
- raise ByeResponseError, greeting if greeting.name == "BYE"
3352
+ case greeting.name
3353
+ when "PREAUTH" then state_authenticated!
3354
+ when "BYE" then state_logout!; raise ByeResponseError, greeting
3355
+ end
3186
3356
  greeting
3187
3357
  end
3188
3358
 
@@ -3214,6 +3384,7 @@ module Net
3214
3384
  resp = get_response
3215
3385
  rescue Exception => e
3216
3386
  synchronize do
3387
+ state_logout!
3217
3388
  @sock.close
3218
3389
  @exception = e
3219
3390
  end
@@ -3233,6 +3404,7 @@ module Net
3233
3404
  @tagged_response_arrival.broadcast
3234
3405
  case resp.tag
3235
3406
  when @logout_command_tag
3407
+ state_logout!
3236
3408
  return
3237
3409
  when @continued_command_tag
3238
3410
  @continuation_request_exception =
@@ -3242,6 +3414,7 @@ module Net
3242
3414
  when UntaggedResponse
3243
3415
  record_untagged_response(resp)
3244
3416
  if resp.name == "BYE" && @logout_command_tag.nil?
3417
+ state_logout!
3245
3418
  @sock.close
3246
3419
  @exception = ByeResponseError.new(resp)
3247
3420
  connection_closed = true
@@ -3249,6 +3422,7 @@ module Net
3249
3422
  when ContinuationRequest
3250
3423
  @continuation_request_arrival.signal
3251
3424
  end
3425
+ state_unselected! if resp in {data: {code: {name: "CLOSED"}}}
3252
3426
  @response_handlers.each do |handler|
3253
3427
  handler.call(resp)
3254
3428
  end
@@ -3269,6 +3443,8 @@ module Net
3269
3443
  @idle_done_cond.signal
3270
3444
  end
3271
3445
  end
3446
+ ensure
3447
+ state_logout!
3272
3448
  end
3273
3449
 
3274
3450
  def get_tagged_response(tag, cmd, timeout = nil)
@@ -3300,23 +3476,10 @@ module Net
3300
3476
  end
3301
3477
 
3302
3478
  def get_response
3303
- buff = String.new
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
3479
+ buff = @reader.read_response_buffer
3315
3480
  return nil if buff.length == 0
3316
- if config.debug?
3317
- $stderr.print(buff.gsub(/^/n, "S: "))
3318
- end
3319
- return @parser.parse(buff)
3481
+ $stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
3482
+ @parser.parse(buff)
3320
3483
  end
3321
3484
 
3322
3485
  #############################
@@ -3620,6 +3783,7 @@ module Net
3620
3783
  raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
3621
3784
  raise "cannot start TLS without SSLContext" unless ssl_ctx
3622
3785
  @sock = SSLSocket.new(@sock, ssl_ctx)
3786
+ @reader = ResponseReader.new(self, @sock)
3623
3787
  @sock.sync_close = true
3624
3788
  @sock.hostname = @host if @sock.respond_to? :hostname=
3625
3789
  ssl_socket_connect(@sock, open_timeout)
@@ -3629,6 +3793,43 @@ module Net
3629
3793
  end
3630
3794
  end
3631
3795
 
3796
+ def state_authenticated!(resp = nil)
3797
+ synchronize do
3798
+ @capabilities = capabilities_from_resp_code resp if resp
3799
+ @connection_state = ConnectionState::Authenticated.new
3800
+ end
3801
+ end
3802
+
3803
+ def state_selected!
3804
+ synchronize do
3805
+ @connection_state = ConnectionState::Selected.new
3806
+ end
3807
+ end
3808
+
3809
+ def state_unselected!
3810
+ synchronize do
3811
+ state_authenticated! if connection_state.to_sym == :selected
3812
+ end
3813
+ end
3814
+
3815
+ def state_logout!
3816
+ return true if connection_state in [:logout, *]
3817
+ synchronize do
3818
+ return true if connection_state in [:logout, *]
3819
+ @connection_state = ConnectionState::Logout.new
3820
+ end
3821
+ end
3822
+
3823
+ # don't wait to aqcuire the lock
3824
+ def try_state_logout?
3825
+ return true if connection_state in [:logout, *]
3826
+ return false unless acquired_lock = mon_try_enter
3827
+ state_logout!
3828
+ true
3829
+ ensure
3830
+ mon_exit if acquired_lock
3831
+ end
3832
+
3632
3833
  def sasl_adapter
3633
3834
  SASLAdapter.new(self, &method(:send_command_with_continuations))
3634
3835
  end
@@ -388,9 +388,11 @@ class StringPrepTablesGenerator
388
388
  end
389
389
 
390
390
  def asgn_mapping(name, replacement = to_map(tables[name]))
391
+ indent = " " * 2
392
+ replacement = replacement.inspect.gsub(/" => "/, '"=>"')
391
393
  cname = name.tr(?., ?_).upcase
392
- "# Replacements for %s\n%s%s = %p.freeze" % [
393
- "IN_#{name}", " " * 2, "MAP_#{cname}", replacement,
394
+ "# Replacements for %s\n%s%s = %s.freeze" % [
395
+ "IN_#{name}", indent, "MAP_#{cname}", replacement,
394
396
  ]
395
397
  end
396
398
 
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.6
4
+ version: 0.5.9
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: 2025-02-07 00:00:00.000000000 Z
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
@@ -127,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
129
  - !ruby/object:Gem::Version
128
130
  version: '0'
129
131
  requirements: []
130
- rubygems_version: 3.6.2
132
+ rubygems_version: 3.6.7
131
133
  specification_version: 4
132
134
  summary: Ruby client api for Internet Message Access Protocol
133
135
  test_files: []