Изменить кодировку StreamReader при чтении из NetworkStream

Я пытаюсь прочитать электронное письмо с POP3 и перейти на правильную кодировку, когда нахожу кодировку в заголовках.

Я использую TCP-клиент для подключения к серверу POP3.

Ниже мой код:

    public string ReadToEnd(POP3Client pop3client, out System.Text.Encoding messageEncoding)
    {
        messageEncoding = TCPStream.CurrentEncoding;
        if (EOF)
            return ("");

        System.Text.StringBuilder sb = new System.Text.StringBuilder(m_bytetotal * 2);
        string st = "";
        string tmp;

        do
        {
            tmp = TCPStream.ReadLine();
            if (tmp == ".")
                EOF = true;
            else
                sb.Append(tmp + "\r\n");

            //st += tmp + "\r\n";

            m_byteread += tmp.Length + 2; // CRLF discarded by read

            FireReceived();

            if (tmp.ToLower().Contains("content-type:") && tmp.ToLower().Contains("charset="))
            {
                try
                {
                    string charSetFound = tmp.Substring(tmp.IndexOf("charset=") + "charset=".Length).Replace("\"", "").Replace(";", "");
                    var realEnc = System.Text.Encoding.GetEncoding(charSetFound);

                    if (realEnc != TCPStream.CurrentEncoding)
                    {
                        TCPStream = new StreamReader(pop3client.m_tcpClient.GetStream(), realEnc);
                    }
                }
                catch { }
            }                
        } while (!EOF);

        messageEncoding = TCPStream.CurrentEncoding;

        return (sb.ToString());
    }

Если я удалю эту строку:

TCPStream = new StreamReader(pop3client.m_tcpClient.GetStream(), realEnc);

Все работает нормально, за исключением того, что когда электронное письмо содержит разные символы кодировки, я получаю вопросительные знаки, поскольку исходная кодировка - ASCII.

Любые предложения о том, как изменить кодировку при чтении данных из сетевого потока?


person net_L    schedule 20.03.2014    source источник
comment
почему бы вам не попробовать декодировать все как utf8? TCPStream = новый StreamReader(pop3client.m_tcpClient.GetStream(), System.Text.Encoding.UTF8);   -  person pedrommuller    schedule 20.03.2014
comment
Согласно RFC 2045 (раздел 5.2) 5.2. Значения Content-Type по умолчанию Сообщения RFC 822 по умолчанию без заголовка MIME Content-Type воспринимаются этим протоколом как обычный текст в наборе символов US-ASCII, который может быть явно указан как: Content-type: text/plain; charset=us-ascii ietf.org/rfc/rfc2045.txt   -  person net_L    schedule 20.03.2014
comment
Кстати, когда я пытался читать все как UTF-8, я использовал некоторые символы вместо символов, когда кодировка была charset=iso-8859-7   -  person net_L    schedule 20.03.2014


Ответы (2)


Есть несколько способов определить кодировку, взглянув на метку порядка байтов, которая представляет собой первые несколько байтов потока. Они скажут вам кодировку. Однако у потока может не быть спецификации, и в этих случаях это может быть ASCII, UTF без спецификации или другие.

Вы можете преобразовать свой поток из одной кодировки в другую с помощью класса кодировки:

Encoding textEncoding = Encoding.[your detected encoding here];
byte[] converted = Encoding.UTF8.GetBytes(textEncoding.GetString(TCPStream.GetBuffer()));

Вы можете выбрать предпочтительную кодировку при конвертации.

Надеюсь, это ответит на ваш вопрос.

изменить
Вы можете использовать этот код для чтения потока блоками.

MemoryStream st = new MemoryStream();
int numOfBytes = 1024;
int reads = 1;
while (reads > 0)
{
    byte[] bytes = new byte[numOfBytes];
    reads = yourStream.Read(bytes, 0, numOfBytes);
    if (reads > 0)
    {
        int writes = ( reads < numOfBytes ? reads : numOfBytes);
        st.Write(bytes, 0, writes);
    }
}
person Ricardo Appleton    schedule 20.03.2014
comment
TCPStream — это StreamReader, который имеет NetworkStream в качестве BaseStream через TCPClient. Только у MemoryStream есть метод GetBuffer, который я не могу использовать в моем случае, или я не знаю, как это сделать. - person net_L; 20.03.2014
comment
Вы могли бы прочитать свой поток в массив байтов, выполнив TCPStream .Read(....)? Вы бы объявили byte[] stBytes = new byte[TCPStream.length]; Затем вы можете прочитать свои байты в MemoryStream - person Ricardo Appleton; 20.03.2014
comment
Со скольки байт? Мне нужно читать NetworkStream построчно, и когда я запускаю '.' это означает, что это конец сообщения электронной почты. Я попытаюсь сделать это, и когда я получу символ перевода строки, проверю строку и сообщу. Спасибо. byte[] stBytes = new byte[TCPStream.length]; TCPStream.Length вызовет исключение при чтении из NetworkStream. ссылка - person net_L; 20.03.2014
comment
Я опубликую кусок кода, который у меня есть, который считывает поток в массив блоками по 1 КБ. Затем вы можете настроить его под свои нужды - person Ricardo Appleton; 20.03.2014
comment
Мне удалось заставить его работать, читая только 1 байт за раз, потому что мне нужно проверить новый перевод строки. Спасибо за ваше руководство, это решило мою проблему. - person net_L; 20.03.2014

Ты делаешь это неправильно (тм).

А если серьезно, то вы пытаетесь решить эту проблему совершенно неправильным способом. Не используйте для этого StreamReader. И особенно не читайте по 1 байту за раз (как вы сказали, что вам нужно было сделать в комментарии к более раннему «решению»).

Для объяснения того, почему не использовать StreamReader, помимо очевидного «потому что он не предназначен для переключения между кодировками в процессе чтения», не стесняйтесь читать другой ответ, который я дал о неэффективность использования StreamReader здесь: Чтение файла mbox в C#< /а>

Что вам нужно сделать, так это буферизовать ваши чтения (например, буфер 4k должен быть в порядке). Затем, как вам уже приходится делать в любом случае, отсканируйте байт '\n', чтобы извлечь содержимое построчно, объединяя строки заголовков, которые были свернуты.

Каждый заголовок может иметь несколько токенов закодированных слов, каждый из которых может быть в отдельной кодировке, при условии, что они правильно закодированы, в противном случае вам придется иметь дело с необъявленными 8-битными данными и попытаться каким-то образом преобразовать их в юникод (вероятно, с помощью набор резервных кодировок). Я бы порекомендовал сначала попробовать UTF-8, а затем выбрать наборы символов, предоставленные пользователем вашей библиотеки, прежде чем, наконец, попробовать iso-8859-1 (убедитесь, что не пробуете iso-8859-1, пока не попробуете все остальное, потому что любая последовательность 8-битного текста будет правильно преобразована в Unicode с использованием кодировки символов iso-8859-1).

Когда вы доберетесь до текстового содержимого сообщения, вам нужно будет проверить заголовок Content-Type на наличие параметра charset. Если параметр charset не определен, он должен быть US-ASCII, но на практике это может быть что угодно. Даже если кодировка определена, она может не совпадать с фактической кодировкой символов, используемой в тексте сообщения, поэтому вам снова может понадобиться набор запасных вариантов.

Как вы, вероятно, догадались к этому моменту, это явно нетривиальная задача, поскольку она требует, чтобы синтаксический анализатор выполнял преобразование символов «на лету» по мере его выполнения (и преобразование символов требует внутреннего состояния синтаксического анализатора о том, что такое ожидаемый набор символов). в любое время).

Поскольку я уже проделал эту работу, вам действительно следует подумать об использовании MimeKit, который будет анализировать электронную почту и правильно выполнить преобразование кодировки в заголовках и контенте, используя соответствующую кодировку кодировки.

Я также написал класс Pop3Client, который включен в мою библиотеку MailKit.

Если ваша цель — изучить и написать собственную библиотеку, я все же настоятельно рекомендую прочитать мой код, потому что он очень эффективен и работает правильно.

person jstedfast    schedule 31.03.2014
comment
Самореклама — это хорошо, но вам действительно нужно предоставить некоторые подробности о том, как это сделать правильно здесь, на этом сайте. Если бы вы кратко рассказали, как это сделать правильно, а затем сказали, что я уже сделал всю эту работу для вас в моей библиотеке..., это было бы прекрасно. Но просто сказать Вы делаете это неправильно, просто используйте мою библиотеку — это пограничный ответ (и в зависимости от того, кого вы спросите, это может быть погранично приемлемым или погранично неприемлемым) - person Scott Chamberlain; 01.04.2014
comment
Я подумал, что было очевидно, почему использование StreamReader для текстового потока, который может менять кодировку несколько раз, не было идеальным решением (я имею в виду, чтобы сделать это таким образом, он должен читать 1 байт за раз, что крайне неэффективно) . - person jstedfast; 01.04.2014
comment
Новая версия намного лучше. - person Scott Chamberlain; 01.04.2014