C# 在.net Framework 4.6.1中使用mailkit支持多内容编码

C# 在.net Framework 4.6.1中使用mailkit支持多内容编码,c#,.net,mailkit,C#,.net,Mailkit,我正在使用.NETFramework4.6.1构建一个电子邮件客户端,它将从邮箱获取电子邮件并显示在电子邮件客户端上。目前我正在使用s22.Imap的ImapClient.GetMessage()方法来检索电子邮件内容。它适用于附件和大多数默认编码的bodyContent 但是我的一些邮件类型是CodePage=932和EncodingName=“Japanese(Shift-JIS)”。我无法获取这些电子邮件,因为它们对大多数BodyEncoding属性/属性抛出了System.NotSupp

我正在使用.NETFramework4.6.1构建一个电子邮件客户端,它将从邮箱获取电子邮件并显示在电子邮件客户端上。目前我正在使用s22.Imap的
ImapClient.GetMessage()
方法来检索电子邮件内容。它适用于附件和大多数默认编码的bodyContent

但是我的一些邮件类型是
CodePage=932
EncodingName=“Japanese(Shift-JIS)”
。我无法获取这些电子邮件,因为它们对大多数BodyEncoding属性/属性抛出了
System.NotSupportedException


在对s22.Imap的github问题进行搜索时,有人建议使用mailkit而不是s22.Imap。我想了解更多关于mailkit中如何处理此编码部分的信息。还想知道是否有一种默认方法来处理未知代码页类型的编码。

您可以阅读这篇博客文章,其中解释了大多数C#MIME解析器出错的原因以及为什么MimeKit可以处理多个字符集编码

是时候对mime解析器大发雷霆了。。。 警告:建议观众自行决定

我应该从哪里开始

我想我应该先说我痴迷于MIME,尤其是MIME解析器。不,真的。我被迷住了。不相信我?在这一点上,我已经编写和/或使用了几个MIME解析器。它开始于我大学时代在Spruce上的工作,它有一个糟糕得可怕的MIME解析器,所以当你进一步阅读我关于糟糕的MIME解析器的咆哮时,请记住:我曾经在那里工作过,我写过一个糟糕的MIME解析器

正如少数人所知,我最近开始实现一个名为的C#MIME解析器。在我进行这项工作的过程中,我一直在GitHub和Google上搜索其他MIME解析器,以了解它们提供的API类型。我想也许我会找到一个提供了设计良好的API的API,它会激励我。也许,由于某种奇迹,我找到了一个实际上相当不错的,我可以贡献自己的作品,而不是从头开始写自己的作品(是的,一厢情愿)。相反,我所发现的都是设计和实现得很差的MIME解析器,其中许多可能出现在本期的头版

我想我先来点垒球

首先,它们中的每一个都被写成
System.String
解析器。不要被那些自称是“流解析器”的人所愚弄,因为他们所做的一切只是在字节流上加上一个
TextReader
,然后开始使用
reader.ReadLine()
。你会问,这有什么不好?对于那些不熟悉MIME的人,我想让你们看看收件箱中原始的电子邮件来源,特别是当你们和美国以外的任何人有通信的时候。希望你的大多数朋友和同事都或多或少地使用了MIME兼容的电子邮件客户端,但我保证你至少会找到一些8比特原始文本的电子邮件

现在,如果他们使用的语言是C或C++,他们可能会逃脱,因为它们在技术上是在字节数组上运行的,但是用java和C语言,一个“string”是Unicode字符串。告诉我:如何从原始字节数组中获取unicode字符串

宾果。在将这些字节转换为unicode字符之前,您需要了解字符集

平心而论,确实没有处理消息头中原始8位文本的好方法,但是通过使用
TextReader
方法,您实际上限制了这种可能性

接下来是
ReadLine()
方法。中的两个早期解析器之一(在0.7天版本中)使用了
ReadLine()
方法,因此我理解其背后的思想。事实上,就正确性而言,这种方法没有错,它更多的是一种“这永远不会是快的”抱怨。在GMime中的两个早期解析器中,pan mime parser.c后端比内存中的解析器慢得可怕。当然,这并不奇怪。当时更让我惊讶的是,当我编写GMime的当前一代解析器(介于v0.7和v1.0之间)时,它的速度与内存中的解析器一样快,并且在任何给定的时间,读取缓冲区中最多只有4k。我的观点是,如果您希望解析器的性能合理,那么有比
ReadLine()
更好的方法。。。你为什么不想要呢?你的用户肯定希望这样

好的,现在是我在发现的几乎所有mime解析器库中遇到的更严重的问题

我认为到目前为止,我发现的每个mime解析器都使用“String.Split()”方法来解析地址头和/或解析头上的参数列表,如内容类型和内容处置

下面是一个C#MIME解析器的示例:

string[] emails = addressHeader.Split(',');
下面是同一个解析器如何解码编码的单词标记:

private static void DecodeHeaders(NameValueCollection headers)
{
    ArrayList tmpKeys = new ArrayList(headers.Keys);

    foreach (string key in headers.AllKeys)
    {
        //strip qp encoding information from the header if present
        headers[key] = Regex.Replace(headers[key].ToString(), @"=\?.*?\?Q\?(.*?)\?=",
            new MatchEvaluator(MyMatchEvaluator), RegexOptions.IgnoreCase | RegexOptions.Multiline);
        headers[key] = Regex.Replace(headers[key].ToString(), @"=\?.*?\?B\?(.*?)\?=",
            new MatchEvaluator(MyMatchEvaluatorBase64), RegexOptions.IgnoreCase | RegexOptions.Multiline);
    }
}

private static string MyMatchEvaluator(Match m)
{
    return DecodeQP(m.Groups[1].Value);
}

private static string MyMatchEvaluatorBase64(Match m)
{
    System.Text.Encoding enc = System.Text.Encoding.UTF7;
    return enc.GetString(Convert.FromBase64String(m.Groups[1].Value));
}
原谅我的语言,但他妈的怎么了?它完全丢弃了每个编码单词标记中的字符集。在引用的可打印标记的情况下,它假设它们都是ASCII(实际上,latin1也可以工作?),而在base64编码字标记的情况下,它假设它们都是UTF-7!?!?他到底是从哪里得到这个主意的?我无法想象他的代码在现实世界中使用任何base64编码的单词标记。如果有什么东西值得一个双面手掌,这就是它

我只想指出,这是本项目的描述所述:

一个用c#编写的小型、高效、可运行的mime解析器库。 ... 我以前使用过几个开源mime解析器,但它们都不是 在一种或另一种编码上失败,或者错过一些关键的编码 信息。这就是我最终决定尝试解决这个问题的原因 我自己 我承认他的MIME解析器很小,但我不得不对“高效”和“wo”提出异议
public static StringDictionary parseHeaderFieldBody ( String field, String fieldbody ) {
    if ( fieldbody==null )
        return null;
    // FIXME: rewrite parseHeaderFieldBody to being regexp based.
    fieldbody = SharpMimeTools.uncommentString (fieldbody);
    StringDictionary fieldbodycol = new StringDictionary ();
    String[] words = fieldbody.Split(new Char[]{';'});
    if ( words.Length>0 ) {
        fieldbodycol.Add (field.ToLower(), words[0].ToLower().Trim());
        for (int i=1; i<words.Length; i++ ) {
            String[] param = words[i].Trim(new Char[]{' ', '\t'}).Split(new Char[]{'='}, 2);
            if ( param.Length==2 ) {
                param[0] = param[0].Trim(new Char[]{' ', '\t'});
                param[1] = param[1].Trim(new Char[]{' ', '\t'});
                if ( param[1].StartsWith("\"") && !param[1].EndsWith("\"")) {
                    do {
                        param[1] += ";" + words[++i];
                    } while ( !words[i].EndsWith("\"") && i<words.Length);
                }
                fieldbodycol.Add ( param[0], SharpMimeTools.parserfc2047Header (param[1].TrimEnd(';').Trim('\"', ' ')) );
            }
        }
    }
    return fieldbodycol;
}
if (m_EncodedWordPattern.RegularExpression.IsMatch(field.Body))
{
    string charset = m_CharsetPattern.RegularExpression.Match(field.Body).Value;
    string text = m_EncodedTextPattern.RegularExpression.Match(field.Body).Value;
    string encoding = m_EncodingPattern.RegularExpression.Match(field.Body).Value;

    Encoding enc = Encoding.GetEncoding(charset);

    byte[] bar;

    if (encoding.ToLower().Equals("q"))
    {
        bar = m_QPDecoder.Decode(ref text);
    }
    else
    {
        bar = m_B64decoder.Decode(ref text);
    }                    
    text = enc.GetString(bar);

    field.Body = Regex.Replace(field.Body,
        m_EncodedWordPattern.TextPattern, text);
    field.Body = field.Body.Replace('_', ' ');
}