【问题标题】:IMAP folder path encoding (IMAP UTF-7) for .NET?.NET 的 IMAP 文件夹路径编码 (IMAP UTF-7)?
【发布时间】:2010-10-08 20:18:28
【问题描述】:

IMAP 规范(RFC 2060,5.1.3. 邮箱国际命名约定)描述了如何处理文件夹名称中的非 ASCII 字符。它定义了一个修改的 UTF-7 编码:

按照惯例,国际邮箱 名称是使用指定的 UTF-7 编码的修改版本 在 [UTF-7] 中描述。目的 这些修改是为了纠正 UTF-7 存在以下问题:

  1. UTF-7 使用“+”字符进行移位;这与 邮箱名称中“+”的常见用法,尤其是 USENET 新闻组名称。

  2. UTF-7 的编码是使用“/”字符的 BASE64;这 与使用“/”作为流行的层次分隔符冲突。

  3. UTF-7 禁止“\”的未编码使用;这与 使用“\”作为流行的层次分隔符。

  4. UTF-7 禁止“~”的未编码使用;这与 在某些服务器中使用“~”作为主目录指示符。

  5. UTF-7 允许多种替代形式来表示相同的 细绳;特别是,可打印的 US-ASCII 字符可以 以编码形式表示。

在修改后的 UTF-7 中,可打印的 US-ASCII 字符(“&”除外)代表它们自己; 也就是说,八位字节值为 0x20-0x25 的字符 和 0x27-0x7e。性格 ”&” (0x26) 由两个八位字节序列“&-”表示。

所有其他字符(八位字节值 0x00-0x1f、0x7f-0xff 和所有 Unicode 16 位八位字节)表示 在修改后的 BASE64 中,进一步 从 [UTF-7] 修改“,”是 用于代替“/”。
修改后的 BASE64 不得用于表示 任何打印的 US-ASCII 字符 可以代表自己。

"&" 用于转换为已修改 BASE64 和“-”转换回 US-ASCII。所有名称均以 US-ASCII 开头, 并且必须以 US-ASCII 结尾(即, 以 Unicode 16 位结尾的名称 八位字节必须以“-”结尾)。

在我开始实现它之前,我的问题是:是否有一些 .NET 代码/库(甚至在框架中)可以完成这项工作?我找不到 .NET 资源(仅限 implementations for other languages/frameworks)。

谢谢!

【问题讨论】:

标签: c# .net encoding imap utf-7


【解决方案1】:
//
// ImapEncoding.cs
//
// Author: Jeffrey Stedfast <jestedfa@microsoft.com>
//
// Copyright (c) 2013-2019 Microsoft Corp. (www.microsoft.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

using System.Text;

namespace MailKit.Net.Imap {
    static class ImapEncoding
    {
        const string utf7_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";

        static readonly byte[] utf7_rank = {
            255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
            255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
            255,255,255,255,255,255,255,255,255,255,255, 62, 63,255,255,255,
             52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,255,255,255,
            255,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
             15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
            255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
             41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
        };

        public static string Decode (string text)
        {
            var decoded = new StringBuilder ();
            bool shifted = false;
            int bits = 0, v = 0;
            int index = 0;
            char c;

            while (index < text.Length) {
                c = text[index++];

                if (shifted) {
                    if (c == '-') {
                        // shifted back out of modified UTF-7
                        shifted = false;
                        bits = v = 0;
                    } else if (c > 127) {
                        // invalid UTF-7
                        return text;
                    } else {
                        byte rank = utf7_rank[(byte) c];

                        if (rank == 0xff) {
                            // invalid UTF-7
                            return text;
                        }

                        v = (v << 6) | rank;
                        bits += 6;

                        if (bits >= 16) {
                            char u = (char) ((v >> (bits - 16)) & 0xffff);
                            decoded.Append (u);
                            bits -= 16;
                        }
                    }
                } else if (c == '&' && index < text.Length) {
                    if (text[index] == '-') {
                        decoded.Append ('&');
                        index++;
                    } else {
                        // shifted into modified UTF-7
                        shifted = true;
                    }
                } else {
                    decoded.Append (c);
                }
            }

            return decoded.ToString ();
        }

        static void Utf7ShiftOut (StringBuilder output, int u, int bits)
        {
            if (bits > 0) {
                int x = (u << (6 - bits)) & 0x3f;
                output.Append (utf7_alphabet[x]);
            }

            output.Append ('-');
        }

        public static string Encode (string text)
        {
            var encoded = new StringBuilder ();
            bool shifted = false;
            int bits = 0, u = 0;

            for (int index = 0; index < text.Length; index++) {
                char c = text[index];

                if (c >= 0x20 && c < 0x7f) {
                    // characters with octet values 0x20-0x25 and 0x27-0x7e
                    // represent themselves while 0x26 ("&") is represented
                    // by the two-octet sequence "&-"

                    if (shifted) {
                        Utf7ShiftOut (encoded, u, bits);
                        shifted = false;
                        bits = 0;
                    }

                    if (c == 0x26)
                        encoded.Append ("&-");
                    else
                        encoded.Append (c);
                } else {
                    // base64 encode
                    if (!shifted) {
                        encoded.Append ('&');
                        shifted = true;
                    }

                    u = (u << 16) | (c & 0xffff);
                    bits += 16;

                    while (bits >= 6) {
                        int x = (u >> (bits - 6)) & 0x3f;
                        encoded.Append (utf7_alphabet[x]);
                        bits -= 6;
                    }
                }
            }

            if (shifted)
                Utf7ShiftOut (encoded, u, bits);

            return encoded.ToString ();
        }
    }
}

【讨论】:

    【解决方案2】:

    这太专业了,无法出现在框架中。尽管我见过的许多不完整的“实现”根本不关心转换,但 codeplex 上可能有一些东西,并且很乐意将所有非 us-ascii 字符传递到 IMAP 服务器。

    不过,我过去已经实现了这一点,它实际上只有 30 行代码。您遍历字符串中的所有字符,如果它们在 0x20 和 0x7e 之间的范围内输出它们(不要忘记在“&”之后附加“-”),否则收集所有非 us-ascii 并使用 UTF7 转换它们(或UTF8 + base64,我不太确定)用“,”替换“/”。此外,您需要保持“转移状态”,例如无论您当前是在编码非 us-ascii 还是输出 us-ascii 并在状态更改时附加转换标记“&”和“-”。

    【讨论】:

      【解决方案3】:

      未经测试,但this MIT 许可的代码看起来不错,如果应用了Alekseys 错误修复:

          /// <summary>
          /// Takes a UTF-16 encoded string and encodes it as modified UTF-7.
          /// </summary>
          /// <param name="s">The string to encode.</param>
          /// <returns>A UTF-7 encoded string</returns>
          /// <remarks>IMAP uses a modified version of UTF-7 for encoding international mailbox names. For
          /// details, refer to RFC 3501 section 5.1.3 (Mailbox International Naming Convention).</remarks>
          internal static string UTF7Encode(string s) {
              StringReader reader = new StringReader(s);
              StringBuilder builder = new StringBuilder();
              while (reader.Peek() != -1) {
                  char c = (char)reader.Read();
                  int codepoint = Convert.ToInt32(c);
                  // It's a printable ASCII character.
                  if (codepoint > 0x1F && codepoint < 0x7F) {
                      builder.Append(c == '&' ? "&-" : c.ToString());
                  } else {
                      // The character sequence needs to be encoded.
                      StringBuilder sequence = new StringBuilder(c.ToString());
                      while (reader.Peek() != -1) {
                          codepoint = Convert.ToInt32((char)reader.Peek());
                          if (codepoint > 0x1F && codepoint < 0x7F)
                              break;
                          sequence.Append((char)reader.Read());
                      }
                      byte[] buffer = Encoding.BigEndianUnicode.GetBytes(
                          sequence.ToString());
                      string encoded = Convert.ToBase64String(buffer).Replace('/', ',').
                          TrimEnd('=');
                      builder.Append("&" + encoded + "-");
                  }
              }
              return builder.ToString();
          }
      
          /// <summary>
          /// Takes a modified UTF-7 encoded string and decodes it.
          /// </summary>
          /// <param name="s">The UTF-7 encoded string to decode.</param>
          /// <returns>A UTF-16 encoded "standard" C# string</returns>
          /// <exception cref="FormatException">The input string is not a properly UTF-7 encoded
          /// string.</exception>
          /// <remarks>IMAP uses a modified version of UTF-7 for encoding international mailbox names. For
          /// details, refer to RFC 3501 section 5.1.3 (Mailbox International Naming Convention).</remarks>
          internal static string UTF7Decode(string s) {
              StringReader reader = new StringReader(s);
              StringBuilder builder = new StringBuilder();
              while (reader.Peek() != -1) {
                  char c = (char)reader.Read();
                  if (c == '&' && reader.Peek() != '-') {
                      // The character sequence needs to be decoded.
                      StringBuilder sequence = new StringBuilder();
                      while (reader.Peek() != -1) {
                          if ((c = (char)reader.Read()) == '-')
                              break;
                          sequence.Append(c);
                      }
                      string encoded = sequence.ToString().Replace(',', '/');
                      int pad = encoded.Length % 4;
                      if (pad > 0)
                          encoded = encoded.PadRight(encoded.Length + (4 - pad), '=');
                      try {
                          byte[] buffer = Convert.FromBase64String(encoded);
                          builder.Append(Encoding.BigEndianUnicode.GetString(buffer));
                      } catch (Exception e) {
                          throw new FormatException(
                              "The input string is not in the correct Format.", e);
                      }
                  } else {
                      if (c == '&' && reader.Peek() == '-')
                          reader.Read();
                      builder.Append(c);
                  }
              }
              return builder.ToString();
          }
      

      不要在当前状态下使用this 代码,它包含[...] UTF7.GetBytes([...]) [...] .Replace('+', '&amp;') - 它使用现有的.Net UTF-7 编码例程并且(除其他外)在结果中将+ 替换为&amp; .这是错误,因为它不仅将“移位字符”从+ 更改为&amp;(这是有意且正确的),而且还更改了base64 编码区域内的所有+ 字符(即不得更改为&amp;)。

      【讨论】:

      • 根据 RFC-3501,if (codepoint &gt; 0x1F &amp;&amp; codepoint &lt; 0x80) 应该是 if (codepoint &gt; 0x1F &amp;&amp; codepoint &lt; 0x7F)。来自 RFC-3501:所有其他字符(八位字节值 0x00-0x1f 和 0x7f-0xff)都用修改后的 BASE64 表示...
      • 是的,你是对的,0x7F 不是可打印字符,应该编码为 BASE64。我在答案中更改了两个 if 语句,并在 github 上创建了一个 issue
      • 或者你可以使用 MailKit 的实现:github.com/jstedfast/MailKit/blob/master/MailKit/Net/Imap/…
      猜你喜欢
      • 2021-01-11
      • 2013-03-26
      • 1970-01-01
      • 2015-05-15
      • 2014-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多