net-imap 0.5.9 → 0.6.4

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
@@ -359,8 +359,8 @@ module Net
359
359
  #
360
360
  # - #capability: Returns the server's capabilities as an array of strings.
361
361
  #
362
- # <em>In general, #capable? should be used rather than explicitly sending a
363
- # +CAPABILITY+ command to the server.</em>
362
+ # <em>In general,</em> #capable? <em>should be used rather than explicitly
363
+ # sending a +CAPABILITY+ command to the server.</em>
364
364
  # - #noop: Allows the server to send unsolicited untagged #responses.
365
365
  # - #logout: Tells the server to end the session. Enters the +logout+ state.
366
366
  #
@@ -450,8 +450,8 @@ module Net
450
450
  #
451
451
  # Although IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] is not supported
452
452
  # yet, Net::IMAP supports several extensions that have been folded into it:
453
- # +ENABLE+, +IDLE+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, +UNSELECT+,
454
- # <tt>STATUS=SIZE</tt>, and the fetch side of +BINARY+.
453
+ # +ENABLE+, +IDLE+, +LITERAL-+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+,
454
+ # +UNSELECT+, <tt>STATUS=SIZE</tt>, and the fetch side of +BINARY+.
455
455
  # Commands for these extensions are listed with the {Core IMAP
456
456
  # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above.
457
457
  #
@@ -459,9 +459,12 @@ module Net
459
459
  # <em>The following are folded into +IMAP4rev2+ but are currently
460
460
  # unsupported or incompletely supported by</em> Net::IMAP<em>: RFC4466
461
461
  # extensions, +SEARCHRES+, +LIST-EXTENDED+, +LIST-STATUS+,
462
- # +LITERAL-+, and +SPECIAL-USE+.</em>
462
+ # and +SPECIAL-USE+.</em>
463
463
  #
464
464
  # ==== RFC2087: +QUOTA+
465
+ # +NOTE:+ Only the +STORAGE+ quota resource type is currently supported.
466
+ # - Obsoleted by <tt>QUOTA=RES-*</tt> [RFC9208[https://www.rfc-editor.org/rfc/rfc9208]],
467
+ # although the commands are backward compatible.
465
468
  # - #getquota: returns the resource usage and limits for a quota root
466
469
  # - #getquotaroot: returns the list of quota roots for a mailbox, as well as
467
470
  # their resource usage and limits.
@@ -486,9 +489,7 @@ module Net
486
489
  # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].
487
490
  # - Updates #fetch and #uid_fetch with the +BINARY+, +BINARY.PEEK+, and
488
491
  # +BINARY.SIZE+ items. See FetchData#binary and FetchData#binary_size.
489
- #
490
- # >>>
491
- # *NOTE:* The binary extension the #append command is _not_ supported yet.
492
+ # - Updates #append to allow binary messages containing +NULL+ bytes.
492
493
  #
493
494
  # ==== RFC3691: +UNSELECT+
494
495
  # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
@@ -568,6 +569,15 @@ module Net
568
569
  # - Updates #store and #uid_store with the +unchangedsince+ modifier and adds
569
570
  # the +MODIFIED+ ResponseCode to the tagged response.
570
571
  #
572
+ # ==== RFC7888: <tt>LITERAL+</tt>
573
+ # - Literal strings smaller than Config#max_non_synchronizing_literal bytes
574
+ # are sent without waiting for the server's continuation request.
575
+ #
576
+ # ==== RFC7888: +LITERAL-+
577
+ # - Literal strings smaller than 4096 bytes or
578
+ # Config#max_non_synchronizing_literal (whichever is smaller)
579
+ # are sent without waiting for the server's continuation request.
580
+ #
571
581
  # ==== RFC8438: <tt>STATUS=SIZE</tt>
572
582
  # - Updates #status with the +SIZE+ status attribute.
573
583
  #
@@ -578,6 +588,16 @@ module Net
578
588
  # See FetchData#emailid and FetchData#emailid.
579
589
  # - Updates #status with support for the +MAILBOXID+ status attribute.
580
590
  #
591
+ # ==== RFC9208: <tt>QUOTA=RES-*</tt>
592
+ # +NOTE:+ Only the +STORAGE+ quota resource type is currently supported.
593
+ # - Obsoletes the +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
594
+ # extension and provides strict semantics for different resource types.
595
+ # - #getquota: returns the resource usage and limits for a quota root
596
+ # - #getquotaroot: returns the list of quota roots for a mailbox, as well as
597
+ # their resource usage and limits.
598
+ # - #setquota: sets the resource limits for a given quota root.
599
+ # - Updates #status with <tt>"DELETED"</tt> and +DELETED-STORAGE+ attributes.
600
+ #
581
601
  # ==== RFC9394: +PARTIAL+
582
602
  # - Updates #search, #uid_search with the +PARTIAL+ return option which adds
583
603
  # ESearchResult#partial return data.
@@ -631,9 +651,9 @@ module Net
631
651
  # RFC 5322, DOI 10.17487/RFC5322, October 2008,
632
652
  # <https://www.rfc-editor.org/info/rfc5322>.
633
653
  #
634
- # <em>Note: obsoletes</em>
635
- # RFC-2822[https://www.rfc-editor.org/rfc/rfc2822]<em> (April 2001) and</em>
636
- # RFC-822[https://www.rfc-editor.org/rfc/rfc822]<em> (August 1982).</em>
654
+ # *NOTE*: obsoletes
655
+ # RFC-2822[https://www.rfc-editor.org/rfc/rfc2822] (April 2001) and
656
+ # RFC-822[https://www.rfc-editor.org/rfc/rfc822] (August 1982).
637
657
  #
638
658
  # [CHARSET[https://www.rfc-editor.org/rfc/rfc2978]]::
639
659
  # Freed, N. and J. Postel, "IANA Charset Registration Procedures", BCP 19,
@@ -698,13 +718,12 @@ module Net
698
718
  #
699
719
  # === \IMAP Extensions
700
720
  #
701
- # [QUOTA[https://www.rfc-editor.org/rfc/rfc9208]]::
702
- # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
703
- # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
721
+ # [QUOTA[https://www.rfc-editor.org/rfc/rfc2087]]::
722
+ # Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
723
+ # January 1997, <https://www.rfc-editor.org/info/rfc2087>.
704
724
  #
705
- # <em>Note: obsoletes</em>
706
- # RFC-2087[https://www.rfc-editor.org/rfc/rfc2087]<em> (January 1997)</em>.
707
- # <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
725
+ # *NOTE*: _obsoleted_ by RFC9208[https://www.rfc-editor.org/rfc/rfc9208]
726
+ # (March 2022).
708
727
  # [IDLE[https://www.rfc-editor.org/rfc/rfc2177]]::
709
728
  # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
710
729
  # June 1997, <https://www.rfc-editor.org/info/rfc2177>.
@@ -741,8 +760,8 @@ module Net
741
760
  # Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
742
761
  # (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
743
762
  # <https://www.rfc-editor.org/info/rfc6851>.
744
- # [UTF8=ACCEPT[https://www.rfc-editor.org/rfc/rfc6855]]::
745
- # [UTF8=ONLY[https://www.rfc-editor.org/rfc/rfc6855]]::
763
+ # [{UTF8=ACCEPT}[https://www.rfc-editor.org/rfc/rfc6855]]::
764
+ # [{UTF8=ONLY}[https://www.rfc-editor.org/rfc/rfc6855]]::
746
765
  # Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed.,
747
766
  # "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013,
748
767
  # <https://www.rfc-editor.org/info/rfc6855>.
@@ -756,6 +775,11 @@ module Net
756
775
  # Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
757
776
  # RFC 8474, DOI 10.17487/RFC8474, September 2018,
758
777
  # <https://www.rfc-editor.org/info/rfc8474>.
778
+ # [{QUOTA=RES-*}[https://www.rfc-editor.org/rfc/rfc9208]]::
779
+ # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
780
+ # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
781
+ #
782
+ # Obsoletes RFC2087[https://www.rfc-editor.org/rfc/rfc2087].
759
783
  # [PARTIAL[https://www.rfc-editor.org/info/rfc9394]]::
760
784
  # Melnikov, A., Achuthan, A., Nagulakonda, V., and L. Alves,
761
785
  # "IMAP PARTIAL Extension for Paged SEARCH and FETCH", RFC 9394,
@@ -769,6 +793,7 @@ module Net
769
793
  #
770
794
  # === IANA registries
771
795
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
796
+ # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
772
797
  # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
773
798
  # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
774
799
  # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
@@ -779,8 +804,8 @@ module Net
779
804
  # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
780
805
  # +imap+
781
806
  # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
807
+ #
782
808
  # ==== For currently unsupported features:
783
- # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
784
809
  # * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
785
810
  # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
786
811
  # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
@@ -788,7 +813,7 @@ module Net
788
813
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
789
814
  #
790
815
  class IMAP < Protocol
791
- VERSION = "0.5.9"
816
+ VERSION = "0.6.4"
792
817
 
793
818
  # Aliases for supported capabilities, to be used with the #enable command.
794
819
  ENABLE_ALIASES = {
@@ -805,10 +830,6 @@ module Net
805
830
  autoload :StringPrep, "#{dir}/stringprep"
806
831
 
807
832
  include MonitorMixin
808
- if defined?(OpenSSL::SSL)
809
- include OpenSSL
810
- include SSL
811
- end
812
833
 
813
834
  # :call-seq:
814
835
  # Net::IMAP::SequenceSet(set = nil) -> SequenceSet
@@ -1124,6 +1145,31 @@ module Net
1124
1145
  start_imap_connection
1125
1146
  end
1126
1147
 
1148
+ # Returns a string representation of +self+, showing basic client state
1149
+ # information.
1150
+ #
1151
+ # imap = Net::IMAP.new(hostname, ssl: true)
1152
+ # imap.inspect #=> "#<Net::IMAP imap.example.net:993 TLS not_authenticated>"
1153
+ #
1154
+ # imap.authenticate(:oauthbearer, "user", token)
1155
+ # imap.inspect #=> "#<Net::IMAP imap.example.net:993 TLS authenticated>"
1156
+ #
1157
+ # imap.select("INBOX")
1158
+ # imap.inspect #=> "#<Net::IMAP imap.example.net:993 TLS selected>"
1159
+ #
1160
+ # imap.logout
1161
+ # imap.inspect #=> "#<Net::IMAP imap.example.net:993 TLS logout>"
1162
+ #
1163
+ def inspect
1164
+ tls_state = tls_verified? ? "TLS" :
1165
+ ssl_ctx ? "TLS (NOT VERIFIED)" :
1166
+ "PLAINTEXT"
1167
+ conn_state = disconnected? ? "disconnected" : connection_state.to_sym
1168
+ "#<%s:0x%08x %s:%s %s %s>" % [
1169
+ self.class.name, __id__, host, port, tls_state, conn_state
1170
+ ]
1171
+ end
1172
+
1127
1173
  # Returns true after the TLS negotiation has completed and the remote
1128
1174
  # hostname has been verified. Returns false when TLS has been established
1129
1175
  # but peer verification was disabled.
@@ -1394,9 +1440,11 @@ module Net
1394
1440
  #
1395
1441
  def starttls(**options)
1396
1442
  @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
1443
+ handled = false
1397
1444
  error = nil
1398
1445
  ok = send_command("STARTTLS") do |resp|
1399
1446
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
1447
+ handled = true
1400
1448
  clear_cached_capabilities
1401
1449
  clear_responses
1402
1450
  start_tls_session
@@ -1408,6 +1456,13 @@ module Net
1408
1456
  disconnect
1409
1457
  raise error
1410
1458
  end
1459
+ unless handled
1460
+ disconnect
1461
+ raise InvalidResponseError,
1462
+ "STARTTLS handler was bypassed, although server responded %p" % [
1463
+ ok.raw_data.chomp
1464
+ ]
1465
+ end
1411
1466
  ok
1412
1467
  end
1413
1468
 
@@ -1522,6 +1577,7 @@ module Net
1522
1577
  # completes. If the TaggedResponse to #authenticate includes updated
1523
1578
  # capabilities, they will be cached.
1524
1579
  def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback)
1580
+ sasl_ir = may_depend_on_capabilities_cached?(sasl_ir)
1525
1581
  sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback)
1526
1582
  .tap do state_authenticated! _1 end
1527
1583
  end
@@ -1573,7 +1629,7 @@ module Net
1573
1629
  # When the +condstore+ keyword argument is true, the server is told to
1574
1630
  # enable the extension. If +mailbox+ supports persistence of mod-sequences,
1575
1631
  # the +HIGHESTMODSEQ+ ResponseCode will be sent as an untagged response to
1576
- # #select and all `FETCH` responses will include FetchData#modseq.
1632
+ # #select and all +FETCH+ responses will include FetchData#modseq.
1577
1633
  # Otherwise, the +NOMODSEQ+ ResponseCode will be sent.
1578
1634
  #
1579
1635
  # A Net::IMAP::NoResponseError is raised if the mailbox does not
@@ -1828,12 +1884,18 @@ module Net
1828
1884
  # to both admin and user. If this mailbox exists, it returns an array
1829
1885
  # containing objects of type MailboxQuotaRoot and MailboxQuota.
1830
1886
  #
1887
+ # *NOTE:* Currently, Net::IMAP only supports +QUOTA+ responses with a single
1888
+ # resource type. This is usually +STORAGE+, but you may need to verify this
1889
+ # with UntaggedResponse#raw_data.
1890
+ #
1831
1891
  # Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota
1832
1892
  #
1833
1893
  # ==== Capabilities
1834
1894
  #
1835
- # The server's capabilities must include +QUOTA+
1836
- # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1895
+ # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1896
+ # capability, or a capability prefixed with <tt>QUOTA=RES-*</tt>
1897
+ # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
1898
+ # resource type.
1837
1899
  def getquotaroot(mailbox)
1838
1900
  synchronize do
1839
1901
  send_command("GETQUOTAROOT", mailbox)
@@ -1845,41 +1907,59 @@ module Net
1845
1907
  end
1846
1908
 
1847
1909
  # Sends a {GETQUOTA command [RFC2087 §4.2]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.2]
1848
- # along with specified +mailbox+. If this mailbox exists, then an array
1849
- # containing a MailboxQuota object is returned. This command is generally
1850
- # only available to server admin.
1910
+ # for the +quota_root+. If this quota root exists, then an array
1911
+ # containing a MailboxQuota object is returned.
1912
+ #
1913
+ # The names of quota roots that are applicable to a particular mailbox can
1914
+ # be discovered with #getquotaroot.
1915
+ #
1916
+ # *NOTE:* Currently, Net::IMAP only supports +QUOTA+ responses with a single
1917
+ # resource type. This is usually +STORAGE+, but you may need to verify this
1918
+ # with UntaggedResponse#raw_data.
1851
1919
  #
1852
1920
  # Related: #getquotaroot, #setquota, MailboxQuota
1853
1921
  #
1854
1922
  # ==== Capabilities
1855
1923
  #
1856
- # The server's capabilities must include +QUOTA+
1857
- # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1858
- def getquota(mailbox)
1924
+ # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1925
+ # capability, or a capability prefixed with <tt>QUOTA=RES-*</tt>
1926
+ # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
1927
+ # resource type.
1928
+ def getquota(quota_root)
1859
1929
  synchronize do
1860
- send_command("GETQUOTA", mailbox)
1930
+ send_command("GETQUOTA", quota_root)
1861
1931
  clear_responses("QUOTA")
1862
1932
  end
1863
1933
  end
1864
1934
 
1865
1935
  # Sends a {SETQUOTA command [RFC2087 §4.1]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.1]
1866
- # along with the specified +mailbox+ and +quota+. If +quota+ is nil, then
1867
- # +quota+ will be unset for that mailbox. Typically one needs to be logged
1868
- # in as a server admin for this to work.
1936
+ # along with the specified +quota_root+ and +storage_limit+. If
1937
+ # +storage_limit+ is +nil+, resource limits are unset for that quota root.
1938
+ # If +storage_limit+ is a number, it sets the +STORAGE+ resource limit.
1939
+ #
1940
+ # imap.setquota "#user/alice", 100
1941
+ # imap.getquota "#user/alice"
1942
+ # # => [#<struct Net::IMAP::MailboxQuota mailbox="#user/alice" usage=54 quota=100>]
1943
+ #
1944
+ # Typically one needs to be logged in as a server admin for this to work.
1945
+ #
1946
+ # *NOTE:* Currently, Net::IMAP only supports setting +STORAGE+ quota limits.
1869
1947
  #
1870
1948
  # Related: #getquota, #getquotaroot
1871
1949
  #
1872
1950
  # ==== Capabilities
1873
1951
  #
1874
- # The server's capabilities must include +QUOTA+
1875
- # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1876
- def setquota(mailbox, quota)
1877
- if quota.nil?
1878
- data = '()'
1952
+ # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1953
+ # capability, or a capability prefixed with <tt>QUOTA=RES-*</tt>
1954
+ # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
1955
+ # resource type.
1956
+ def setquota(quota_root, storage_limit)
1957
+ if storage_limit.nil?
1958
+ list = []
1879
1959
  else
1880
- data = '(STORAGE ' + quota.to_s + ')'
1960
+ list = ["STORAGE", NumValidator.coerce_number64(storage_limit)]
1881
1961
  end
1882
- send_command("SETQUOTA", mailbox, RawData.new(data))
1962
+ send_command("SETQUOTA", quota_root, list)
1883
1963
  end
1884
1964
 
1885
1965
  # Sends a {SETACL command [RFC4314 §3.1]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.1]
@@ -1986,7 +2066,10 @@ module Net
1986
2066
  # <tt>STATUS=SIZE</tt>
1987
2067
  # {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html].
1988
2068
  #
1989
- # +DELETED+ requires the server's capabilities to include +IMAP4rev2+.
2069
+ # +DELETED+ must be supported when the server's capabilities includes
2070
+ # +IMAP4rev2+.
2071
+ # or <tt>QUOTA=RES-MESSAGES</tt>
2072
+ # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208.html].
1990
2073
  #
1991
2074
  # +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+
1992
2075
  # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
@@ -2022,9 +2105,14 @@ module Net
2022
2105
  #
2023
2106
  # ==== Capabilities
2024
2107
  #
2108
+ # If +BINARY+ [RFC3516[https://www.rfc-editor.org/rfc/rfc3516.html]] is
2109
+ # supported by the server, +message+ may contain +NULL+ characters and
2110
+ # be sent as a binary literal. Otherwise, binary message parts must be
2111
+ # encoded appropriately (for example, +base64+).
2112
+ #
2025
2113
  # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
2026
2114
  # supported and the destination supports persistent UIDs, the server's
2027
- # response should include an +APPENDUID+ response code with UIDPlusData.
2115
+ # response should include an +APPENDUID+ response code with AppendUIDData.
2028
2116
  # This will report the UIDVALIDITY of the destination mailbox and the
2029
2117
  # assigned UID of the appended message.
2030
2118
  #
@@ -2032,12 +2120,11 @@ module Net
2032
2120
  # TODO: add MULTIAPPEND support
2033
2121
  #++
2034
2122
  def append(mailbox, message, flags = nil, date_time = nil)
2123
+ message = StringFormatter.literal_or_literal8(message, name: "message")
2035
2124
  args = []
2036
- if flags
2037
- args.push(flags)
2038
- end
2125
+ args.push(flags) if flags
2039
2126
  args.push(date_time) if date_time
2040
- args.push(Literal.new(message))
2127
+ args.push(message)
2041
2128
  send_command("APPEND", mailbox, *args)
2042
2129
  end
2043
2130
 
@@ -2110,8 +2197,8 @@ module Net
2110
2197
  end
2111
2198
 
2112
2199
  # call-seq:
2113
- # uid_expunge{uid_set) -> array of message sequence numbers
2114
- # uid_expunge{uid_set) -> VanishedData of UIDs
2200
+ # uid_expunge(uid_set) -> array of message sequence numbers
2201
+ # uid_expunge(uid_set) -> VanishedData of UIDs
2115
2202
  #
2116
2203
  # Sends a {UID EXPUNGE command [RFC4315 §2.1]}[https://www.rfc-editor.org/rfc/rfc4315#section-2.1]
2117
2204
  # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9]
@@ -2267,11 +2354,11 @@ module Net
2267
2354
  # Encoded as an \IMAP date (see ::encode_date).
2268
2355
  #
2269
2356
  # [When +criteria+ is a String]
2270
- # +criteria+ will be sent directly to the server <em>without any
2271
- # validation or encoding</em>.
2357
+ # +criteria+ will be sent to the server <em>with minimal validation and no
2358
+ # encoding or formatting</em>.
2272
2359
  #
2273
- # <em>*WARNING:* This is vulnerable to injection attacks when external
2274
- # inputs are used.</em>
2360
+ # <em>*WARNING:* Although CRLF is prohibited, this is vulnerable to other
2361
+ # types of attribute injection attack if unvetted user input is used.</em>
2275
2362
  #
2276
2363
  # ==== Supported return options
2277
2364
  #
@@ -2592,6 +2679,13 @@ module Net
2592
2679
  #
2593
2680
  # +attr+ is a list of attributes to fetch; see FetchStruct documentation for
2594
2681
  # a list of supported attributes.
2682
+ # >>>
2683
+ # When +attr+ is a String, it will be sent <em>with minimal validation and
2684
+ # no encoding or formatting</em>. When +attr+ is an Array, each String in
2685
+ # +attr+ will be sent this way.
2686
+ #
2687
+ # <em>*WARNING:* Although CRLF is prohibited, this is vulnerable to other
2688
+ # types of attribute injection attack if unvetted user input is used.</em>
2595
2689
  #
2596
2690
  # +changedsince+ is an optional integer mod-sequence. It limits results to
2597
2691
  # messages with a mod-sequence greater than +changedsince+.
@@ -2675,6 +2769,7 @@ module Net
2675
2769
  # # fetch should return quickly and allocate little memory
2676
2770
  # results.size # => 0..500
2677
2771
  # break if results.empty?
2772
+ # results.sort_by!(&:uid) # server may return results out of order
2678
2773
  # next_uid_to_fetch = results.last.uid + 1
2679
2774
  # process results
2680
2775
  # end
@@ -2780,7 +2875,7 @@ module Net
2780
2875
  #
2781
2876
  # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
2782
2877
  # supported, the server's response should include a +COPYUID+ response code
2783
- # with UIDPlusData. This will report the UIDVALIDITY of the destination
2878
+ # with CopyUIDData. This will report the UIDVALIDITY of the destination
2784
2879
  # mailbox, the UID set of the source messages, and the assigned UID set of
2785
2880
  # the moved messages.
2786
2881
  #
@@ -2821,7 +2916,7 @@ module Net
2821
2916
  #
2822
2917
  # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
2823
2918
  # supported, the server's response should include a +COPYUID+ response code
2824
- # with UIDPlusData. This will report the UIDVALIDITY of the destination
2919
+ # with CopyUIDData. This will report the UIDVALIDITY of the destination
2825
2920
  # mailbox, the UID set of the source messages, and the assigned UID set of
2826
2921
  # the moved messages.
2827
2922
  #
@@ -2961,6 +3056,18 @@ module Net
2961
3056
  # command parameters defined by the extension will implicitly enable it.
2962
3057
  # See {[RFC7162 §3.1]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1].
2963
3058
  #
3059
+ # [+QRESYNC+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]]
3060
+ # *NOTE:* Enabling QRESYNC will replace +EXPUNGE+ with +VANISHED+, but
3061
+ # the extension arguments to #select, #examine, and #uid_fetch are not
3062
+ # supported yet.
3063
+ #
3064
+ # Adds quick resynchronization options to #select, #examine, and
3065
+ # #uid_fetch. +QRESYNC+ _must_ be explicitly enabled before using any of
3066
+ # the extension's command parameters. All +EXPUNGE+ responses will be
3067
+ # replaced with +VANISHED+ responses. Enabling +QRESYNC+ implicitly
3068
+ # enables +CONDSTORE+ as well.
3069
+ # See {[RFC7162 §3.2]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.2].
3070
+ #
2964
3071
  # [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
2965
3072
  #
2966
3073
  # In a future release, <tt>enable(:utf8)</tt> will enable either
@@ -3067,6 +3174,7 @@ module Net
3067
3174
 
3068
3175
  synchronize do
3069
3176
  tag = Thread.current[:net_imap_tag] = generate_tag
3177
+ guard_against_tagged_response_skipping_handler!(tag, "IDLE")
3070
3178
  put_string("#{tag} IDLE#{CRLF}")
3071
3179
 
3072
3180
  begin
@@ -3220,7 +3328,7 @@ module Net
3220
3328
  warn(RESPONSES_DEPRECATION_MSG, uplevel: 1, category: :deprecated)
3221
3329
  when :frozen_dup
3222
3330
  synchronize {
3223
- responses = @responses.transform_values(&:freeze)
3331
+ responses = @responses.transform_values { _1.dup.freeze }
3224
3332
  responses.default_proc = nil
3225
3333
  responses.default = [].freeze
3226
3334
  return responses.freeze
@@ -3471,7 +3579,7 @@ module Net
3471
3579
  raise BadResponseError, resp
3472
3580
  else
3473
3581
  disconnect
3474
- raise InvalidResponseError, "invalid tagged resp: %p" % [resp.raw.chomp]
3582
+ raise InvalidResponseError, "invalid tagged resp: %p" % [resp.raw_data.chomp]
3475
3583
  end
3476
3584
  end
3477
3585
 
@@ -3531,21 +3639,29 @@ module Net
3531
3639
  put_string(" ")
3532
3640
  send_data(i, tag)
3533
3641
  end
3534
- put_string(CRLF)
3535
- if cmd == "LOGOUT"
3536
- @logout_command_tag = tag
3537
- end
3538
- if block
3539
- add_response_handler(&block)
3540
- end
3642
+ @logout_command_tag = tag if cmd == "LOGOUT"
3643
+ guard_against_tagged_response_skipping_handler!(tag, cmd)
3644
+ add_response_handler(&block) if block
3541
3645
  begin
3542
- return get_tagged_response(tag, cmd)
3646
+ put_string(CRLF)
3647
+ get_tagged_response(tag, cmd)
3543
3648
  ensure
3544
- if block
3545
- remove_response_handler(block)
3546
- end
3649
+ remove_response_handler(block) if block
3547
3650
  end
3548
3651
  end
3652
+ rescue InvalidResponseError
3653
+ disconnect
3654
+ raise
3655
+ end
3656
+
3657
+ def guard_against_tagged_response_skipping_handler!(tag, cmd)
3658
+ return unless (resp = @tagged_responses[tag])&.name&.upcase == "OK"
3659
+ raise InvalidResponseError, format(
3660
+ "Received tagged 'OK' to incomplete %s command (tag=%s). " \
3661
+ "This could indicate a malicious server, a man-in-the-middle, or " \
3662
+ "client-side command injection. Disconnecting.",
3663
+ cmd, tag
3664
+ )
3549
3665
  end
3550
3666
 
3551
3667
  def generate_tag
@@ -3569,11 +3685,11 @@ module Net
3569
3685
  end
3570
3686
 
3571
3687
  def enforce_logindisabled?
3572
- if config.enforce_logindisabled == :when_capabilities_cached
3573
- capabilities_cached?
3574
- else
3575
- config.enforce_logindisabled
3576
- end
3688
+ may_depend_on_capabilities_cached?(config.enforce_logindisabled)
3689
+ end
3690
+
3691
+ def may_depend_on_capabilities_cached?(value)
3692
+ value == :when_capabilities_cached ? capabilities_cached? : value
3577
3693
  end
3578
3694
 
3579
3695
  def expunge_internal(...)
@@ -3672,6 +3788,9 @@ module Net
3672
3788
  end
3673
3789
 
3674
3790
  def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
3791
+ if partial && !cmd.start_with?("UID ")
3792
+ raise ArgumentError, "partial can only be used with uid_fetch"
3793
+ end
3675
3794
  set = SequenceSet[set]
3676
3795
  if partial
3677
3796
  mod ||= []
@@ -3696,7 +3815,7 @@ module Net
3696
3815
  end
3697
3816
 
3698
3817
  def store_internal(cmd, set, attr, flags, unchangedsince: nil)
3699
- attr = RawData.new(attr) if attr.instance_of?(String)
3818
+ attr = Atom.new(attr) if attr.instance_of?(String)
3700
3819
  args = [SequenceSet.new(set)]
3701
3820
  args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
3702
3821
  args << attr << flags
@@ -3766,12 +3885,9 @@ module Net
3766
3885
  def build_ssl_ctx(ssl)
3767
3886
  if ssl
3768
3887
  params = (Hash.try_convert(ssl) || {}).freeze
3769
- context = SSLContext.new
3888
+ context = OpenSSL::SSL::SSLContext.new
3770
3889
  context.set_params(params)
3771
- if defined?(VerifyCallbackProc)
3772
- context.verify_callback = VerifyCallbackProc
3773
- end
3774
- context.freeze
3890
+ context.setup
3775
3891
  [params, context]
3776
3892
  else
3777
3893
  false
@@ -3782,12 +3898,12 @@ module Net
3782
3898
  raise "SSL extension not installed" unless defined?(OpenSSL::SSL)
3783
3899
  raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
3784
3900
  raise "cannot start TLS without SSLContext" unless ssl_ctx
3785
- @sock = SSLSocket.new(@sock, ssl_ctx)
3901
+ @sock = OpenSSL::SSL::SSLSocket.new(@sock, ssl_ctx)
3786
3902
  @reader = ResponseReader.new(self, @sock)
3787
3903
  @sock.sync_close = true
3788
3904
  @sock.hostname = @host if @sock.respond_to? :hostname=
3789
3905
  ssl_socket_connect(@sock, open_timeout)
3790
- if ssl_ctx.verify_mode != VERIFY_NONE
3906
+ if ssl_ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE
3791
3907
  @sock.post_connection_check(@host)
3792
3908
  @tls_verified = true
3793
3909
  end
@@ -3851,7 +3967,6 @@ require_relative "imap/errors"
3851
3967
  require_relative "imap/config"
3852
3968
  require_relative "imap/command_data"
3853
3969
  require_relative "imap/data_encoding"
3854
- require_relative "imap/data_lite"
3855
3970
  require_relative "imap/flags"
3856
3971
  require_relative "imap/response_data"
3857
3972
  require_relative "imap/response_parser"
data/net-imap.gemspec CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.summary = %q{Ruby client api for Internet Message Access Protocol}
17
17
  spec.description = %q{Ruby client api for Internet Message Access Protocol}
18
18
  spec.homepage = "https://github.com/ruby/net-imap"
19
- spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0")
19
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0")
20
20
  spec.licenses = ["Ruby", "BSD-2-Clause"]
21
21
 
22
22
  spec.metadata["homepage_uri"] = spec.homepage
data/rakelib/rdoc.rake CHANGED
@@ -12,17 +12,6 @@ module RDoc::Generator
12
12
  end
13
13
  end
14
14
 
15
- # See https://github.com/ruby/rdoc/pull/936
16
- module FixSectionComments
17
- def markup(text)
18
- @store ||= @parent&.store
19
- super
20
- end
21
- def description; markup comment end
22
- def comment; super || @comments&.first end
23
- def parse(_comment_location = nil) super() end
24
- end
25
-
26
15
  # render "[label] data" lists as tables. adapted from "hanna-nouveau" gem.
27
16
  module LabelListTable
28
17
  def list_item_start(list_item, list_type)
@@ -51,20 +40,14 @@ class RDoc::AnyMethod
51
40
  prepend RDoc::Generator::NetIMAP::RemoveRedundantParens
52
41
  end
53
42
 
54
- class RDoc::Context::Section
55
- prepend RDoc::Generator::NetIMAP::FixSectionComments
56
- end
57
-
58
43
  class RDoc::Markup::ToHtml
59
44
  LIST_TYPE_TO_HTML[:NOTE] = ['<table class="rdoc-list note-list"><tbody>', '</tbody></table>']
60
45
  prepend RDoc::Generator::NetIMAP::LabelListTable
61
46
  end
62
47
 
63
48
  RDoc::Task.new do |doc|
64
- doc.main = "README.md"
65
49
  doc.title = "net-imap #{Net::IMAP::VERSION}"
66
50
  doc.rdoc_dir = "doc"
67
- doc.rdoc_files = FileList.new %w[lib/**/*.rb *.rdoc *.md]
68
51
  doc.options << "--template-stylesheets" << "docs/styles.css"
69
- # doc.generator = "hanna"
52
+ doc.generator = "darkfish" # TODO: fix issues with aliki
70
53
  end