net-imap 0.4.24 → 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.24"
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,11 +953,6 @@ 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
958
  # Related: #logout, #logout!
@@ -1290,10 +1196,6 @@ module Net
1290
1196
  # both successful. Any error indicates that the connection has not been
1291
1197
  # secured.
1292
1198
  #
1293
- # After the server agrees to start a TLS connection, this method waits up to
1294
- # {config.open_timeout}[rdoc-ref:Config#open_timeout] before raising
1295
- # +Net::OpenTimeout+.
1296
- #
1297
1199
  # *Note:*
1298
1200
  # >>>
1299
1201
  # Any #response_handlers added before STARTTLS should be aware that the
@@ -1312,30 +1214,13 @@ module Net
1312
1214
  #
1313
1215
  def starttls(**options)
1314
1216
  @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
1315
- handled = false
1316
- error = nil
1317
- ok = send_command("STARTTLS") do |resp|
1217
+ send_command("STARTTLS") do |resp|
1318
1218
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
1319
- handled = true
1320
1219
  clear_cached_capabilities
1321
1220
  clear_responses
1322
1221
  start_tls_session
1323
1222
  end
1324
- rescue Exception => error
1325
- raise # note that the error backtrace is in the receiver_thread
1326
- end
1327
- if error
1328
- disconnect
1329
- raise error
1330
1223
  end
1331
- unless handled
1332
- disconnect
1333
- raise InvalidResponseError,
1334
- "STARTTLS handler was bypassed, although server responded %p" % [
1335
- ok.raw_data.chomp
1336
- ]
1337
- end
1338
- ok
1339
1224
  end
1340
1225
 
1341
1226
  # :call-seq:
@@ -1351,6 +1236,9 @@ module Net
1351
1236
  # +SASL-IR+ capability, below). Defaults to the #config value for
1352
1237
  # {sasl_ir}[rdoc-ref:Config#sasl_ir], which defaults to +true+.
1353
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
+ #
1354
1242
  # All other arguments are forwarded to the registered SASL authenticator for
1355
1243
  # the requested mechanism. <em>The documentation for each individual
1356
1244
  # mechanism must be consulted for its specific parameters.</em>
@@ -1445,29 +1333,9 @@ module Net
1445
1333
  # Previously cached #capabilities will be cleared when this method
1446
1334
  # completes. If the TaggedResponse to #authenticate includes updated
1447
1335
  # capabilities, they will be cached.
1448
- def authenticate(mechanism, *creds,
1449
- sasl_ir: config.sasl_ir,
1450
- **props, &callback)
1451
- mechanism = mechanism.to_s.tr("_", "-").upcase
1452
- authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
1453
- cmdargs = ["AUTHENTICATE", mechanism]
1454
- if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
1455
- authenticator.respond_to?(:initial_response?) &&
1456
- authenticator.initial_response?
1457
- response = authenticator.process(nil)
1458
- cmdargs << (response.empty? ? "=" : [response].pack("m0"))
1459
- end
1460
- result = send_command_with_continuations(*cmdargs) {|data|
1461
- challenge = data.unpack1("m")
1462
- response = authenticator.process challenge
1463
- [response].pack("m0")
1464
- }
1465
- if authenticator.respond_to?(:done?) && !authenticator.done?
1466
- logout!
1467
- raise SASL::AuthenticationIncomplete, result
1468
- end
1469
- @capabilities = capabilities_from_resp_code result
1470
- 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 }
1471
1339
  end
1472
1340
 
1473
1341
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1487,13 +1355,9 @@ module Net
1487
1355
  # ===== Capabilities
1488
1356
  #
1489
1357
  # An IMAP client MUST NOT call #login when the server advertises the
1490
- # +LOGINDISABLED+ capability.
1491
- #
1492
- # if imap.capability? "LOGINDISABLED"
1493
- # raise "Remote server has disabled the login command"
1494
- # else
1495
- # imap.login username, password
1496
- # end
1358
+ # +LOGINDISABLED+ capability. By default, Net::IMAP will raise a
1359
+ # LoginDisabledError when that capability is present. See
1360
+ # Config#enforce_logindisabled.
1497
1361
  #
1498
1362
  # Server capabilities may change after #starttls, #login, and #authenticate.
1499
1363
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1501,6 +1365,9 @@ module Net
1501
1365
  # ResponseCode.
1502
1366
  #
1503
1367
  def login(user, password)
1368
+ if enforce_logindisabled? && capability?("LOGINDISABLED")
1369
+ raise LoginDisabledError
1370
+ end
1504
1371
  send_command("LOGIN", user, password)
1505
1372
  .tap { @capabilities = capabilities_from_resp_code _1 }
1506
1373
  end
@@ -1769,18 +1636,12 @@ module Net
1769
1636
  # to both admin and user. If this mailbox exists, it returns an array
1770
1637
  # containing objects of type MailboxQuotaRoot and MailboxQuota.
1771
1638
  #
1772
- # *NOTE:* Currently, Net::IMAP only supports +QUOTA+ responses with a single
1773
- # resource type. This is usually +STORAGE+, but you may need to verify this
1774
- # with UntaggedResponse#raw_data.
1775
- #
1776
1639
  # Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota
1777
1640
  #
1778
1641
  # ===== Capabilities
1779
1642
  #
1780
- # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1781
- # capability, or a capability prefixed with <tt>QUOTA=RES-*</tt>
1782
- # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
1783
- # resource type.
1643
+ # The server's capabilities must include +QUOTA+
1644
+ # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1784
1645
  def getquotaroot(mailbox)
1785
1646
  synchronize do
1786
1647
  send_command("GETQUOTAROOT", mailbox)
@@ -1792,59 +1653,41 @@ module Net
1792
1653
  end
1793
1654
 
1794
1655
  # Sends a {GETQUOTA command [RFC2087 §4.2]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.2]
1795
- # for the +quota_root+. If this quota root exists, then an array
1796
- # containing a MailboxQuota object is returned.
1797
- #
1798
- # The names of quota roots that are applicable to a particular mailbox can
1799
- # be discovered with #getquotaroot.
1800
- #
1801
- # *NOTE:* Currently, Net::IMAP only supports +QUOTA+ responses with a single
1802
- # resource type. This is usually +STORAGE+, but you may need to verify this
1803
- # 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.
1804
1659
  #
1805
1660
  # Related: #getquotaroot, #setquota, MailboxQuota
1806
1661
  #
1807
1662
  # ===== Capabilities
1808
1663
  #
1809
- # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1810
- # capability, or a capability prefixed with <tt>QUOTA=RES-*</tt>
1811
- # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
1812
- # resource type.
1813
- def getquota(quota_root)
1664
+ # The server's capabilities must include +QUOTA+
1665
+ # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1666
+ def getquota(mailbox)
1814
1667
  synchronize do
1815
- send_command("GETQUOTA", quota_root)
1668
+ send_command("GETQUOTA", mailbox)
1816
1669
  clear_responses("QUOTA")
1817
1670
  end
1818
1671
  end
1819
1672
 
1820
1673
  # Sends a {SETQUOTA command [RFC2087 §4.1]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.1]
1821
- # along with the specified +quota_root+ and +storage_limit+. If
1822
- # +storage_limit+ is +nil+, resource limits are unset for that quota root.
1823
- # If +storage_limit+ is a number, it sets the +STORAGE+ resource limit.
1824
- #
1825
- # imap.setquota "#user/alice", 100
1826
- # imap.getquota "#user/alice"
1827
- # # => [#<struct Net::IMAP::MailboxQuota mailbox="#user/alice" usage=54 quota=100>]
1828
- #
1829
- # Typically one needs to be logged in as a server admin for this to work.
1830
- #
1831
- # *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.
1832
1677
  #
1833
1678
  # Related: #getquota, #getquotaroot
1834
1679
  #
1835
1680
  # ===== Capabilities
1836
1681
  #
1837
- # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1838
- # capability, or both +QUOTASET+ and a capability prefixed with
1839
- # <tt>QUOTA=RES-*</tt> {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208]
1840
- # for each supported resource type.
1841
- def setquota(quota_root, storage_limit)
1842
- if storage_limit.nil?
1843
- 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 = '()'
1844
1687
  else
1845
- list = ["STORAGE", Integer(storage_limit)]
1688
+ data = '(STORAGE ' + quota.to_s + ')'
1846
1689
  end
1847
- send_command("SETQUOTA", quota_root, list)
1690
+ send_command("SETQUOTA", mailbox, RawData.new(data))
1848
1691
  end
1849
1692
 
1850
1693
  # Sends a {SETACL command [RFC4314 §3.1]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.1]
@@ -1951,10 +1794,7 @@ module Net
1951
1794
  # <tt>STATUS=SIZE</tt>
1952
1795
  # {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html].
1953
1796
  #
1954
- # +DELETED+ must be supported when the server's capabilities includes
1955
- # +IMAP4rev2+.
1956
- # or <tt>QUOTA=RES-MESSAGES</tt>
1957
- # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208.html].
1797
+ # +DELETED+ requires the server's capabilities to include +IMAP4rev2+.
1958
1798
  #
1959
1799
  # +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+
1960
1800
  # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
@@ -2084,7 +1924,7 @@ module Net
2084
1924
  # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]].
2085
1925
  def uid_expunge(uid_set)
2086
1926
  synchronize do
2087
- send_command("UID EXPUNGE", MessageSet.new(uid_set))
1927
+ send_command("UID EXPUNGE", SequenceSet.new(uid_set))
2088
1928
  clear_responses("EXPUNGE")
2089
1929
  end
2090
1930
  end
@@ -2103,14 +1943,6 @@ module Net
2103
1943
  #
2104
1944
  # ===== Search criteria
2105
1945
  #
2106
- # >>>
2107
- # When +criteria+ is an Array, elements in the array will be validated and
2108
- # formatted. When +criteria+ is a String, it will be sent <em>with
2109
- # minimal validation and no encoding or formatting</em>.
2110
- #
2111
- # <em>*WARNING:* Although CRLF is prohibited, this is vulnerable to other
2112
- # types of attribute injection attack if unvetted user input is used.</em>
2113
- #
2114
1946
  # For a full list of search criteria,
2115
1947
  # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]],
2116
1948
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
@@ -2198,13 +2030,6 @@ module Net
2198
2030
  #
2199
2031
  # +attr+ is a list of attributes to fetch; see the documentation
2200
2032
  # for FetchData for a list of valid attributes.
2201
- # >>>
2202
- # When +attr+ is a String, it will be sent <em>with minimal validation and
2203
- # no encoding or formatting</em>. When +attr+ is an Array, each String in
2204
- # +attr+ will be sent this way.
2205
- #
2206
- # <em>*WARNING:* Although CRLF is prohibited, this is vulnerable to other
2207
- # types of attribute injection attack if unvetted user input is used.</em>
2208
2033
  #
2209
2034
  # +changedsince+ is an optional integer mod-sequence. It limits results to
2210
2035
  # messages with a mod-sequence greater than +changedsince+.
@@ -2757,10 +2582,10 @@ module Net
2757
2582
  when :raise
2758
2583
  raise ArgumentError, RESPONSES_DEPRECATION_MSG
2759
2584
  when :warn
2760
- warn(RESPONSES_DEPRECATION_MSG, uplevel: 1)
2585
+ warn(RESPONSES_DEPRECATION_MSG, uplevel: 1, category: :deprecated)
2761
2586
  when :frozen_dup
2762
2587
  synchronize {
2763
- responses = @responses.transform_values { _1.dup.freeze }
2588
+ responses = @responses.transform_values(&:freeze)
2764
2589
  responses.default_proc = nil
2765
2590
  responses.default = [].freeze
2766
2591
  return responses.freeze
@@ -2847,10 +2672,6 @@ module Net
2847
2672
  # end
2848
2673
  # }
2849
2674
  #
2850
- # Response handlers can also be added when the client is created before the
2851
- # receiver thread is started, by the +response_handlers+ argument to ::new.
2852
- # This ensures every server response is handled, including the #greeting.
2853
- #
2854
2675
  # Related: #remove_response_handler, #response_handlers
2855
2676
  def add_response_handler(handler = nil, &block)
2856
2677
  raise ArgumentError, "two Procs are passed" if handler && block
@@ -2877,7 +2698,6 @@ module Net
2877
2698
  def start_imap_connection
2878
2699
  @greeting = get_server_greeting
2879
2700
  @capabilities = capabilities_from_resp_code @greeting
2880
- @response_handlers.each do |handler| handler.call(@greeting) end
2881
2701
  @receiver_thread = start_receiver_thread
2882
2702
  rescue Exception
2883
2703
  @sock.close
@@ -3006,10 +2826,23 @@ module Net
3006
2826
  end
3007
2827
 
3008
2828
  def get_response
3009
- 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
3010
2841
  return nil if buff.length == 0
3011
- $stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
3012
- @parser.parse(buff)
2842
+ if config.debug?
2843
+ $stderr.print(buff.gsub(/^/n, "S: "))
2844
+ end
2845
+ return @parser.parse(buff)
3013
2846
  end
3014
2847
 
3015
2848
  #############################
@@ -3061,7 +2894,6 @@ module Net
3061
2894
  put_string(" ")
3062
2895
  send_data(i, tag)
3063
2896
  end
3064
- guard_against_tagged_response_skipping_handler!(tag)
3065
2897
  put_string(CRLF)
3066
2898
  if cmd == "LOGOUT"
3067
2899
  @logout_command_tag = tag
@@ -3077,17 +2909,6 @@ module Net
3077
2909
  end
3078
2910
  end
3079
2911
  end
3080
- rescue InvalidResponseError
3081
- disconnect
3082
- raise
3083
- end
3084
-
3085
- def guard_against_tagged_response_skipping_handler!(tag)
3086
- return unless (resp = @tagged_responses[tag])&.name&.upcase == "OK"
3087
- raise(InvalidResponseError,
3088
- "Server sent tagged 'OK' before command was finished: %p. " \
3089
- "This could indicate a malicious server or client-side " \
3090
- "command injection. Disconnecting." % [resp.raw_data.chomp])
3091
2912
  end
3092
2913
 
3093
2914
  def generate_tag
@@ -3110,6 +2931,14 @@ module Net
3110
2931
  end
3111
2932
  end
3112
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
+
3113
2942
  def search_internal(cmd, keys, charset)
3114
2943
  if keys.instance_of?(String)
3115
2944
  keys = [RawData.new(keys)]
@@ -3143,17 +2972,17 @@ module Net
3143
2972
  synchronize do
3144
2973
  clear_responses("FETCH")
3145
2974
  if mod
3146
- send_command(cmd, MessageSet.new(set), attr, mod)
2975
+ send_command(cmd, SequenceSet.new(set), attr, mod)
3147
2976
  else
3148
- send_command(cmd, MessageSet.new(set), attr)
2977
+ send_command(cmd, SequenceSet.new(set), attr)
3149
2978
  end
3150
2979
  clear_responses("FETCH")
3151
2980
  end
3152
2981
  end
3153
2982
 
3154
2983
  def store_internal(cmd, set, attr, flags, unchangedsince: nil)
3155
- attr = Atom.new(attr) if attr.instance_of?(String)
3156
- args = [MessageSet.new(set)]
2984
+ attr = RawData.new(attr) if attr.instance_of?(String)
2985
+ args = [SequenceSet.new(set)]
3157
2986
  args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
3158
2987
  args << attr << flags
3159
2988
  synchronize do
@@ -3164,7 +2993,7 @@ module Net
3164
2993
  end
3165
2994
 
3166
2995
  def copy_internal(cmd, set, mailbox)
3167
- send_command(cmd, MessageSet.new(set), mailbox)
2996
+ send_command(cmd, SequenceSet.new(set), mailbox)
3168
2997
  end
3169
2998
 
3170
2999
  def sort_internal(cmd, sort_keys, search_keys, charset)
@@ -3195,7 +3024,7 @@ module Net
3195
3024
  keys.collect! do |i|
3196
3025
  case i
3197
3026
  when -1, Range, Array
3198
- MessageSet.new(i)
3027
+ SequenceSet.new(i)
3199
3028
  else
3200
3029
  i
3201
3030
  end
@@ -3222,7 +3051,6 @@ module Net
3222
3051
  raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
3223
3052
  raise "cannot start TLS without SSLContext" unless ssl_ctx
3224
3053
  @sock = SSLSocket.new(@sock, ssl_ctx)
3225
- @reader = ResponseReader.new(self, @sock)
3226
3054
  @sock.sync_close = true
3227
3055
  @sock.hostname = @host if @sock.respond_to? :hostname=
3228
3056
  ssl_socket_connect(@sock, open_timeout)