net-imap 0.4.19 → 0.4.24

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,16 @@ 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
+ #
50
56
  # === Sequence numbers and UIDs
51
57
  #
52
58
  # Messages have two sorts of identifiers: message sequence
@@ -199,6 +205,42 @@ module Net
199
205
  #
200
206
  # This script invokes the FETCH command and the SEARCH command concurrently.
201
207
  #
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
+ #
202
244
  # == Errors
203
245
  #
204
246
  # An \IMAP server can send three different types of responses to indicate
@@ -260,8 +302,9 @@ module Net
260
302
  #
261
303
  # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
262
304
  # waits for a successful server greeting before the method returns.
305
+ # - #connection_state: Returns the connection state.
263
306
  # - #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.
307
+ # - #logout: Tells the server to end the session. Enters the +logout+ state.
265
308
  # - #disconnect: Disconnects the connection (without sending #logout first).
266
309
  # - #disconnected?: True if the connection has been closed.
267
310
  #
@@ -317,37 +360,36 @@ module Net
317
360
  # <em>In general, #capable? should be used rather than explicitly sending a
318
361
  # +CAPABILITY+ command to the server.</em>
319
362
  # - #noop: Allows the server to send unsolicited untagged #responses.
320
- # - #logout: Tells the server to end the session. Enters the "_logout_" state.
363
+ # - #logout: Tells the server to end the session. Enters the +logout+ state.
321
364
  #
322
365
  # ==== Not Authenticated state
323
366
  #
324
367
  # In addition to the commands for any state, the following commands are valid
325
- # in the "<em>not authenticated</em>" state:
368
+ # in the +not_authenticated+ state:
326
369
  #
327
370
  # - #starttls: Upgrades a clear-text connection to use TLS.
328
371
  #
329
372
  # <em>Requires the +STARTTLS+ capability.</em>
330
373
  # - #authenticate: Identifies the client to the server using the given
331
374
  # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
332
- # and credentials. Enters the "_authenticated_" state.
375
+ # and credentials. Enters the +authenticated+ state.
333
376
  #
334
377
  # <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
335
378
  # supported mechanisms.</em>
336
379
  # - #login: Identifies the client to the server using a plain text password.
337
- # Using #authenticate is generally preferred. Enters the "_authenticated_"
338
- # state.
380
+ # Using #authenticate is preferred. Enters the +authenticated+ state.
339
381
  #
340
382
  # <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
341
383
  #
342
384
  # ==== Authenticated state
343
385
  #
344
386
  # In addition to the commands for any state, the following commands are valid
345
- # in the "_authenticated_" state:
387
+ # in the +authenticated+ state:
346
388
  #
347
389
  # - #enable: Enables backwards incompatible server extensions.
348
390
  # <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.
391
+ # - #select: Open a mailbox and enter the +selected+ state.
392
+ # - #examine: Open a mailbox read-only, and enter the +selected+ state.
351
393
  # - #create: Creates a new mailbox.
352
394
  # - #delete: Permanently remove a mailbox.
353
395
  # - #rename: Change the name of a mailbox.
@@ -369,12 +411,12 @@ module Net
369
411
  #
370
412
  # ==== Selected state
371
413
  #
372
- # In addition to the commands for any state and the "_authenticated_"
373
- # commands, the following commands are valid in the "_selected_" state:
414
+ # In addition to the commands for any state and the +authenticated+
415
+ # commands, the following commands are valid in the +selected+ state:
374
416
  #
375
- # - #close: Closes the mailbox and returns to the "_authenticated_" state,
417
+ # - #close: Closes the mailbox and returns to the +authenticated+ state,
376
418
  # expunging deleted messages, unless the mailbox was opened as read-only.
377
- # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
419
+ # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
378
420
  # without expunging any messages.
379
421
  # <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
380
422
  # - #expunge: Permanently removes messages which have the Deleted flag set.
@@ -395,7 +437,7 @@ module Net
395
437
  #
396
438
  # ==== Logout state
397
439
  #
398
- # No \IMAP commands are valid in the "_logout_" state. If the socket is still
440
+ # No \IMAP commands are valid in the +logout+ state. If the socket is still
399
441
  # open, Net::IMAP will close it after receiving server confirmation.
400
442
  # Exceptions will be raised by \IMAP commands that have already started and
401
443
  # are waiting for a response, as well as any that are called after logout.
@@ -418,6 +460,9 @@ module Net
418
460
  # +LITERAL-+, and +SPECIAL-USE+.</em>
419
461
  #
420
462
  # ==== 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.
421
466
  # - #getquota: returns the resource usage and limits for a quota root
422
467
  # - #getquotaroot: returns the list of quota roots for a mailbox, as well as
423
468
  # their resource usage and limits.
@@ -449,7 +494,7 @@ module Net
449
494
  # ==== RFC3691: +UNSELECT+
450
495
  # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
451
496
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
452
- # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
497
+ # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
453
498
  # without expunging any messages.
454
499
  #
455
500
  # ==== RFC4314: +ACL+
@@ -530,6 +575,16 @@ module Net
530
575
  # See FetchData#emailid and FetchData#emailid.
531
576
  # - Updates #status with support for the +MAILBOXID+ status attribute.
532
577
  #
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
+ #
533
588
  # == References
534
589
  #
535
590
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
@@ -639,14 +694,13 @@ module Net
639
694
  #
640
695
  # === \IMAP Extensions
641
696
  #
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>.
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>.
645
700
  #
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]]::
701
+ # *NOTE*: _obsoleted_ by RFC9208[https://www.rfc-editor.org/rfc/rfc9208]
702
+ # (March 2022).
703
+ # [IDLE[https://www.rfc-editor.org/rfc/rfc2177]]::
650
704
  # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
651
705
  # June 1997, <https://www.rfc-editor.org/info/rfc2177>.
652
706
  # [NAMESPACE[https://tools.ietf.org/html/rfc2342]]::
@@ -697,9 +751,15 @@ module Net
697
751
  # Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
698
752
  # RFC 8474, DOI 10.17487/RFC8474, September 2018,
699
753
  # <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].
700
759
  #
701
760
  # === IANA registries
702
761
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
762
+ # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
703
763
  # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
704
764
  # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
705
765
  # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
@@ -719,7 +779,7 @@ module Net
719
779
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
720
780
  #
721
781
  class IMAP < Protocol
722
- VERSION = "0.4.19"
782
+ VERSION = "0.4.24"
723
783
 
724
784
  # Aliases for supported capabilities, to be used with the #enable command.
725
785
  ENABLE_ALIASES = {
@@ -727,6 +787,7 @@ module Net
727
787
  "UTF8=ONLY" => "UTF8=ACCEPT",
728
788
  }.freeze
729
789
 
790
+ autoload :ResponseReader, File.expand_path("imap/response_reader", __dir__)
730
791
  autoload :SASL, File.expand_path("imap/sasl", __dir__)
731
792
  autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
732
793
  autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
@@ -741,9 +802,11 @@ module Net
741
802
  def self.config; Config.global end
742
803
 
743
804
  # Returns the global debug mode.
805
+ # Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
744
806
  def self.debug; config.debug end
745
807
 
746
808
  # Sets the global debug mode.
809
+ # Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
747
810
  def self.debug=(val)
748
811
  config.debug = val
749
812
  end
@@ -764,7 +827,7 @@ module Net
764
827
  alias default_ssl_port default_tls_port
765
828
  end
766
829
 
767
- # Returns the initial greeting the server, an UntaggedResponse.
830
+ # Returns the initial greeting sent by the server, an UntaggedResponse.
768
831
  attr_reader :greeting
769
832
 
770
833
  # The client configuration. See Net::IMAP::Config.
@@ -773,13 +836,28 @@ module Net
773
836
  # Net::IMAP.config.
774
837
  attr_reader :config
775
838
 
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
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].
780
843
 
844
+ ##
845
+ # :attr_reader: idle_response_timeout
781
846
  # Seconds to wait until an IDLE response is received.
782
- def idle_response_timeout; config.idle_response_timeout end
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:
783
861
 
784
862
  # The hostname this client connected to
785
863
  attr_reader :host
@@ -835,6 +913,12 @@ module Net
835
913
  #
836
914
  # See DeprecatedClientOptions.new for deprecated SSL arguments.
837
915
  #
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
+ #
838
922
  # [config]
839
923
  # A Net::IMAP::Config object to use as the basis for #config. By default,
840
924
  # the global Net::IMAP.config is used.
@@ -906,7 +990,7 @@ module Net
906
990
  # [Net::IMAP::ByeResponseError]
907
991
  # Connected to the host successfully, but it immediately said goodbye.
908
992
  #
909
- def initialize(host, port: nil, ssl: nil,
993
+ def initialize(host, port: nil, ssl: nil, response_handlers: nil,
910
994
  config: Config.global, **config_options)
911
995
  super()
912
996
  # Config options
@@ -929,6 +1013,7 @@ module Net
929
1013
  @receiver_thread = nil
930
1014
  @receiver_thread_exception = nil
931
1015
  @receiver_thread_terminating = false
1016
+ response_handlers&.each do add_response_handler(_1) end
932
1017
 
933
1018
  # Client Protocol Sender (including state for currently running commands)
934
1019
  @tag_prefix = "RUBY"
@@ -944,6 +1029,7 @@ module Net
944
1029
  # Connection
945
1030
  @tls_verified = false
946
1031
  @sock = tcp_socket(@host, @port)
1032
+ @reader = ResponseReader.new(self, @sock)
947
1033
  start_tls_session if ssl_ctx
948
1034
  start_imap_connection
949
1035
 
@@ -1204,6 +1290,10 @@ module Net
1204
1290
  # both successful. Any error indicates that the connection has not been
1205
1291
  # secured.
1206
1292
  #
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
+ #
1207
1297
  # *Note:*
1208
1298
  # >>>
1209
1299
  # Any #response_handlers added before STARTTLS should be aware that the
@@ -1222,9 +1312,11 @@ module Net
1222
1312
  #
1223
1313
  def starttls(**options)
1224
1314
  @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
1315
+ handled = false
1225
1316
  error = nil
1226
1317
  ok = send_command("STARTTLS") do |resp|
1227
1318
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
1319
+ handled = true
1228
1320
  clear_cached_capabilities
1229
1321
  clear_responses
1230
1322
  start_tls_session
@@ -1236,6 +1328,13 @@ module Net
1236
1328
  disconnect
1237
1329
  raise error
1238
1330
  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
1239
1338
  ok
1240
1339
  end
1241
1340
 
@@ -1670,12 +1769,18 @@ module Net
1670
1769
  # to both admin and user. If this mailbox exists, it returns an array
1671
1770
  # containing objects of type MailboxQuotaRoot and MailboxQuota.
1672
1771
  #
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
+ #
1673
1776
  # Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota
1674
1777
  #
1675
1778
  # ===== Capabilities
1676
1779
  #
1677
- # The server's capabilities must include +QUOTA+
1678
- # [RFC2087[https://tools.ietf.org/html/rfc2087]].
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.
1679
1784
  def getquotaroot(mailbox)
1680
1785
  synchronize do
1681
1786
  send_command("GETQUOTAROOT", mailbox)
@@ -1687,41 +1792,59 @@ module Net
1687
1792
  end
1688
1793
 
1689
1794
  # Sends a {GETQUOTA command [RFC2087 §4.2]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.2]
1690
- # along with specified +mailbox+. If this mailbox exists, then an array
1691
- # containing a MailboxQuota object is returned. This command is generally
1692
- # only available to server admin.
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.
1693
1804
  #
1694
1805
  # Related: #getquotaroot, #setquota, MailboxQuota
1695
1806
  #
1696
1807
  # ===== Capabilities
1697
1808
  #
1698
- # The server's capabilities must include +QUOTA+
1699
- # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1700
- def getquota(mailbox)
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)
1701
1814
  synchronize do
1702
- send_command("GETQUOTA", mailbox)
1815
+ send_command("GETQUOTA", quota_root)
1703
1816
  clear_responses("QUOTA")
1704
1817
  end
1705
1818
  end
1706
1819
 
1707
1820
  # Sends a {SETQUOTA command [RFC2087 §4.1]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.1]
1708
- # along with the specified +mailbox+ and +quota+. If +quota+ is nil, then
1709
- # +quota+ will be unset for that mailbox. Typically one needs to be logged
1710
- # in as a server admin for this to work.
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.
1711
1832
  #
1712
1833
  # Related: #getquota, #getquotaroot
1713
1834
  #
1714
1835
  # ===== Capabilities
1715
1836
  #
1716
- # The server's capabilities must include +QUOTA+
1717
- # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1718
- def setquota(mailbox, quota)
1719
- if quota.nil?
1720
- data = '()'
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 = []
1721
1844
  else
1722
- data = '(STORAGE ' + quota.to_s + ')'
1845
+ list = ["STORAGE", Integer(storage_limit)]
1723
1846
  end
1724
- send_command("SETQUOTA", mailbox, RawData.new(data))
1847
+ send_command("SETQUOTA", quota_root, list)
1725
1848
  end
1726
1849
 
1727
1850
  # Sends a {SETACL command [RFC4314 §3.1]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.1]
@@ -1828,7 +1951,10 @@ module Net
1828
1951
  # <tt>STATUS=SIZE</tt>
1829
1952
  # {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html].
1830
1953
  #
1831
- # +DELETED+ requires the server's capabilities to include +IMAP4rev2+.
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].
1832
1958
  #
1833
1959
  # +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+
1834
1960
  # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
@@ -1977,6 +2103,14 @@ module Net
1977
2103
  #
1978
2104
  # ===== Search criteria
1979
2105
  #
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
+ #
1980
2114
  # For a full list of search criteria,
1981
2115
  # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]],
1982
2116
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
@@ -2064,6 +2198,13 @@ module Net
2064
2198
  #
2065
2199
  # +attr+ is a list of attributes to fetch; see the documentation
2066
2200
  # 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>
2067
2208
  #
2068
2209
  # +changedsince+ is an optional integer mod-sequence. It limits results to
2069
2210
  # messages with a mod-sequence greater than +changedsince+.
@@ -2619,7 +2760,7 @@ module Net
2619
2760
  warn(RESPONSES_DEPRECATION_MSG, uplevel: 1)
2620
2761
  when :frozen_dup
2621
2762
  synchronize {
2622
- responses = @responses.transform_values(&:freeze)
2763
+ responses = @responses.transform_values { _1.dup.freeze }
2623
2764
  responses.default_proc = nil
2624
2765
  responses.default = [].freeze
2625
2766
  return responses.freeze
@@ -2706,6 +2847,10 @@ module Net
2706
2847
  # end
2707
2848
  # }
2708
2849
  #
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
+ #
2709
2854
  # Related: #remove_response_handler, #response_handlers
2710
2855
  def add_response_handler(handler = nil, &block)
2711
2856
  raise ArgumentError, "two Procs are passed" if handler && block
@@ -2732,6 +2877,7 @@ module Net
2732
2877
  def start_imap_connection
2733
2878
  @greeting = get_server_greeting
2734
2879
  @capabilities = capabilities_from_resp_code @greeting
2880
+ @response_handlers.each do |handler| handler.call(@greeting) end
2735
2881
  @receiver_thread = start_receiver_thread
2736
2882
  rescue Exception
2737
2883
  @sock.close
@@ -2860,23 +3006,10 @@ module Net
2860
3006
  end
2861
3007
 
2862
3008
  def get_response
2863
- buff = String.new
2864
- while true
2865
- s = @sock.gets(CRLF)
2866
- break unless s
2867
- buff.concat(s)
2868
- if /\{(\d+)\}\r\n/n =~ s
2869
- s = @sock.read($1.to_i)
2870
- buff.concat(s)
2871
- else
2872
- break
2873
- end
2874
- end
3009
+ buff = @reader.read_response_buffer
2875
3010
  return nil if buff.length == 0
2876
- if config.debug?
2877
- $stderr.print(buff.gsub(/^/n, "S: "))
2878
- end
2879
- return @parser.parse(buff)
3011
+ $stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
3012
+ @parser.parse(buff)
2880
3013
  end
2881
3014
 
2882
3015
  #############################
@@ -2928,6 +3061,7 @@ module Net
2928
3061
  put_string(" ")
2929
3062
  send_data(i, tag)
2930
3063
  end
3064
+ guard_against_tagged_response_skipping_handler!(tag)
2931
3065
  put_string(CRLF)
2932
3066
  if cmd == "LOGOUT"
2933
3067
  @logout_command_tag = tag
@@ -2943,6 +3077,17 @@ module Net
2943
3077
  end
2944
3078
  end
2945
3079
  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])
2946
3091
  end
2947
3092
 
2948
3093
  def generate_tag
@@ -3007,7 +3152,7 @@ module Net
3007
3152
  end
3008
3153
 
3009
3154
  def store_internal(cmd, set, attr, flags, unchangedsince: nil)
3010
- attr = RawData.new(attr) if attr.instance_of?(String)
3155
+ attr = Atom.new(attr) if attr.instance_of?(String)
3011
3156
  args = [MessageSet.new(set)]
3012
3157
  args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
3013
3158
  args << attr << flags
@@ -3077,6 +3222,7 @@ module Net
3077
3222
  raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
3078
3223
  raise "cannot start TLS without SSLContext" unless ssl_ctx
3079
3224
  @sock = SSLSocket.new(@sock, ssl_ctx)
3225
+ @reader = ResponseReader.new(self, @sock)
3080
3226
  @sock.sync_close = true
3081
3227
  @sock.hostname = @host if @sock.respond_to? :hostname=
3082
3228
  ssl_socket_connect(@sock, open_timeout)
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.4.19
4
+ version: 0.4.24
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
@@ -68,6 +68,7 @@ files:
68
68
  - lib/net/imap/response_data.rb
69
69
  - lib/net/imap/response_parser.rb
70
70
  - lib/net/imap/response_parser/parser_utils.rb
71
+ - lib/net/imap/response_reader.rb
71
72
  - lib/net/imap/sasl.rb
72
73
  - lib/net/imap/sasl/anonymous_authenticator.rb
73
74
  - lib/net/imap/sasl/authentication_exchange.rb
@@ -124,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
125
  - !ruby/object:Gem::Version
125
126
  version: '0'
126
127
  requirements: []
127
- rubygems_version: 3.6.2
128
+ rubygems_version: 4.0.6
128
129
  specification_version: 4
129
130
  summary: Ruby client api for Internet Message Access Protocol
130
131
  test_files: []