diff --git a/lib/net/imap.rb b/lib/net/imap.rb index fe56bc0810..862b10ee39 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -23,6 +23,7 @@ rescue LoadError end require_relative "imap/command_data" +require_relative "imap/data_encoding" require_relative "imap/response_data" require_relative "imap/response_parser" @@ -1054,46 +1055,6 @@ module Net end end - # Decode a string from modified UTF-7 format to UTF-8. - # - # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a - # slightly modified version of this to encode mailbox names - # containing non-ASCII characters; see [IMAP] section 5.1.3. - # - # Net::IMAP does _not_ automatically encode and decode - # mailbox names to and from UTF-7. - def self.decode_utf7(s) - return s.gsub(/&([^-]+)?-/n) { - if $1 - ($1.tr(",", "/") + "===").unpack1("m").encode(Encoding::UTF_8, Encoding::UTF_16BE) - else - "&" - end - } - end - - # Encode a string from UTF-8 format to modified UTF-7. - def self.encode_utf7(s) - return s.gsub(/(&)|[^\x20-\x7e]+/) { - if $1 - "&-" - else - base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0") - "&" + base64.delete("=").tr("/", ",") + "-" - end - }.force_encoding("ASCII-8BIT") - end - - # Formats +time+ as an IMAP-style date. - def self.format_date(time) - return time.strftime('%d-%b-%Y') - end - - # Formats +time+ as an IMAP-style date-time. - def self.format_datetime(time) - return time.strftime('%d-%b-%Y %H:%M %z') - end - private CRLF = "\r\n" # :nodoc: diff --git a/lib/net/imap/data_encoding.rb b/lib/net/imap/data_encoding.rb new file mode 100644 index 0000000000..d8449f582c --- /dev/null +++ b/lib/net/imap/data_encoding.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Net + class IMAP < Protocol + + # Decode a string from modified UTF-7 format to UTF-8. + # + # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a + # slightly modified version of this to encode mailbox names + # containing non-ASCII characters; see [IMAP] section 5.1.3. + # + # Net::IMAP does _not_ automatically encode and decode + # mailbox names to and from UTF-7. + def self.decode_utf7(s) + return s.gsub(/&([^-]+)?-/n) { + if $1 + ($1.tr(",", "/") + "===").unpack1("m").encode(Encoding::UTF_8, Encoding::UTF_16BE) + else + "&" + end + } + end + + # Encode a string from UTF-8 format to modified UTF-7. + def self.encode_utf7(s) + return s.gsub(/(&)|[^\x20-\x7e]+/) { + if $1 + "&-" + else + base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0") + "&" + base64.delete("=").tr("/", ",") + "-" + end + }.force_encoding("ASCII-8BIT") + end + + # Formats +time+ as an IMAP-style date. + def self.format_date(time) + return time.strftime('%d-%b-%Y') + end + + # Formats +time+ as an IMAP-style date-time. + def self.format_datetime(time) + return time.strftime('%d-%b-%Y %H:%M %z') + end + + end +end diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb index 49e146718e..46008b0531 100644 --- a/test/net/imap/test_imap.rb +++ b/test/net/imap/test_imap.rb @@ -22,44 +22,6 @@ class IMAPTest < Test::Unit::TestCase Socket.do_not_reverse_lookup = @do_not_reverse_lookup end - def test_encode_utf7 - assert_equal("foo", Net::IMAP.encode_utf7("foo")) - assert_equal("&-", Net::IMAP.encode_utf7("&")) - - utf8 = "\357\274\241\357\274\242\357\274\243".dup.force_encoding("UTF-8") - s = Net::IMAP.encode_utf7(utf8) - assert_equal("&,yH,Iv8j-", s) - s = Net::IMAP.encode_utf7("foo&#{utf8}-bar".encode("EUC-JP")) - assert_equal("foo&-&,yH,Iv8j--bar", s) - - utf8 = "\343\201\202&".dup.force_encoding("UTF-8") - s = Net::IMAP.encode_utf7(utf8) - assert_equal("&MEI-&-", s) - s = Net::IMAP.encode_utf7(utf8.encode("EUC-JP")) - assert_equal("&MEI-&-", s) - end - - def test_decode_utf7 - assert_equal("&", Net::IMAP.decode_utf7("&-")) - assert_equal("&-", Net::IMAP.decode_utf7("&--")) - - s = Net::IMAP.decode_utf7("&,yH,Iv8j-") - utf8 = "\357\274\241\357\274\242\357\274\243".dup.force_encoding("UTF-8") - assert_equal(utf8, s) - end - - def test_format_date - time = Time.mktime(2009, 7, 24) - s = Net::IMAP.format_date(time) - assert_equal("24-Jul-2009", s) - end - - def test_format_datetime - time = Time.mktime(2009, 7, 24, 1, 23, 45) - s = Net::IMAP.format_datetime(time) - assert_match(/\A24-Jul-2009 01:23 [+\-]\d{4}\z/, s) - end - if defined?(OpenSSL::SSL::SSLError) def test_imaps_unknown_ca assert_raise(OpenSSL::SSL::SSLError) do diff --git a/test/net/imap/test_imap_data_encoding.rb b/test/net/imap/test_imap_data_encoding.rb new file mode 100644 index 0000000000..2ca1c1822b --- /dev/null +++ b/test/net/imap/test_imap_data_encoding.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "net/imap" +require "test/unit" + +class IMAPDataEncodingTest < Test::Unit::TestCase + + def test_encode_utf7 + assert_equal("foo", Net::IMAP.encode_utf7("foo")) + assert_equal("&-", Net::IMAP.encode_utf7("&")) + + utf8 = "\357\274\241\357\274\242\357\274\243".dup.force_encoding("UTF-8") + s = Net::IMAP.encode_utf7(utf8) + assert_equal("&,yH,Iv8j-", s) + s = Net::IMAP.encode_utf7("foo&#{utf8}-bar".encode("EUC-JP")) + assert_equal("foo&-&,yH,Iv8j--bar", s) + + utf8 = "\343\201\202&".dup.force_encoding("UTF-8") + s = Net::IMAP.encode_utf7(utf8) + assert_equal("&MEI-&-", s) + s = Net::IMAP.encode_utf7(utf8.encode("EUC-JP")) + assert_equal("&MEI-&-", s) + end + + def test_decode_utf7 + assert_equal("&", Net::IMAP.decode_utf7("&-")) + assert_equal("&-", Net::IMAP.decode_utf7("&--")) + + s = Net::IMAP.decode_utf7("&,yH,Iv8j-") + utf8 = "\357\274\241\357\274\242\357\274\243".dup.force_encoding("UTF-8") + assert_equal(utf8, s) + end + + def test_format_date + time = Time.mktime(2009, 7, 24) + s = Net::IMAP.format_date(time) + assert_equal("24-Jul-2009", s) + end + + def test_format_datetime + time = Time.mktime(2009, 7, 24, 1, 23, 45) + s = Net::IMAP.format_datetime(time) + assert_match(/\A24-Jul-2009 01:23 [+\-]\d{4}\z/, s) + end + +end