C#: Класс для декодирования Quoted-Printable кодирования?

Существует ли в C# класс, который может преобразовать кодировку Quoted-Printable в String? Нажмите на ссылку выше, чтобы получить больше информации о кодировке.

Следующее приведено по ссылке выше для вашего удобства.

Любое 8-битное значение байта может быть закодировано с 3 символами, после "=" следуют две шестнадцатеричные цифры (0–9 или A–F), представляющие числовое значение байта. Например, символ подачи в US-ASCII (десятичное значение 12) может быть представлен как "=0C", а знак равенства US-ASCII (десятичное значение 61) представлен как "=3D". Все символы, кроме печатных символов ASCII или символов конца строки, должны быть закодированы таким образом.

Все печатные символы ASCII (десятичные значения от 33 до 126) могут быть представлены сами по себе, кроме "=" (десятичный 61).

Символы табуляции и пробела ASCII, десятичные значения 9 и 32, могут быть представлены сами по себе, за исключением случаев, когда эти символы появляются в конце строки. Если один из этих символов появляется в конце строки, он должен быть закодирован как "=09" (табуляция) или "=20" (пробел).

Если кодируемые данные содержат значимые разрывы строк, они должны быть закодированы как последовательность ASCII CR LF, а не как их исходные значения байтов. И наоборот, если значения байтов 13 и 10 имеют значения, отличные от конца строки, они должны быть закодированы как =0D и =0A.

Строки закодированных для печати кодированных данных не должны быть длиннее 76 символов. Чтобы удовлетворить это требование без изменения закодированного текста, мягкие разрывы строк могут быть добавлены по желанию. Мягкий разрыв строки состоит из "=" в конце кодированной строки и не вызывает разрыв строки в декодированном тексте.

15 ответов

Решение

Для этого в библиотеках фреймворка есть функциональные возможности, но, похоже, они не совсем понятны. Реализация во внутреннем классе System.Net.Mime.QuotedPrintableStream, Этот класс определяет метод с именем DecodeBytes который делает то, что вы хотите. Этот метод, по-видимому, используется только одним методом, который используется для декодирования заголовков MIME. Этот метод также является внутренним, но вызывается довольно напрямую в нескольких местах, например, Attachment.Name сеттер. Демонстрация:

using System;
using System.Net.Mail;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Attachment attachment = Attachment.CreateAttachmentFromString("", "=?iso-8859-1?Q?=A1Hola,_se=F1or!?=");
            Console.WriteLine(attachment.Name);
        }
    }
}

Производит вывод:

¡Hola, _señor!

Возможно, вам придется провести некоторое тестирование, чтобы убедиться, что возврат каретки и т. Д. Обрабатываются правильно, хотя в быстром тесте, который я сделал, они были. Однако, возможно, не стоит полагаться на эту функциональность, если ваш вариант использования не достаточно близок к декодированию строки заголовка MIME, и вы не думаете, что она будет нарушена при любых изменениях, внесенных в библиотеку. Возможно, вам лучше написать собственный цитируемый для печати декодер.

Я расширил решение Мартина Мерфи, и я надеюсь, что оно будет работать в каждом случае.

private static string DecodeQuotedPrintables(string input, string charSet)
{           
    if (string.IsNullOrEmpty(charSet))
    {
        var charSetOccurences = new Regex(@"=\?.*\?Q\?", RegexOptions.IgnoreCase);
        var charSetMatches = charSetOccurences.Matches(input);
        foreach (Match match in charSetMatches)
        {
            charSet = match.Groups[0].Value.Replace("=?", "").Replace("?Q?", "");
            input = input.Replace(match.Groups[0].Value, "").Replace("?=", "");
        }
    }

    Encoding enc = new ASCIIEncoding();
    if (!string.IsNullOrEmpty(charSet))
    {
        try
        {
            enc = Encoding.GetEncoding(charSet);
        }
        catch
        {
            enc = new ASCIIEncoding();
        }
    }

    //decode iso-8859-[0-9]
    var occurences = new Regex(@"=[0-9A-Z]{2}", RegexOptions.Multiline);
    var matches = occurences.Matches(input);
    foreach (Match match in matches)
    {
        try
        {
            byte[] b = new byte[] { byte.Parse(match.Groups[0].Value.Substring(1), System.Globalization.NumberStyles.AllowHexSpecifier) };
            char[] hexChar = enc.GetChars(b);
            input = input.Replace(match.Groups[0].Value, hexChar[0].ToString());
        }
        catch { }
    }

    //decode base64String (utf-8?B?)
    occurences = new Regex(@"\?utf-8\?B\?.*\?", RegexOptions.IgnoreCase);
    matches = occurences.Matches(input);
    foreach (Match match in matches)
    {
        byte[] b = Convert.FromBase64String(match.Groups[0].Value.Replace("?utf-8?B?", "").Replace("?UTF-8?B?", "").Replace("?", ""));
        string temp = Encoding.UTF8.GetString(b);
        input = input.Replace(match.Groups[0].Value, temp);
    }

    input = input.Replace("=\r\n", "");
    return input;
}

Я написал это очень быстро.

    public static string DecodeQuotedPrintables(string input)
    {
        var occurences = new Regex(@"=[0-9A-H]{2}", RegexOptions.Multiline);
        var matches = occurences.Matches(input);
        var uniqueMatches = new HashSet<string>(matches);
        foreach (string match in uniqueMatches)
        {
            char hexChar= (char) Convert.ToInt32(match.Substring(1), 16);
            input =input.Replace(match, hexChar.ToString());
        }
        return input.Replace("=\r\n", "");
    }       

Я искал динамическое решение и потратил 2 дня, пробуя разные решения. Это решение будет поддерживать японские символы и другие стандартные наборы символов

private static string Decode(string input, string bodycharset) {
        var i = 0;
        var output = new List<byte>();
        while (i < input.Length) {
            if (input[i] == '=' && input[i + 1] == '\r' && input[i + 2] == '\n') {
                //Skip
                i += 3;
            } else if (input[i] == '=') {
                string sHex = input;
                sHex = sHex.Substring(i + 1, 2);
                int hex = Convert.ToInt32(sHex, 16);
                byte b = Convert.ToByte(hex);
                output.Add(b);
                i += 3;
            } else {
                output.Add((byte)input[i]);
                i++;
            }
        }


        if (String.IsNullOrEmpty(bodycharset))
            return Encoding.UTF8.GetString(output.ToArray());
        else {
            if (String.Compare(bodycharset, "ISO-2022-JP", true) == 0)
                return Encoding.GetEncoding("Shift_JIS").GetString(output.ToArray());
            else
                return Encoding.GetEncoding(bodycharset).GetString(output.ToArray());
        }

    }

Затем вы можете вызвать функцию с

Decode("=E3=82=AB=E3=82=B9=E3", "utf-8")

Это было первоначально найдено здесь

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

Например - если у вас есть следующая последовательность =E2=80=99 и декодируете ее, используя UTF8 по одному, вы получите три "странных" символа - если вместо этого вы создадите массив из трех байтов и преобразуете три байта с помощью в кодировке UTF8 вы получаете один апостроп.

Очевидно, что если вы используете кодировку ASCII, то поочередно проблем не возникает, однако выполнение декодирования означает, что ваш код будет работать независимо от используемого кодировщика текста.

Да, и не забывайте =3D - это особый случай, который означает, что вам нужно декодировать все, что у вас есть еще раз... Это сумасшедшая ошибка!

надеюсь, это поможет

Этот цитируемый печатный декодер прекрасно работает!

public static byte[] FromHex(byte[] hexData)
    {
        if (hexData == null)
        {
            throw new ArgumentNullException("hexData");
        }

        if (hexData.Length < 2 || (hexData.Length / (double)2 != Math.Floor(hexData.Length / (double)2)))
        {
            throw new Exception("Illegal hex data, hex data must be in two bytes pairs, for example: 0F,FF,A3,... .");
        }

        MemoryStream retVal = new MemoryStream(hexData.Length / 2);
        // Loop hex value pairs
        for (int i = 0; i < hexData.Length; i += 2)
        {
            byte[] hexPairInDecimal = new byte[2];
            // We need to convert hex char to decimal number, for example F = 15
            for (int h = 0; h < 2; h++)
            {
                if (((char)hexData[i + h]) == '0')
                {
                    hexPairInDecimal[h] = 0;
                }
                else if (((char)hexData[i + h]) == '1')
                {
                    hexPairInDecimal[h] = 1;
                }
                else if (((char)hexData[i + h]) == '2')
                {
                    hexPairInDecimal[h] = 2;
                }
                else if (((char)hexData[i + h]) == '3')
                {
                    hexPairInDecimal[h] = 3;
                }
                else if (((char)hexData[i + h]) == '4')
                {
                    hexPairInDecimal[h] = 4;
                }
                else if (((char)hexData[i + h]) == '5')
                {
                    hexPairInDecimal[h] = 5;
                }
                else if (((char)hexData[i + h]) == '6')
                {
                    hexPairInDecimal[h] = 6;
                }
                else if (((char)hexData[i + h]) == '7')
                {
                    hexPairInDecimal[h] = 7;
                }
                else if (((char)hexData[i + h]) == '8')
                {
                    hexPairInDecimal[h] = 8;
                }
                else if (((char)hexData[i + h]) == '9')
                {
                    hexPairInDecimal[h] = 9;
                }
                else if (((char)hexData[i + h]) == 'A' || ((char)hexData[i + h]) == 'a')
                {
                    hexPairInDecimal[h] = 10;
                }
                else if (((char)hexData[i + h]) == 'B' || ((char)hexData[i + h]) == 'b')
                {
                    hexPairInDecimal[h] = 11;
                }
                else if (((char)hexData[i + h]) == 'C' || ((char)hexData[i + h]) == 'c')
                {
                    hexPairInDecimal[h] = 12;
                }
                else if (((char)hexData[i + h]) == 'D' || ((char)hexData[i + h]) == 'd')
                {
                    hexPairInDecimal[h] = 13;
                }
                else if (((char)hexData[i + h]) == 'E' || ((char)hexData[i + h]) == 'e')
                {
                    hexPairInDecimal[h] = 14;
                }
                else if (((char)hexData[i + h]) == 'F' || ((char)hexData[i + h]) == 'f')
                {
                    hexPairInDecimal[h] = 15;
                }
            }

            // Join hex 4 bit(left hex cahr) + 4bit(right hex char) in bytes 8 it
            retVal.WriteByte((byte)((hexPairInDecimal[0] << 4) | hexPairInDecimal[1]));
        }

        return retVal.ToArray();
    }
    public static byte[] QuotedPrintableDecode(byte[] data)
    {
        if (data == null)
        {
            throw new ArgumentNullException("data");
        }

        MemoryStream msRetVal = new MemoryStream();
        MemoryStream msSourceStream = new MemoryStream(data);

        int b = msSourceStream.ReadByte();
        while (b > -1)
        {
            // Encoded 8-bit byte(=XX) or soft line break(=CRLF)
            if (b == '=')
            {
                byte[] buffer = new byte[2];
                int nCount = msSourceStream.Read(buffer, 0, 2);
                if (nCount == 2)
                {
                    // Soft line break, line splitted, just skip CRLF
                    if (buffer[0] == '\r' && buffer[1] == '\n')
                    {
                    }
                    // This must be encoded 8-bit byte
                    else
                    {
                        try
                        {
                            msRetVal.Write(FromHex(buffer), 0, 1);
                        }
                        catch
                        {
                            // Illegal value after =, just leave it as is
                            msRetVal.WriteByte((byte)'=');
                            msRetVal.Write(buffer, 0, 2);
                        }
                    }
                }
                // Illegal =, just leave as it is
                else
                {
                    msRetVal.Write(buffer, 0, nCount);
                }
            }
            // Just write back all other bytes
            else
            {
                msRetVal.WriteByte((byte)b);
            }

            // Read next byte
            b = msSourceStream.ReadByte();
        }

        return msRetVal.ToArray();
    }
    private string quotedprintable(string data, string encoding)
    {
        data = data.Replace("=\r\n", "");
        for (int position = -1; (position = data.IndexOf("=", position + 1)) != -1;)
        {
            string leftpart = data.Substring(0, position);
            System.Collections.ArrayList hex = new System.Collections.ArrayList();
            hex.Add(data.Substring(1 + position, 2));
            while (position + 3 < data.Length && data.Substring(position + 3, 1) == "=")
            {
                position = position + 3;
                hex.Add(data.Substring(1 + position, 2));
            }
            byte[] bytes = new byte[hex.Count];
            for (int i = 0; i < hex.Count; i++)
            {
                bytes[i] = System.Convert.ToByte(new string(((string)hex[i]).ToCharArray()), 16);
            }
            string equivalent = System.Text.Encoding.GetEncoding(encoding).GetString(bytes);
            string rightpart = data.Substring(position + 3);
            data = leftpart + equivalent + rightpart;
        }
        return data;
    }

Единственный, который работал на меня.

http://sourceforge.net/apps/trac/syncmldotnet/wiki/Quoted%20Printable

Если вам просто нужно декодировать QP, извлеките из своего кода эти три функции из ссылки выше:

    HexDecoderEvaluator(Match m)
    HexDecoder(string line)
    Decode(string encodedText)

А потом просто:

var humanReadable = Decode(myQPString);

наслаждаться

Лучшее решение

    private static string DecodeQuotedPrintables(string input, string charSet)
    {
        try
        {
            enc = Encoding.GetEncoding(CharSet);
        }
        catch
        {
            enc = new UTF8Encoding();
        }

        var occurences = new Regex(@"(=[0-9A-Z]{2}){1,}", RegexOptions.Multiline);
        var matches = occurences.Matches(input);

    foreach (Match match in matches)
    {
            try
            {
                byte[] b = new byte[match.Groups[0].Value.Length / 3];
                for (int i = 0; i < match.Groups[0].Value.Length / 3; i++)
                {
                    b[i] = byte.Parse(match.Groups[0].Value.Substring(i * 3 + 1, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
                }
                char[] hexChar = enc.GetChars(b);
                input = input.Replace(match.Groups[0].Value, hexChar[0].ToString());
        }
            catch
            { ;}
        }
        input = input.Replace("=\r\n", "").Replace("=\n", "").Replace("?=", "");

        return input;
}

Начиная с решения @Dave, это декодирует печатные строки в кавычках с более чем одной кодировкой, например"=?utf-8?Q?Firststring?=\t=?utf-8?Q?_-_1.250=2C50_=E2=82=AC=5F1000=5F2646.pdf?="

      public static string DecodeQuotedPrintable(string text)
{
    Regex quotedPrintableEncodingRegex = new Regex(@"=\?((?!\?=).)*\?=", RegexOptions.IgnoreCase);

    MatchCollection quotedPrintableEncodingMatches = quotedPrintableEncodingRegex.Matches(text);

    if (quotedPrintableEncodingMatches.Count <= 0) 
        return text;

    var decodedText = "";
    foreach (Match match in quotedPrintableEncodingMatches)
    {
        Attachment decodedTextPart = Attachment.CreateAttachmentFromString("", match.Value);
        decodedText += decodedTextPart.Name;
    }
    return decodedText;
}

Я знаю его старый вопрос, но это должно помочь

    private static string GetPrintableCharacter(char character)
    {
        switch (character)
        {
            case '\a':
            {
                return "\\a";
            }
            case '\b':
            {
                return "\\b";
            }
            case '\t':
            {
                return "\\t";
            }
            case '\n':
            {
                return "\\n";
            }
            case '\v':
            {
                return "\\v";
            }
            case '\f':
            {
                return "\\f";
            }
            case '\r':
            {
                return "\\r";
            }
            default:
            {
                if (character == ' ')
                {
                    break;
                }
                else
                {
                    throw new InvalidArgumentException(Resources.NOTSUPPORTCHAR, new object[] { character });
                }
            }
        }
        return "\\x20";
    }

    public static string GetPrintableText(string text)
    {
        StringBuilder stringBuilder = new StringBuilder(1024);
        if (text == null)
        {
            return "[~NULL~]";
        }
        if (text.Length == 0)
        {
            return "[~EMPTY~]";
        }
        stringBuilder.Remove(0, stringBuilder.Length);
        int num = 0;
        for (int i = 0; i < text.Length; i++)
        {
            if (text[i] == '\a' || text[i] == '\b' || text[i] == '\f' || text[i] == '\v' || text[i] == '\t' || text[i] == '\n' || text[i] == '\r' || text[i] == ' ')
            {
                num += 3;
            }
        }
        int length = text.Length + num;
        if (stringBuilder.Capacity < length)
        {
            stringBuilder = new StringBuilder(length);
        }
        string str = text;
        for (int j = 0; j < str.Length; j++)
        {
            char chr = str[j];
            if (chr > ' ')
            {
                stringBuilder.Append(chr);
            }
            else
            {
                stringBuilder.Append(StringHelper.GetPrintableCharacter(chr));
            }
        }
        return stringBuilder.ToString();
    }

Обратите внимание: решения с "input.Replace" распространены по всему Интернету, и все же они не верны.

Посмотрите, если у вас есть ОДИН декодированный символ и затем используется "замена", ВСЕ символы во "входе" будут заменены, и тогда все последующее декодирование будет прервано.

Более правильное решение:

public static string DecodeQuotedPrintable(string input, string charSet)
    {

        Encoding enc;

        try
        {
            enc = Encoding.GetEncoding(charSet);
        }
        catch
        {
            enc = new UTF8Encoding();
        }

        input = input.Replace("=\r\n=", "=");
        input = input.Replace("=\r\n ", "\r\n ");
        input = input.Replace("= \r\n", " \r\n");
        var occurences = new Regex(@"(=[0-9A-Z]{2})", RegexOptions.Multiline); //{1,}
        var matches = occurences.Matches(input);

        foreach (Match match in matches)
        {
            try
            {
                byte[] b = new byte[match.Groups[0].Value.Length / 3];
                for (int i = 0; i < match.Groups[0].Value.Length / 3; i++)
                {
                    b[i] = byte.Parse(match.Groups[0].Value.Substring(i * 3 + 1, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
                }
                char[] hexChar = enc.GetChars(b);
                input = input.Replace(match.Groups[0].Value, new String(hexChar));

            }
            catch
            { Console.WriteLine("QP dec err"); }
        }
        input = input.Replace("?=", ""); //.Replace("\r\n", "");

        return input;
    }

Иногда строка в EML-файл состоит из нескольких закодированных частей. Это функция для использования метода Дейва в следующих случаях:

public string DecodeQP(string codedstring)
{
    Regex codified;

    codified=new Regex(@"=\?((?!\?=).)*\?=", RegexOptions.IgnoreCase);
    MatchCollection setMatches = codified.Matches(cadena);
    if(setMatches.Count > 0)
    {
        Attachment attdecode;
        codedstring= "";
        foreach (Match match in setMatches)
        {
            attdecode = Attachment.CreateAttachmentFromString("", match.Value);
            codedstring+= attdecode.Name;

        }                
    }
    return codedstring;
}

Немного улучшенная версия (неработающего) кода от Мартина Мерфи:

      static Regex reQuotHex = new Regex(@"=[0-9A-H]{2}", RegexOptions.Multiline|RegexOptions.Compiled);

public static string DecodeQuotedPrintable(string input)
{
    var dic = new Dictionary<string, string>();
    foreach (var qp in new HashSet<string>(reQuotHex.Matches(input).Cast<Match>().Select(m => m.Value)))
        dic[qp] = ((char)Convert.ToInt32(qp.Substring(1), 16)).ToString();
        
    foreach (string qp in dic.Keys) {
        input = input.Replace(qp, dic[qp]);
    }
    return input.Replace("=\r\n", "");
}
public static string DecodeQuotedPrintables(string input, Encoding encoding)
    {
        var regex = new Regex(@"\=(?<Symbol>[0-9A-Z]{2})", RegexOptions.Multiline);
        var matches = regex.Matches(input);
        var bytes = new byte[matches.Count];

        for (var i = 0; i < matches.Count; i++)
        {
            bytes[i] = Convert.ToByte(matches[i].Groups["Symbol"].Value, 16);
        }

        return encoding.GetString(bytes);
    }
Другие вопросы по тегам