net-imap 0.4.25 → 0.5.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.
data/lib/net/imap.rb CHANGED
@@ -43,16 +43,10 @@ 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
50
  # === Sequence numbers and UIDs
57
51
  #
58
52
  # Messages have two sorts of identifiers: message sequence
@@ -205,42 +199,6 @@ module Net
205
199
  #
206
200
  # This script invokes the FETCH command and the SEARCH command concurrently.
207
201
  #
208
- # When running multiple commands, care must be taken to avoid ambiguity. For
209
- # example, SEARCH responses are ambiguous about which command they are
210
- # responding to, so search commands should not run simultaneously, unless the
211
- # server supports +ESEARCH+ {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731] or
212
- # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]. See {RFC9051
213
- # §5.5}[https://www.rfc-editor.org/rfc/rfc9051.html#section-5.5] for
214
- # other examples of command sequences which should not be pipelined.
215
- #
216
- # == Unbounded memory use
217
- #
218
- # Net::IMAP reads server responses in a separate receiver thread per client.
219
- # Unhandled response data is saved to #responses, and response_handlers run
220
- # inside the receiver thread. See the list of methods for {handling server
221
- # responses}[rdoc-ref:Net::IMAP@Handling+server+responses], below.
222
- #
223
- # Because the receiver thread continuously reads and saves new responses, some
224
- # scenarios must be careful to avoid unbounded memory use:
225
- #
226
- # * Commands such as #list or #fetch can have an enormous number of responses.
227
- # * Commands such as #fetch can result in an enormous size per response.
228
- # * Long-lived connections will gradually accumulate unsolicited server
229
- # responses, especially +EXISTS+, +FETCH+, and +EXPUNGE+ responses.
230
- # * A buggy or untrusted server could send inappropriate responses, which
231
- # could be very numerous, very large, and very rapid.
232
- #
233
- # Use paginated or limited versions of commands whenever possible.
234
- #
235
- # Use Config#max_response_size to impose a limit on incoming server responses
236
- # as they are being read. <em>This is especially important for untrusted
237
- # servers.</em>
238
- #
239
- # Use #add_response_handler to handle responses after each one is received.
240
- # Use the +response_handlers+ argument to ::new to assign response handlers
241
- # before the receiver thread is started. Use #extract_responses,
242
- # #clear_responses, or #responses (with a block) to prune responses.
243
- #
244
202
  # == Errors
245
203
  #
246
204
  # An \IMAP server can send three different types of responses to indicate
@@ -302,9 +260,8 @@ module Net
302
260
  #
303
261
  # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
304
262
  # waits for a successful server greeting before the method returns.
305
- # - #connection_state: Returns the connection state.
306
263
  # - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
307
- # - #logout: Tells the server to end the session. Enters the +logout+ state.
264
+ # - #logout: Tells the server to end the session. Enters the "_logout_" state.
308
265
  # - #disconnect: Disconnects the connection (without sending #logout first).
309
266
  # - #disconnected?: True if the connection has been closed.
310
267
  #
@@ -360,36 +317,37 @@ module Net
360
317
  # <em>In general, #capable? should be used rather than explicitly sending a
361
318
  # +CAPABILITY+ command to the server.</em>
362
319
  # - #noop: Allows the server to send unsolicited untagged #responses.
363
- # - #logout: Tells the server to end the session. Enters the +logout+ state.
320
+ # - #logout: Tells the server to end the session. Enters the "_logout_" state.
364
321
  #
365
322
  # ==== Not Authenticated state
366
323
  #
367
324
  # In addition to the commands for any state, the following commands are valid
368
- # in the +not_authenticated+ state:
325
+ # in the "<em>not authenticated</em>" state:
369
326
  #
370
327
  # - #starttls: Upgrades a clear-text connection to use TLS.
371
328
  #
372
329
  # <em>Requires the +STARTTLS+ capability.</em>
373
330
  # - #authenticate: Identifies the client to the server using the given
374
331
  # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
375
- # and credentials. Enters the +authenticated+ state.
332
+ # and credentials. Enters the "_authenticated_" state.
376
333
  #
377
334
  # <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
378
335
  # supported mechanisms.</em>
379
336
  # - #login: Identifies the client to the server using a plain text password.
380
- # Using #authenticate is preferred. Enters the +authenticated+ state.
337
+ # Using #authenticate is generally preferred. Enters the "_authenticated_"
338
+ # state.
381
339
  #
382
340
  # <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
383
341
  #
384
342
  # ==== Authenticated state
385
343
  #
386
344
  # In addition to the commands for any state, the following commands are valid
387
- # in the +authenticated+ state:
345
+ # in the "_authenticated_" state:
388
346
  #
389
347
  # - #enable: Enables backwards incompatible server extensions.
390
348
  # <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
391
- # - #select: Open a mailbox and enter the +selected+ state.
392
- # - #examine: Open a mailbox read-only, and enter the +selected+ state.
349
+ # - #select: Open a mailbox and enter the "_selected_" state.
350
+ # - #examine: Open a mailbox read-only, and enter the "_selected_" state.
393
351
  # - #create: Creates a new mailbox.
394
352
  # - #delete: Permanently remove a mailbox.
395
353
  # - #rename: Change the name of a mailbox.
@@ -411,12 +369,12 @@ module Net
411
369
  #
412
370
  # ==== Selected state
413
371
  #
414
- # In addition to the commands for any state and the +authenticated+
415
- # commands, the following commands are valid in the +selected+ state:
372
+ # In addition to the commands for any state and the "_authenticated_"
373
+ # commands, the following commands are valid in the "_selected_" state:
416
374
  #
417
- # - #close: Closes the mailbox and returns to the +authenticated+ state,
375
+ # - #close: Closes the mailbox and returns to the "_authenticated_" state,
418
376
  # expunging deleted messages, unless the mailbox was opened as read-only.
419
- # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
377
+ # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
420
378
  # without expunging any messages.
421
379
  # <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
422
380
  # - #expunge: Permanently removes messages which have the Deleted flag set.
@@ -437,7 +395,7 @@ module Net
437
395
  #
438
396
  # ==== Logout state
439
397
  #
440
- # No \IMAP commands are valid in the +logout+ state. If the socket is still
398
+ # No \IMAP commands are valid in the "_logout_" state. If the socket is still
441
399
  # open, Net::IMAP will close it after receiving server confirmation.
442
400
  # Exceptions will be raised by \IMAP commands that have already started and
443
401
  # are waiting for a response, as well as any that are called after logout.
@@ -460,9 +418,6 @@ module Net
460
418
  # +LITERAL-+, and +SPECIAL-USE+.</em>
461
419
  #
462
420
  # ==== RFC2087: +QUOTA+
463
- # +NOTE:+ Only the +STORAGE+ quota resource type is currently supported.
464
- # - Obsoleted by <tt>QUOTA=RES-*</tt> [RFC9208[https://www.rfc-editor.org/rfc/rfc9208]],
465
- # although the commands are backward compatible.
466
421
  # - #getquota: returns the resource usage and limits for a quota root
467
422
  # - #getquotaroot: returns the list of quota roots for a mailbox, as well as
468
423
  # their resource usage and limits.
@@ -494,7 +449,7 @@ module Net
494
449
  # ==== RFC3691: +UNSELECT+
495
450
  # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
496
451
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
497
- # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
452
+ # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
498
453
  # without expunging any messages.
499
454
  #
500
455
  # ==== RFC4314: +ACL+
@@ -575,16 +530,6 @@ module Net
575
530
  # See FetchData#emailid and FetchData#emailid.
576
531
  # - Updates #status with support for the +MAILBOXID+ status attribute.
577
532
  #
578
- # ==== RFC9208: <tt>QUOTA=RES-*</tt>
579
- # +NOTE:+ Only the +STORAGE+ quota resource type is currently supported.
580
- # - Obsoletes the +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
581
- # extension and provides strict semantics for different resource types.
582
- # - #getquota: returns the resource usage and limits for a quota root
583
- # - #getquotaroot: returns the list of quota roots for a mailbox, as well as
584
- # their resource usage and limits.
585
- # - #setquota: sets the resource limits for a given quota root.
586
- # - Updates #status with <tt>"DELETED"</tt> and +DELETED-STORAGE+ attributes.
587
- #
588
533
  # == References
589
534
  #
590
535
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
@@ -694,13 +639,14 @@ module Net
694
639
  #
695
640
  # === \IMAP Extensions
696
641
  #
697
- # [QUOTA[https://www.rfc-editor.org/rfc/rfc2087]]::
698
- # Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
699
- # January 1997, <https://www.rfc-editor.org/info/rfc2087>.
642
+ # [QUOTA[https://tools.ietf.org/html/rfc9208]]::
643
+ # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
644
+ # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
700
645
  #
701
- # *NOTE*: _obsoleted_ by RFC9208[https://www.rfc-editor.org/rfc/rfc9208]
702
- # (March 2022).
703
- # [IDLE[https://www.rfc-editor.org/rfc/rfc2177]]::
646
+ # <em>Note: obsoletes</em>
647
+ # RFC-2087[https://tools.ietf.org/html/rfc2087]<em> (January 1997)</em>.
648
+ # <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
649
+ # [IDLE[https://tools.ietf.org/html/rfc2177]]::
704
650
  # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
705
651
  # June 1997, <https://www.rfc-editor.org/info/rfc2177>.
706
652
  # [NAMESPACE[https://tools.ietf.org/html/rfc2342]]::
@@ -751,15 +697,9 @@ module Net
751
697
  # Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
752
698
  # RFC 8474, DOI 10.17487/RFC8474, September 2018,
753
699
  # <https://www.rfc-editor.org/info/rfc8474>.
754
- # [{QUOTA=RES-*}[https://www.rfc-editor.org/rfc/rfc9208]]::
755
- # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
756
- # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
757
- #
758
- # Obsoletes RFC2087[https://www.rfc-editor.org/rfc/rfc2087].
759
700
  #
760
701
  # === IANA registries
761
702
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
762
- # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
763
703
  # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
764
704
  # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
765
705
  # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
@@ -779,7 +719,7 @@ module Net
779
719
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
780
720
  #
781
721
  class IMAP < Protocol
782
- VERSION = "0.4.25"
722
+ VERSION = "0.5.0"
783
723
 
784
724
  # Aliases for supported capabilities, to be used with the #enable command.
785
725
  ENABLE_ALIASES = {
@@ -787,7 +727,6 @@ module Net
787
727
  "UTF8=ONLY" => "UTF8=ACCEPT",
788
728
  }.freeze
789
729
 
790
- autoload :ResponseReader, File.expand_path("imap/response_reader", __dir__)
791
730
  autoload :SASL, File.expand_path("imap/sasl", __dir__)
792
731
  autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
793
732
  autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
@@ -802,11 +741,9 @@ module Net
802
741
  def self.config; Config.global end
803
742
 
804
743
  # Returns the global debug mode.
805
- # Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
806
744
  def self.debug; config.debug end
807
745
 
808
746
  # Sets the global debug mode.
809
- # Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
810
747
  def self.debug=(val)
811
748
  config.debug = val
812
749
  end
@@ -827,7 +764,7 @@ module Net
827
764
  alias default_ssl_port default_tls_port
828
765
  end
829
766
 
830
- # Returns the initial greeting sent by the server, an UntaggedResponse.
767
+ # Returns the initial greeting the server, an UntaggedResponse.
831
768
  attr_reader :greeting
832
769
 
833
770
  # The client configuration. See Net::IMAP::Config.
@@ -836,28 +773,13 @@ module Net
836
773
  # Net::IMAP.config.
837
774
  attr_reader :config
838
775
 
839
- ##
840
- # :attr_reader: open_timeout
841
- # Seconds to wait until a connection is opened. Also used by #starttls.
842
- # Delegates to {config.open_timeout}[rdoc-ref:Config#open_timeout].
776
+ # Seconds to wait until a connection is opened.
777
+ # If the IMAP object cannot open a connection within this time,
778
+ # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
779
+ def open_timeout; config.open_timeout end
843
780
 
844
- ##
845
- # :attr_reader: idle_response_timeout
846
781
  # Seconds to wait until an IDLE response is received.
847
- # Delegates to {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout].
848
-
849
- ##
850
- # :attr_accessor: max_response_size
851
- #
852
- # The maximum allowed server response size, in bytes.
853
- # Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size].
854
-
855
- # :stopdoc:
856
- def open_timeout; config.open_timeout end
857
- def idle_response_timeout; config.idle_response_timeout end
858
- def max_response_size; config.max_response_size end
859
- def max_response_size=(val) config.max_response_size = val end
860
- # :startdoc:
782
+ def idle_response_timeout; config.idle_response_timeout end
861
783
 
862
784
  # The hostname this client connected to
863
785
  attr_reader :host
@@ -913,12 +835,6 @@ module Net
913
835
  #
914
836
  # See DeprecatedClientOptions.new for deprecated SSL arguments.
915
837
  #
916
- # [response_handlers]
917
- # A list of response handlers to be added before the receiver thread is
918
- # started. This ensures every server response is handled, including the
919
- # #greeting. Note that the greeting is handled in the current thread, but
920
- # all other responses are handled in the receiver thread.
921
- #
922
838
  # [config]
923
839
  # A Net::IMAP::Config object to use as the basis for #config. By default,
924
840
  # the global Net::IMAP.config is used.
@@ -990,7 +906,7 @@ module Net
990
906
  # [Net::IMAP::ByeResponseError]
991
907
  # Connected to the host successfully, but it immediately said goodbye.
992
908
  #
993
- def initialize(host, port: nil, ssl: nil, response_handlers: nil,
909
+ def initialize(host, port: nil, ssl: nil,
994
910
  config: Config.global, **config_options)
995
911
  super()
996
912
  # Config options
@@ -1013,7 +929,6 @@ module Net
1013
929
  @receiver_thread = nil
1014
930
  @receiver_thread_exception = nil
1015
931
  @receiver_thread_terminating = false
1016
- response_handlers&.each do add_response_handler(_1) end
1017
932
 
1018
933
  # Client Protocol Sender (including state for currently running commands)
1019
934
  @tag_prefix = "RUBY"
@@ -1029,12 +944,8 @@ module Net
1029
944
  # Connection
1030
945
  @tls_verified = false
1031
946
  @sock = tcp_socket(@host, @port)
1032
- @reader = ResponseReader.new(self, @sock)
1033
947
  start_tls_session if ssl_ctx
1034
948
  start_imap_connection
1035
-
1036
- # DEPRECATED: to remove in next version
1037
- @client_thread = Thread.current
1038
949
  end
1039
950
 
1040
951
  # Returns true after the TLS negotiation has completed and the remote
@@ -1042,29 +953,28 @@ module Net
1042
953
  # but peer verification was disabled.
1043
954
  def tls_verified?; @tls_verified end
1044
955
 
1045
- def client_thread # :nodoc:
1046
- warn "Net::IMAP#client_thread is deprecated and will be removed soon."
1047
- @client_thread
1048
- end
1049
-
1050
956
  # Disconnects from the server.
1051
957
  #
1052
- # Waits for receiver thread to close before returning. Slow or stuck
1053
- # response handlers can cause #disconnect to hang until they complete.
1054
- #
1055
958
  # Related: #logout, #logout!
1056
959
  def disconnect
1057
960
  return if disconnected?
1058
- in_receiver_thread = Thread.current == @receiver_thread
1059
961
  begin
1060
- @sock.to_io.shutdown
962
+ begin
963
+ # try to call SSL::SSLSocket#io.
964
+ @sock.io.shutdown
965
+ rescue NoMethodError
966
+ # @sock is not an SSL::SSLSocket.
967
+ @sock.shutdown
968
+ end
1061
969
  rescue Errno::ENOTCONN
1062
970
  # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms.
1063
971
  rescue Exception => e
1064
- @receiver_thread.raise(e) unless in_receiver_thread
972
+ @receiver_thread.raise(e)
973
+ end
974
+ @receiver_thread.join
975
+ synchronize do
976
+ @sock.close
1065
977
  end
1066
- @sock.close
1067
- @receiver_thread.join unless mon_owned? || in_receiver_thread
1068
978
  raise e if e
1069
979
  end
1070
980
 
@@ -1286,10 +1196,6 @@ module Net
1286
1196
  # both successful. Any error indicates that the connection has not been
1287
1197
  # secured.
1288
1198
  #
1289
- # After the server agrees to start a TLS connection, this method waits up to
1290
- # {config.open_timeout}[rdoc-ref:Config#open_timeout] before raising
1291
- # +Net::OpenTimeout+.
1292
- #
1293
1199
  # *Note:*
1294
1200
  # >>>
1295
1201
  # Any #response_handlers added before STARTTLS should be aware that the
@@ -1308,30 +1214,13 @@ module Net
1308
1214
  #
1309
1215
  def starttls(**options)
1310
1216
  @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
1311
- handled = false
1312
- error = nil
1313
- ok = send_command("STARTTLS") do |resp|
1217
+ send_command("STARTTLS") do |resp|
1314
1218
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
1315
- handled = true
1316
1219
  clear_cached_capabilities
1317
1220
  clear_responses
1318
1221
  start_tls_session
1319
1222
  end
1320
- rescue Exception => error
1321
- raise # note that the error backtrace is in the receiver_thread
1322
1223
  end
1323
- if error
1324
- disconnect
1325
- raise error
1326
- end
1327
- unless handled
1328
- disconnect
1329
- raise InvalidResponseError,
1330
- "STARTTLS handler was bypassed, although server responded %p" % [
1331
- ok.raw_data.chomp
1332
- ]
1333
- end
1334
- ok
1335
1224
  end
1336
1225
 
1337
1226
  # :call-seq:
@@ -1347,6 +1236,9 @@ module Net
1347
1236
  # +SASL-IR+ capability, below). Defaults to the #config value for
1348
1237
  # {sasl_ir}[rdoc-ref:Config#sasl_ir], which defaults to +true+.
1349
1238
  #
1239
+ # The +registry+ kwarg can be used to select the mechanism implementation
1240
+ # from a custom registry. See SASL.authenticator and SASL::Authenticators.
1241
+ #
1350
1242
  # All other arguments are forwarded to the registered SASL authenticator for
1351
1243
  # the requested mechanism. <em>The documentation for each individual
1352
1244
  # mechanism must be consulted for its specific parameters.</em>
@@ -1441,29 +1333,9 @@ module Net
1441
1333
  # Previously cached #capabilities will be cleared when this method
1442
1334
  # completes. If the TaggedResponse to #authenticate includes updated
1443
1335
  # capabilities, they will be cached.
1444
- def authenticate(mechanism, *creds,
1445
- sasl_ir: config.sasl_ir,
1446
- **props, &callback)
1447
- mechanism = mechanism.to_s.tr("_", "-").upcase
1448
- authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
1449
- cmdargs = ["AUTHENTICATE", mechanism]
1450
- if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
1451
- authenticator.respond_to?(:initial_response?) &&
1452
- authenticator.initial_response?
1453
- response = authenticator.process(nil)
1454
- cmdargs << (response.empty? ? "=" : [response].pack("m0"))
1455
- end
1456
- result = send_command_with_continuations(*cmdargs) {|data|
1457
- challenge = data.unpack1("m")
1458
- response = authenticator.process challenge
1459
- [response].pack("m0")
1460
- }
1461
- if authenticator.respond_to?(:done?) && !authenticator.done?
1462
- logout!
1463
- raise SASL::AuthenticationIncomplete, result
1464
- end
1465
- @capabilities = capabilities_from_resp_code result
1466
- result
1336
+ def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback)
1337
+ sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback)
1338
+ .tap { @capabilities = capabilities_from_resp_code _1 }
1467
1339
  end
1468
1340
 
1469
1341
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1483,13 +1355,9 @@ module Net
1483
1355
  # ===== Capabilities
1484
1356
  #
1485
1357
  # An IMAP client MUST NOT call #login when the server advertises the
1486
- # +LOGINDISABLED+ capability.
1487
- #
1488
- # if imap.capability? "LOGINDISABLED"
1489
- # raise "Remote server has disabled the login command"
1490
- # else
1491
- # imap.login username, password
1492
- # end
1358
+ # +LOGINDISABLED+ capability. By default, Net::IMAP will raise a
1359
+ # LoginDisabledError when that capability is present. See
1360
+ # Config#enforce_logindisabled.
1493
1361
  #
1494
1362
  # Server capabilities may change after #starttls, #login, and #authenticate.
1495
1363
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1497,6 +1365,9 @@ module Net
1497
1365
  # ResponseCode.
1498
1366
  #
1499
1367
  def login(user, password)
1368
+ if enforce_logindisabled? && capability?("LOGINDISABLED")
1369
+ raise LoginDisabledError
1370
+ end
1500
1371
  send_command("LOGIN", user, password)
1501
1372
  .tap { @capabilities = capabilities_from_resp_code _1 }
1502
1373
  end
@@ -1765,18 +1636,12 @@ module Net
1765
1636
  # to both admin and user. If this mailbox exists, it returns an array
1766
1637
  # containing objects of type MailboxQuotaRoot and MailboxQuota.
1767
1638
  #
1768
- # *NOTE:* Currently, Net::IMAP only supports +QUOTA+ responses with a single
1769
- # resource type. This is usually +STORAGE+, but you may need to verify this
1770
- # with UntaggedResponse#raw_data.
1771
- #
1772
1639
  # Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota
1773
1640
  #
1774
1641
  # ===== Capabilities
1775
1642
  #
1776
- # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1777
- # capability, or a capability prefixed with <tt>QUOTA=RES-*</tt>
1778
- # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
1779
- # resource type.
1643
+ # The server's capabilities must include +QUOTA+
1644
+ # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1780
1645
  def getquotaroot(mailbox)
1781
1646
  synchronize do
1782
1647
  send_command("GETQUOTAROOT", mailbox)
@@ -1788,59 +1653,41 @@ module Net
1788
1653
  end
1789
1654
 
1790
1655
  # Sends a {GETQUOTA command [RFC2087 §4.2]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.2]
1791
- # for the +quota_root+. If this quota root exists, then an array
1792
- # containing a MailboxQuota object is returned.
1793
- #
1794
- # The names of quota roots that are applicable to a particular mailbox can
1795
- # be discovered with #getquotaroot.
1796
- #
1797
- # *NOTE:* Currently, Net::IMAP only supports +QUOTA+ responses with a single
1798
- # resource type. This is usually +STORAGE+, but you may need to verify this
1799
- # with UntaggedResponse#raw_data.
1656
+ # along with specified +mailbox+. If this mailbox exists, then an array
1657
+ # containing a MailboxQuota object is returned. This command is generally
1658
+ # only available to server admin.
1800
1659
  #
1801
1660
  # Related: #getquotaroot, #setquota, MailboxQuota
1802
1661
  #
1803
1662
  # ===== Capabilities
1804
1663
  #
1805
- # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1806
- # capability, or a capability prefixed with <tt>QUOTA=RES-*</tt>
1807
- # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
1808
- # resource type.
1809
- def getquota(quota_root)
1664
+ # The server's capabilities must include +QUOTA+
1665
+ # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1666
+ def getquota(mailbox)
1810
1667
  synchronize do
1811
- send_command("GETQUOTA", quota_root)
1668
+ send_command("GETQUOTA", mailbox)
1812
1669
  clear_responses("QUOTA")
1813
1670
  end
1814
1671
  end
1815
1672
 
1816
1673
  # Sends a {SETQUOTA command [RFC2087 §4.1]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.1]
1817
- # along with the specified +quota_root+ and +storage_limit+. If
1818
- # +storage_limit+ is +nil+, resource limits are unset for that quota root.
1819
- # If +storage_limit+ is a number, it sets the +STORAGE+ resource limit.
1820
- #
1821
- # imap.setquota "#user/alice", 100
1822
- # imap.getquota "#user/alice"
1823
- # # => [#<struct Net::IMAP::MailboxQuota mailbox="#user/alice" usage=54 quota=100>]
1824
- #
1825
- # Typically one needs to be logged in as a server admin for this to work.
1826
- #
1827
- # *NOTE:* Currently, Net::IMAP only supports setting +STORAGE+ quota limits.
1674
+ # along with the specified +mailbox+ and +quota+. If +quota+ is nil, then
1675
+ # +quota+ will be unset for that mailbox. Typically one needs to be logged
1676
+ # in as a server admin for this to work.
1828
1677
  #
1829
1678
  # Related: #getquota, #getquotaroot
1830
1679
  #
1831
1680
  # ===== Capabilities
1832
1681
  #
1833
- # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1834
- # capability, or both +QUOTASET+ and a capability prefixed with
1835
- # <tt>QUOTA=RES-*</tt> {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208]
1836
- # for each supported resource type.
1837
- def setquota(quota_root, storage_limit)
1838
- if storage_limit.nil?
1839
- list = []
1682
+ # The server's capabilities must include +QUOTA+
1683
+ # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1684
+ def setquota(mailbox, quota)
1685
+ if quota.nil?
1686
+ data = '()'
1840
1687
  else
1841
- list = ["STORAGE", Integer(storage_limit)]
1688
+ data = '(STORAGE ' + quota.to_s + ')'
1842
1689
  end
1843
- send_command("SETQUOTA", quota_root, list)
1690
+ send_command("SETQUOTA", mailbox, RawData.new(data))
1844
1691
  end
1845
1692
 
1846
1693
  # Sends a {SETACL command [RFC4314 §3.1]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.1]
@@ -1947,10 +1794,7 @@ module Net
1947
1794
  # <tt>STATUS=SIZE</tt>
1948
1795
  # {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html].
1949
1796
  #
1950
- # +DELETED+ must be supported when the server's capabilities includes
1951
- # +IMAP4rev2+.
1952
- # or <tt>QUOTA=RES-MESSAGES</tt>
1953
- # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208.html].
1797
+ # +DELETED+ requires the server's capabilities to include +IMAP4rev2+.
1954
1798
  #
1955
1799
  # +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+
1956
1800
  # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
@@ -2080,7 +1924,7 @@ module Net
2080
1924
  # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]].
2081
1925
  def uid_expunge(uid_set)
2082
1926
  synchronize do
2083
- send_command("UID EXPUNGE", MessageSet.new(uid_set))
1927
+ send_command("UID EXPUNGE", SequenceSet.new(uid_set))
2084
1928
  clear_responses("EXPUNGE")
2085
1929
  end
2086
1930
  end
@@ -2091,8 +1935,6 @@ module Net
2091
1935
  # string holding the entire search string, or a single-dimension array of
2092
1936
  # search keywords and arguments.
2093
1937
  #
2094
- # <em>Please note</em> the warning (below) when +keys+ is a String.
2095
- #
2096
1938
  # Returns a SearchResult object. SearchResult inherits from Array (for
2097
1939
  # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
2098
1940
  # capability has been enabled.
@@ -2101,14 +1943,6 @@ module Net
2101
1943
  #
2102
1944
  # ===== Search criteria
2103
1945
  #
2104
- # >>>
2105
- # When +keys+ is an Array, elements in the array will be validated and
2106
- # formatted. When +keys+ is a String, it will be sent <em>with
2107
- # minimal validation and no encoding or formatting</em>.
2108
- #
2109
- # <em>*WARNING:* Although CRLF is prohibited, this is vulnerable to other
2110
- # types of attribute injection attack if unvetted user input is used.</em>
2111
- #
2112
1946
  # For a full list of search criteria,
2113
1947
  # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]],
2114
1948
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
@@ -2174,8 +2008,7 @@ module Net
2174
2008
  # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
2175
2009
  # capability has been enabled.
2176
2010
  #
2177
- # See #search for documentation of parameters. <em>Please note</em> the
2178
- # warning for when +keys+ is a String.
2011
+ # See #search for documentation of search criteria.
2179
2012
  def uid_search(keys, charset = nil)
2180
2013
  return search_internal("UID SEARCH", keys, charset)
2181
2014
  end
@@ -2197,13 +2030,6 @@ module Net
2197
2030
  #
2198
2031
  # +attr+ is a list of attributes to fetch; see the documentation
2199
2032
  # for FetchData for a list of valid attributes.
2200
- # >>>
2201
- # When +attr+ is a String, it will be sent <em>with minimal validation and
2202
- # no encoding or formatting</em>. When +attr+ is an Array, each String in
2203
- # +attr+ will be sent this way.
2204
- #
2205
- # <em>*WARNING:* Although CRLF is prohibited, this is vulnerable to other
2206
- # types of attribute injection attack if unvetted user input is used.</em>
2207
2033
  #
2208
2034
  # +changedsince+ is an optional integer mod-sequence. It limits results to
2209
2035
  # messages with a mod-sequence greater than +changedsince+.
@@ -2252,8 +2078,6 @@ module Net
2252
2078
  # Similar to #fetch, but the +set+ parameter contains unique identifiers
2253
2079
  # instead of message sequence numbers.
2254
2080
  #
2255
- # +attr+ behaves the same as with #fetch. <em>Please note</em> the #fetch
2256
- # warning on the +attr+ argument.
2257
2081
  # >>>
2258
2082
  # *Note:* Servers _MUST_ implicitly include the +UID+ message data item as
2259
2083
  # part of any +FETCH+ response caused by a +UID+ command, regardless of
@@ -2406,10 +2230,8 @@ module Net
2406
2230
 
2407
2231
  # Sends a {SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3]
2408
2232
  # to search a mailbox for messages that match +search_keys+ and return an
2409
- # array of message sequence numbers, sorted by +sort_keys+.
2410
- #
2411
- # +search_keys+ are interpreted the same as the +criteria+ argument for
2412
- # #search. <em>Please note</em> the #search warning for String +criteria+.
2233
+ # array of message sequence numbers, sorted by +sort_keys+. +search_keys+
2234
+ # are interpreted the same as for #search.
2413
2235
  #
2414
2236
  #--
2415
2237
  # TODO: describe +sort_keys+
@@ -2434,10 +2256,8 @@ module Net
2434
2256
 
2435
2257
  # Sends a {UID SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3]
2436
2258
  # to search a mailbox for messages that match +search_keys+ and return an
2437
- # array of unique identifiers, sorted by +sort_keys+.
2438
- #
2439
- # +search_keys+ are interpreted the same as the +criteria+ argument for
2440
- # #search. <em>Please note</em> the #search warning for String +criteria+.
2259
+ # array of unique identifiers, sorted by +sort_keys+. +search_keys+ are
2260
+ # interpreted the same as for #search.
2441
2261
  #
2442
2262
  # Related: #sort, #search, #uid_search, #thread, #uid_thread
2443
2263
  #
@@ -2451,10 +2271,8 @@ module Net
2451
2271
 
2452
2272
  # Sends a {THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3]
2453
2273
  # to search a mailbox and return message sequence numbers in threaded
2454
- # format, as a ThreadMember tree.
2455
- #
2456
- # +search_keys+ are interpreted the same as the +criteria+ argument for
2457
- # #search. <em>Please note</em> the #search warning for String +criteria+.
2274
+ # format, as a ThreadMember tree. +search_keys+ are interpreted the same as
2275
+ # for #search.
2458
2276
  #
2459
2277
  # The supported algorithms are:
2460
2278
  #
@@ -2480,9 +2298,6 @@ module Net
2480
2298
  # Similar to #thread, but returns unique identifiers instead of
2481
2299
  # message sequence numbers.
2482
2300
  #
2483
- # +search_keys+ are interpreted the same as the +criteria+ argument for
2484
- # #search. <em>Please note</em> the #search warning for String +criteria+.
2485
- #
2486
2301
  # Related: #thread, #search, #uid_search, #sort, #uid_sort
2487
2302
  #
2488
2303
  # ===== Capabilities
@@ -2570,11 +2385,10 @@ module Net
2570
2385
  capabilities = capabilities
2571
2386
  .flatten
2572
2387
  .map {|e| ENABLE_ALIASES[e] || e }
2573
- .flat_map { _1.is_a?(String) && !_1.empty? ? _1.split(/ /, -1) : [_1] }
2574
2388
  .uniq
2575
- .map { Atom[_1] }
2389
+ .join(' ')
2576
2390
  synchronize do
2577
- send_command("ENABLE", *capabilities)
2391
+ send_command("ENABLE #{capabilities}")
2578
2392
  result = clear_responses("ENABLED").last || []
2579
2393
  @utf8_strings ||= result.include? "UTF8=ACCEPT"
2580
2394
  @utf8_strings ||= result.include? "IMAP4REV2"
@@ -2768,10 +2582,10 @@ module Net
2768
2582
  when :raise
2769
2583
  raise ArgumentError, RESPONSES_DEPRECATION_MSG
2770
2584
  when :warn
2771
- warn(RESPONSES_DEPRECATION_MSG, uplevel: 1)
2585
+ warn(RESPONSES_DEPRECATION_MSG, uplevel: 1, category: :deprecated)
2772
2586
  when :frozen_dup
2773
2587
  synchronize {
2774
- responses = @responses.transform_values { _1.dup.freeze }
2588
+ responses = @responses.transform_values(&:freeze)
2775
2589
  responses.default_proc = nil
2776
2590
  responses.default = [].freeze
2777
2591
  return responses.freeze
@@ -2858,10 +2672,6 @@ module Net
2858
2672
  # end
2859
2673
  # }
2860
2674
  #
2861
- # Response handlers can also be added when the client is created before the
2862
- # receiver thread is started, by the +response_handlers+ argument to ::new.
2863
- # This ensures every server response is handled, including the #greeting.
2864
- #
2865
2675
  # Related: #remove_response_handler, #response_handlers
2866
2676
  def add_response_handler(handler = nil, &block)
2867
2677
  raise ArgumentError, "two Procs are passed" if handler && block
@@ -2888,7 +2698,6 @@ module Net
2888
2698
  def start_imap_connection
2889
2699
  @greeting = get_server_greeting
2890
2700
  @capabilities = capabilities_from_resp_code @greeting
2891
- @response_handlers.each do |handler| handler.call(@greeting) end
2892
2701
  @receiver_thread = start_receiver_thread
2893
2702
  rescue Exception
2894
2703
  @sock.close
@@ -3017,10 +2826,23 @@ module Net
3017
2826
  end
3018
2827
 
3019
2828
  def get_response
3020
- buff = @reader.read_response_buffer
2829
+ buff = String.new
2830
+ while true
2831
+ s = @sock.gets(CRLF)
2832
+ break unless s
2833
+ buff.concat(s)
2834
+ if /\{(\d+)\}\r\n/n =~ s
2835
+ s = @sock.read($1.to_i)
2836
+ buff.concat(s)
2837
+ else
2838
+ break
2839
+ end
2840
+ end
3021
2841
  return nil if buff.length == 0
3022
- $stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
3023
- @parser.parse(buff)
2842
+ if config.debug?
2843
+ $stderr.print(buff.gsub(/^/n, "S: "))
2844
+ end
2845
+ return @parser.parse(buff)
3024
2846
  end
3025
2847
 
3026
2848
  #############################
@@ -3072,7 +2894,6 @@ module Net
3072
2894
  put_string(" ")
3073
2895
  send_data(i, tag)
3074
2896
  end
3075
- guard_against_tagged_response_skipping_handler!(tag)
3076
2897
  put_string(CRLF)
3077
2898
  if cmd == "LOGOUT"
3078
2899
  @logout_command_tag = tag
@@ -3088,17 +2909,6 @@ module Net
3088
2909
  end
3089
2910
  end
3090
2911
  end
3091
- rescue InvalidResponseError
3092
- disconnect
3093
- raise
3094
- end
3095
-
3096
- def guard_against_tagged_response_skipping_handler!(tag)
3097
- return unless (resp = @tagged_responses[tag])&.name&.upcase == "OK"
3098
- raise(InvalidResponseError,
3099
- "Server sent tagged 'OK' before command was finished: %p. " \
3100
- "This could indicate a malicious server or client-side " \
3101
- "command injection. Disconnecting." % [resp.raw_data.chomp])
3102
2912
  end
3103
2913
 
3104
2914
  def generate_tag
@@ -3121,6 +2931,14 @@ module Net
3121
2931
  end
3122
2932
  end
3123
2933
 
2934
+ def enforce_logindisabled?
2935
+ if config.enforce_logindisabled == :when_capabilities_cached
2936
+ capabilities_cached?
2937
+ else
2938
+ config.enforce_logindisabled
2939
+ end
2940
+ end
2941
+
3124
2942
  def search_internal(cmd, keys, charset)
3125
2943
  if keys.instance_of?(String)
3126
2944
  keys = [RawData.new(keys)]
@@ -3154,17 +2972,17 @@ module Net
3154
2972
  synchronize do
3155
2973
  clear_responses("FETCH")
3156
2974
  if mod
3157
- send_command(cmd, MessageSet.new(set), attr, mod)
2975
+ send_command(cmd, SequenceSet.new(set), attr, mod)
3158
2976
  else
3159
- send_command(cmd, MessageSet.new(set), attr)
2977
+ send_command(cmd, SequenceSet.new(set), attr)
3160
2978
  end
3161
2979
  clear_responses("FETCH")
3162
2980
  end
3163
2981
  end
3164
2982
 
3165
2983
  def store_internal(cmd, set, attr, flags, unchangedsince: nil)
3166
- attr = Atom.new(attr) if attr.instance_of?(String)
3167
- args = [MessageSet.new(set)]
2984
+ attr = RawData.new(attr) if attr.instance_of?(String)
2985
+ args = [SequenceSet.new(set)]
3168
2986
  args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
3169
2987
  args << attr << flags
3170
2988
  synchronize do
@@ -3175,7 +2993,7 @@ module Net
3175
2993
  end
3176
2994
 
3177
2995
  def copy_internal(cmd, set, mailbox)
3178
- send_command(cmd, MessageSet.new(set), mailbox)
2996
+ send_command(cmd, SequenceSet.new(set), mailbox)
3179
2997
  end
3180
2998
 
3181
2999
  def sort_internal(cmd, sort_keys, search_keys, charset)
@@ -3206,7 +3024,7 @@ module Net
3206
3024
  keys.collect! do |i|
3207
3025
  case i
3208
3026
  when -1, Range, Array
3209
- MessageSet.new(i)
3027
+ SequenceSet.new(i)
3210
3028
  else
3211
3029
  i
3212
3030
  end
@@ -3233,7 +3051,6 @@ module Net
3233
3051
  raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
3234
3052
  raise "cannot start TLS without SSLContext" unless ssl_ctx
3235
3053
  @sock = SSLSocket.new(@sock, ssl_ctx)
3236
- @reader = ResponseReader.new(self, @sock)
3237
3054
  @sock.sync_close = true
3238
3055
  @sock.hostname = @host if @sock.respond_to? :hostname=
3239
3056
  ssl_socket_connect(@sock, open_timeout)