Поддержка нескольких кодировок контента в. net Framework 4.6.1 с использованием mailkit - PullRequest
0 голосов
/ 24 марта 2020

Я работаю над созданием почтового клиента, используя. net framework 4.6.1, который будет получать письма из почтового ящика и отображать их в почтовом клиенте. В настоящее время я использую метод ImapClient.GetMessage() s22.Imap для получения содержимого электронной почты. Он отлично работает для вложений и для bodyContent с большинством кодировки по умолчанию.

Но некоторые из моих писем имеют тип CodePage = 932 и EncodingName = "Japanese (Shift-JIS)". Я не могу получить эти электронные письма, так как они выбрасывают System.NotSupportedException для большинства атрибутов / свойств BodyEncoding.

При поиске проблем github для s22.Imap возникла проблема , в которой предлагалось использовать mailkit вместо s22.Imap. Я хотел бы узнать больше о том, как эта часть кодирования обрабатывается в mailkit. Также хотелось бы узнать, есть ли способ обработки кодировки неизвестных типов CodePage по умолчанию.

1 Ответ

1 голос
/ 24 марта 2020

Вы можете прочитать это сообщение в блоге, которое объясняет, что большинство C# парсеров MIME ошибаются и почему MimeKit может обрабатывать несколько кодировок кодировок.

https://jeffreystedfast.blogspot.com/2013/09/time-for-rant-on-mime-parsers.html https://jeffreystedfast.blogspot.com/2013/08/why-decoding-rfc2047-encoded-headers-is.html

Поскольку модераторы StackOverflow, скорее всего, будут жаловаться на размещение ссылки, вот копия и вставка содержимого:

Время разглагольствования на парсерах MIME ...

Предупреждение: рекомендуется на усмотрение зрителя.

С чего мне начать?

Думаю, мне следует начать с того, что я одержим MIME и, в частности, парсерами MIME. Нет, правда. Я одержим Не веришь мне? На данный момент я написал и / или работал над несколькими MIME-парсерами. Это началось в мои студенческие годы, когда я работал над Spruce, у которого был ужасно плохой MIME-парсер, и поэтому, читая дальше в моей статье о дерьмовых парсерах MIME, имейте в виду: я был там, я написал дерьмовый MIME парсер.

Как известно нескольким людям, я недавно начал реализовывать парсер C# MIME с именем MimeKit . Работая над этим, я искал GitHub и Google, чтобы узнать, какие существуют другие MIME-парсеры, чтобы выяснить, какие API они предоставляют. Я подумал, что, возможно, я найду тот, который предлагает хорошо разработанный API, который вдохновит меня. Возможно, каким-то чудом я бы нашел тот, который на самом деле был довольно хорош, и в который я мог бы просто внести свой вклад вместо того, чтобы писать свой собственный с нуля (да, желаемое за действительное). Вместо этого все, что я обнаружил, это плохо спроектированные и реализованные парсеры MIME, многие из которых, вероятно, принадлежат на первой странице Daily WTF .

Я предполагаю, что начну с некоторых софтболов.

Во-первых, есть факт, что каждый из них был написан как System.String парсеры. Не дайте себя одурачить тем, кто утверждает, что они являются «анализаторами потока», потому что все, что они делали, это шлепнули TextReader поверх потока байтов и начали использовать reader.ReadLine(). Вы спросите, что в этом плохого? Для тех, кто не знаком с MIME, я хотел бы, чтобы вы взглянули на необработанные источники электронной почты в ваших почтовых ящиках, особенно если у вас есть переписка с кем-либо за пределами США. Надеюсь, что большинство ваших друзей и коллег используют более или менее MIME-совместимые почтовые клиенты, но я гарантирую, что вы найдете по крайней мере несколько писем с необработанным 8-битным текстом.

Теперь, если язык, который они использовали был C или C ++, они могли бы сойти с рук, потому что они технически работали бы с байтовыми массивами, но с Java и C#, 'string' - это строка в Unicode. Подскажите: как получить строку юникода из необработанного байтового массива?

Bin go. Вам нужно знать кодировку, прежде чем вы сможете преобразовать эти байты в символы Юникода.

Если честно, действительно нет хорошего способа обработки необработанного 8-битного текста в заголовках сообщений, но используя подход TextReader, вы действительно ограничивают возможности.

Далее следует подход ReadLine(). Один из 2 ранних парсеров в GMime ( pan-mime-parser. c в версии 0.7 дня) использовал подход ReadLine(), так что я понимаю причину это. И действительно, в этом подходе нет ничего плохого в том, что касается правильности, это скорее жалоба «это никогда не может быть быстрым». Из двух ранних парсеров в GMime бэкэнд пан-пантомимы. 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#. ... Раньше я использовал несколько парсеров MIM с открытым исходным кодом, но все они либо терпят неудачу при одном или другом кодировании, либо пропускают важную информацию. Вот почему я решил, наконец, иметь go в этой проблеме сам. Я разрешу вам, что его 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() подход был ошибочным, и поэтому попытался компенсировать это, снова объединив Шалтай-Болтай. Конечно, с его String.Trim () он просто не сможет собрать его снова с какой-либо степенью уверенности. Пробелы в указанных в кавычках токенах могут иметь важное значение.

Многие из C# MIME-парсеров любят использовать 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, вам действительно нужно быть ).

...