使用Mailkit在.net Framework 4.6.1中支持多种内容编码

问题描述 投票:0回答:1

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

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

关于搜索s22.Imap的github问题,有一个issue建议使用mailkit而不是s22.Imap。我想进一步了解mailkit中如何处理此编码部分。还想知道是否存在默认方式来处理未知CodePage类型的编码。

c# .net mailkit
1个回答
0
投票

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

https://jeffreystedfast.blogspot.com/2013/09/time-for-rant-on-mime-parsers.html

由于StackOverflow主持人可能会抱怨发布链接,所以这里是内容的副本和粘贴:

在哑剧解析器上咆哮的时间...

警告:建议谨慎选择查看器。

我应该从哪里开始?

我想我首先要说我迷恋MIME,尤其是MIME解析器。不完全是。我很着迷。不相信我吗?此时,我已经编写和/或处理了多个MIME解析器。它始于我上大学时就在Spruce上工作,该Spruce的MIME分析器非常糟糕,所以当您进一步阅读有关shitty MIME解析器的言论时,请记住:我去过那里,我编写了一个shitty MIME解析器。

[少数人知道,我最近开始实现名为MimeKit的C#MIME解析器。在进行此工作时,我一直在GitHub和Google上进行搜索,以查看那里还存在哪些其他MIME解析器,以了解它们提供了什么样的API。我想也许我会找到一个提供精心设计的API的启发我的。也许,从某种奇迹上来说,我会发现一个实际上非常好的我可以贡献的东西,而不是从头开始写自己的东西(是的,一厢情愿的想法)。取而代之的是,我发现所有设计和实现的MIME解析器都设计不佳,其中许多解析器可能位于Daily WTF的首页。

我想我将从垒球开始。

[首先,事实是它们中的每个都被写为System.String解析器。不要被那些声称是“流解析器”的人所迷惑,因为所有这些都是将TextReader打在字节流的顶部并开始使用reader.ReadLine()。你问这有什么不好?对于不熟悉MIME的用户,我希望您查看收件箱中的原始电子邮件来源,特别是如果您与美国以外的任何人有往来信件时。希望您的大多数朋友和同事都使用或多或少兼容MIME的电子邮件客户端,但我保证您至少会发现一些带有原始8位文本的电子邮件。

现在,如果他们使用的语言是C或C ++,他们可能可以摆脱这种情况,因为从技术上讲,它们可以在字节数组上进行操作,但是对于Java和C#,“字符串”是一个Unicode字符串。告诉我:如何从原始字节数组中获取unicode字符串?

宾果。您需要先了解字符集,然后才能将这些字节转换为Unicode字符。

公平地说,在消息头中确实没有处理原始8位文本的好方法,但是通过使用TextReader方法,实际上限制了可能性。

接下来是ReadLine()方法。 GMime中的两个早期解析器之一(在0.7天的版本中为pan-mime-parser.c)使用了ReadLine()方法,因此我了解其背后的想法。实际上,就正确性而言,这种方法没有任何问题,更多的是“这不可能很快”的抱怨。在GMime中的两个早期解析器中,与内存解析器相比,pan-mime-parser.c后端的运行速度非常慢。当然,这并不奇怪。当时令我惊讶的是,当我编写GMime的当前解析器(有时在v0.7和v1.0之间)时,它的速度与内存解析器一样快,并且仅在内存中达到4k。在任何给定时间的读取缓冲区。我的观点是,如果您希望解析器具有合理的性能,则有比ReadLine()更好的方法……为什么不想要呢?您的用户绝对希望如此。

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

我认为到目前为止,我发现的每个MIME解析器都使用“ String.Split()”方法来解析地址标头和/或解析标头上的参数列表,例如Content-Type和Content-Disposition。

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

string[] emails = addressHeader.Split(',');

IT Crowd - Facepalm

这是同一解析器解码编码词令牌的方式:

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));
}

Star Trek - Double Facepalm

对不起,我的语言,但是该死的是什么?它完全丢弃了每个编码字标记中的字符集。对于带引号的可打印令牌,假定它们都是ASCII码(实际上,latin1是否也可以工作?),而对于base64编码字令牌,则假定它们都属于UTF-7!?!?他在世界上哪个地方得到了这个主意?我无法想象他的代码可以在现实世界中的任何base64编码字令牌上工作。如果有什么需要双面乳液,就是它。

我只想指出,这就是该项目的描述所陈述的内容:

用C#编写的小型,高效且有效的mime解析器库。...我以前使用过几个开源的mime解析器,但是它们要么未能通过一种编码或另一种编码,或错过某些关键编码信息。这就是为什么我决定终于解决这个问题的原因我。我会向您保证他的MIME解析器很小,但是我不得不质疑“有效”和“有效”形容词的问题。由于大量使用字符串分配和正则表达式匹配,因此很难认为它是“有效的”。正如上面指出的代码所示,“工作”有点夸大其词。

伙计...这是您选择“轻量级” MIME解析器时所得到的,因为您认为GMime之类的解析器已“膨胀”。

解析器#2 ...我喜欢称其为“矮胖”方法:

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;
}

[我会给这个家伙一些荣誉,至少他看到他的String.Split()方法有缺陷,因此尝试通过将Humpty Dumpty重新拼凑在一起来进行补偿。当然,有了他的String.Trim()ing,他将无法以任何确定性再次将他放回原处。这些引用的标记中的空格可能具有重要意义。

许多C#MIME解析器喜欢在各处使用Regex。这是一个完全由Regex编写的解析器的摘录(是的,维护它很有趣...):

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('_', ' ');
}

假装正则表达式模式字符串的定义正确(因为它们读起来真令人敬畏,而且我不愿意再次检查它们),用空格替换'_'是错误的(它只能在“ q”的情况下执行),并且Regex.Replace()只是邪恶的。更不用说每个字段中可能有多个编码词。此代码完全无法处理的正文。

人。我知道您喜欢正则表达式,并且它们非常有用,但是它们不能替代编写真正的分词器。如果您想宽容所接受的内容(对于MIME,you really need to be),则尤其如此。

© www.soinside.com 2019 - 2024. All rights reserved.