Skip to content

Commit b557d51

Browse files
authored
🔀 Merge pull request #111 from ruby/UTF8-for-IMAP4rev2
✨ Parse UTF-8 encoded strings, for UTF8=ACCEPT and IMAP4rev2
2 parents bd2ddc0 + bf71d85 commit b557d51

File tree

9 files changed

+387
-143
lines changed

9 files changed

+387
-143
lines changed

‎benchmarks/generate_parser_benchmarks

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ init = <<RUBY
2828
require "net/imap"
2929
3030
def load_response(file, name)
31-
YAML.unsafe_load_file(file).dig(:tests, name, :response) \\
31+
YAML.unsafe_load_file(file).dig(:tests, name, :response)
32+
.force_encoding "ASCII-8BIT" \\
3233
or abort "ERRORO: missing %p fixture data in %p" % [name, file]
3334
end
3435

‎benchmarks/parser.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ prelude: |2
44
require "net/imap"
55
66
def load_response(file, name)
7-
YAML.unsafe_load_file(file).dig(:tests, name, :response) \
7+
YAML.unsafe_load_file(file).dig(:tests, name, :response)
8+
.force_encoding "ASCII-8BIT" \
89
or abort "ERRORO: missing %p fixture data in %p" % [name, file]
910
end
1011
@@ -560,6 +561,16 @@ benchmark:
560561
response = load_response("../test/net/imap/fixtures/response_parser/thread_responses.yml",
561562
"thread_rfc5256_example5")
562563
script: parser.parse(response)
564+
- name: utf8_in_list_mailbox
565+
prelude: |2
566+
response = load_response("../test/net/imap/fixtures/response_parser/utf8_responses.yml",
567+
"test_utf8_in_list_mailbox")
568+
script: parser.parse(response)
569+
- name: utf8_in_resp_text
570+
prelude: |2
571+
response = load_response("../test/net/imap/fixtures/response_parser/utf8_responses.yml",
572+
"test_utf8_in_resp_text")
573+
script: parser.parse(response)
563574
- name: xlist_inbox
564575
prelude: |2
565576
response = load_response("../test/net/imap/fixtures/response_parser/list_responses.yml",

‎lib/net/imap.rb

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -489,12 +489,9 @@ module Net
489489
# - #move, #uid_move: Moves the specified messages to the end of the
490490
# specified destination mailbox, expunging them from the current mailbox.
491491
#
492-
#--
493-
# ==== RFC6855: UTF8=ACCEPT
494-
# TODO...
495-
# ==== RFC6855: UTF8=ONLY
496-
# TODO...
497-
#++
492+
# ==== RFC6855: <tt>UTF8=ACCEPT</tt>, <tt>UTF8=ONLY</tt>
493+
#
494+
# - See #enable for information about support foi UTF-8 string encoding.
498495
#
499496
#--
500497
# ==== RFC7888: <tt>LITERAL+</tt>, +LITERAL-+
@@ -679,6 +676,11 @@ module Net
679676
# Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
680677
# (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
681678
# <https://www.rfc-editor.org/info/rfc6851>.
679+
# [UTF8=ACCEPT[https://tools.ietf.org/html/rfc6855]]::
680+
# [UTF8=ONLY[https://tools.ietf.org/html/rfc6855]]::
681+
# Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed.,
682+
# "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013,
683+
# <https://www.rfc-editor.org/info/rfc6855>.
682684
#
683685
# === IANA registries
684686
#
@@ -705,6 +707,12 @@ module Net
705707
class IMAP < Protocol
706708
VERSION = "0.3.4"
707709

710+
# Aliases for supported capabilities, to be used with the #enable command.
711+
ENABLE_ALIASES = {
712+
utf8: "UTF8=ACCEPT",
713+
"UTF8=ONLY" => "UTF8=ACCEPT",
714+
}.freeze
715+
708716
autoload :SASL, File.expand_path("imap/sasl", __dir__)
709717
autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
710718

@@ -812,12 +820,14 @@ def disconnected?
812820
# Capability requirements—other than +IMAP4rev1+—are listed in the
813821
# documentation for each command method.
814822
#
823+
# Related: #enable
824+
#
815825
# ===== Basic IMAP4rev1 capabilities
816826
#
817827
# All IMAP4rev1 servers must include +IMAP4rev1+ in their capabilities list.
818828
# All IMAP4rev1 servers must _implement_ the +STARTTLS+,
819829
# <tt>AUTH=PLAIN</tt>, and +LOGINDISABLED+ capabilities, and clients must
820-
# respect their presence or absence. See the capabilites requirements on
830+
# respect their presence or absence. See the capabilities requirements on
821831
# #starttls, #login, and #authenticate.
822832
#
823833
# ===== Using IMAP4rev1 extensions
@@ -1886,26 +1896,84 @@ def uid_thread(algorithm, search_keys, charset)
18861896

18871897
# Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1]
18881898
# {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1]
1889-
# to enable the specified extenstions, which may be either an
1890-
# array or a string. Returns a list of the extensions that were enabled.
1891-
#
1892-
# Some of the extensions that use ENABLE permit the server to send
1893-
# syntax that this class cannot parse. Caution is advised.
1899+
# to enable the specified server +capabilities+. Each capability may be an
1900+
# array, string, or symbol. Returns a list of the capabilities that were
1901+
# enabled.
18941902
#
18951903
# The +ENABLE+ command is only valid in the _authenticated_ state, before
18961904
# any mailbox is selected.
18971905
#
1906+
# Related: #capability
1907+
#
18981908
# ===== Capabilities
18991909
#
1900-
# The server's capabilities must include +ENABLE+
1901-
# [RFC5161[https://tools.ietf.org/html/rfc5161]] or IMAP4REV2
1902-
# [RFC9051[https://tools.ietf.org/html/rfc9051]].
1910+
# The server's capabilities must include
1911+
# +ENABLE+ [RFC5161[https://tools.ietf.org/html/rfc5161]]
1912+
# or +IMAP4REV2+ [RFC9051[https://tools.ietf.org/html/rfc9051]].
19031913
#
19041914
# Additionally, the server capabilities must include a capability matching
19051915
# each enabled extension (usually the same name as the enabled extension).
1906-
def enable(extensions)
1916+
# The following capabilities may be enabled:
1917+
#
1918+
# [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
1919+
#
1920+
# In a future release, <tt>enable(:utf8)</tt> will enable either
1921+
# <tt>"UTF8=ACCEPT"</tt> or <tt>"IMAP4rev2"</tt>, depending on server
1922+
# capabilities.
1923+
#
1924+
# [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
1925+
#
1926+
# The server's capabilities must include <tt>UTF8=ACCEPT</tt> _or_
1927+
# <tt>UTF8=ONLY</tt>.
1928+
#
1929+
# This allows the server to send strings encoded as UTF-8 which might
1930+
# otherwise need to use a 7-bit encoding, such as {modified
1931+
# UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for
1932+
# message headers.
1933+
#
1934+
# *Note:* For now, strings with 8-bit characters are still _sent_ using
1935+
# "literal" syntax. A future update will change how commands send UTF-8
1936+
# strings when <tt>UTF8=ACCEPT</tt> is enabled. This update should be
1937+
# backward-compatible.
1938+
#
1939+
# *Note:* <em>A future update may set string encodings slightly
1940+
# differently</em>, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8"
1941+
# when it is. Currently, the encoding of strings sent as "quoted" or
1942+
# "text" will _always_ be "UTF-8", even when a 7-bit encoding is used
1943+
# (e.g. UTF-7, encoded-words, quoted-printable, base64). And currently,
1944+
# string "literals" sent by the server will always have an "ASCII-8BIT"
1945+
# (binary) encoding, even if they must contain UTF-8 data---although a
1946+
# server _should_ use "quoted" strings once <tt>UTF8=ACCEPT</tt> is
1947+
# enabled.
1948+
#
1949+
# [<tt>"UTF8=ONLY"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
1950+
#
1951+
# A server that reports the <tt>UTF8=ONLY</tt> #capability _requires_ that
1952+
# the client <tt>enable("UTF8=ACCEPT")</tt> before any mailboxes may be
1953+
# selected. For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
1954+
# <tt>enable("UTF8=ACCEPT")</tt>.
1955+
#
1956+
# ===== Unsupported capabilities
1957+
#
1958+
# *Note:* Some extensions that use ENABLE permit the server to send syntax
1959+
# that Net::IMAP cannot parse, which may raise an exception and disconnect.
1960+
# Some extensions may work, but the support may be incomplete, untested, or
1961+
# experimental.
1962+
#
1963+
# Until a capability is documented here as supported, enabling it may result
1964+
# in undocumented behavior and a future release may update with incompatible
1965+
# behavior <em>without warning or deprecation</em>.
1966+
#
1967+
# <em>Caution is advised.</em>
1968+
#
1969+
def enable(*capabilities)
1970+
capabilities = capabilities
1971+
.flatten
1972+
.map {|e| ENABLE_ALIASES[e] || e }
1973+
.uniq
1974+
.join(' ')
19071975
synchronize do
1908-
send_command("ENABLE #{[extensions].flatten.join(' ')}")
1976+
send_command("ENABLE #{capabilities}")
19091977
return @responses.delete("ENABLED")[-1]
19101978
end
19111979
end

0 commit comments

Comments
 (0)