C#:用于解码 Quoted-Printable 编码的类?

Posted

技术标签:

【中文标题】C#:用于解码 Quoted-Printable 编码的类?【英文标题】:C#: Class for decoding Quoted-Printable encoding? 【发布时间】:2011-01-14 15:43:53 【问题描述】:

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 个字符。 为了满足这个要求而不 更改编码文本,软线 可以根据需要添加中断。一个软 换行符由一个“=”组成 编码行的结尾,并且不 导致解码中的换行符 文本。

【问题讨论】:

我刚刚在这里发布了一个简单的UTF8解码答案:***.com/questions/37540244/… 【参考方案1】:

框架库中有执行此操作的功能,但似乎没有完全公开。实现在内部类System.Net.Mime.QuotedPrintableStream 中。这个类定义了一个名为DecodeBytes 的方法,它可以满足您的需求。该方法似乎仅由一种用于解码 MIME 标头的方法使用。这个方法也是内部的,但在几个地方直接调用,例如Attachment.Name setter。一个示范:

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

产生输出:

¡你好,_señor!

您可能需要进行一些测试以确保正确处理回车等,尽管在快速测试中我确实做到了。但是,依赖此功能可能是不明智的,除非您的用例足够接近对 MIME 标头字符串的解码,您认为对库所做的任何更改都不会破坏它。您最好编写自己的引用打印解码器。

【讨论】:

这正是我想要的。谢谢戴夫! :) 这不会为我转换 =3D 等,并且 codeproject 版本在解码时失败。 framework 2有bug,无法处理编码字符串中的下划线。下划线代表旧解析的空间。在版本 4 中已修复。这种方式还有一个小问题,如果字符集信息不在字符串的开头,则无法对其进行解码。像这样的编码字符串 **ID: 12345 Arpège ** Attachment 类也可以采用这种格式进行编码。使用空字符串创建一个对象,设置Name,然后使用ToString() 获取编码名称。不幸的是,输出也有 MIME 类型和字符集,例如"text/plain; name=\"=?utf-8?B?SWN ... nUg?=\"; charset=us-ascii" 需要被剥离。此外,它遵循 RFC2074 并将输入分成几部分,使编码的单词不超过 75 个字符。【参考方案2】:

我写得很快。

    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", "");
           

【讨论】:

需要更改 uniqueMatches 行以正确处理MatchCollection,但除此之外这有效。谢谢!【参考方案3】:

我扩展了 Martin Murphy 的解决方案,我希望它适用于所有情况。

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;

【讨论】:

非常感谢...它解决了我的问题,这就是我一直在寻找的东西.. 这一行不支持=和回车"If you believe that truth=3Dbeauty, then surely =\nmathematics is the most beautiful branch of philosophy." 这适用于我有限的要求,谢谢。顺便说一句,为什么是空的 catch 块? 这个方法必须重构。我添加了工作副本,但不是最终版本。 “=[0-9A-Z]2”并非总是可以解码。例如当输入为“3+7=10”时 嘿,感谢您的代码!我现在遇到了麻烦,如果你能看一下here,我将不胜感激【参考方案4】:

如果您使用 UTF-8 编码解码quoted-printable,您将需要注意,如果有引用的可打印字符运行,您不能像其他人所显示的那样一次解码每个quoted-printable 序列在一起。

例如 - 如果您有以下序列 =E2=80=99 并使用 UTF8 一次解码,您会得到三个“奇怪”字符 - 如果您改为构建一个包含三个字节的数组并将使用 UTF8 编码的三个字节,您会得到一个单引号。

显然,如果您使用的是 ASCII 编码,那么一次一个是没有问题的,但是解码运行意味着您的代码无论使用哪种文本编码器都可以工作。

哦,别忘了 =3D 是一种特殊情况,这意味着您需要再解码任何时间...这是一个疯狂的陷阱!

希望有帮助

【讨论】:

是的,这更接近于Skeet's answer over here。我们不应该将 Quoted Printable 视为字符,而应将其视为序列化字节的一种方式。首先解码为字节,然后将该字节数组解码为您的编码字符串——System.Text.Encoding.UTF8.GetString(byteArray) 或你有什么。试图从小于 128 的字节中“挽救”字符是一个头线程错误。【参考方案5】:

更好的解决方案

    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;

【讨论】:

【参考方案6】:

这个引用的可打印解码器效果很好!

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

【讨论】:

【参考方案7】:

唯一对我有用的。

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

如果您只需要解码 QP,请从上面的链接中将这三个函数拉入您的代码中:

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

然后就是:

var humanReadable = Decode(myQPString);

享受

【讨论】:

【参考方案8】:
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);
    

【讨论】:

试着解释一下你的解决方案。你可以通过编辑你的答案来做到这一点。【参考方案9】:
    private string quotedprintable(string data, string encoding)
    
        data = data.Replace("=\r\n", "");
        for (int position = -1; (position = data.IndexOf("=")) != -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;
    

【讨论】:

this.Text =quotedprintable("=3D", "utf-8"); 这支持顺序十六进制组合为字节然后转换为编码 适用于多个连续的多字节字符。太棒了!【参考方案10】:

我一直在寻找动态解决方案,并花了 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")

这是最初发现的here

【讨论】:

喜欢这个!在 UTF-8 上正确工作,并且比对字符串的常量 Replace() 操作更有效。 Stream 可能比 List 稍微高效一些,但不那么可读,而且如果字符串不太长,也不是什么大问题。 (有兴趣的可以查看***.com/questions/4015602/…)。我现在已经使用 Unicode 翻译工作了很多,并且在解码之前我仍然认可中间字节列表/数组/流;处理 UTF-8 编码的可变字节数的任何其他方式都非常复杂,不值得。【参考方案11】:

有时,EML 文件中的字符串由几个编码部分组成。这是在这些情况下使用 Dave 方法的函数:

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;

【讨论】:

【参考方案12】:

请注意: 带有“input.Replace”的解决方案遍布互联网,但仍然不正确。

看看,如果你有一个解码符号然后使用“替换”, “输入”中的ALL符号将被替换,随后所有的解码都将被破坏。

更正确的解决方案:

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;
    

【讨论】:

还有一个有趣的事实:如果它是 unicode,它可以只是一个符号或一组十六进制匹配一个符号。例如 =E2=80=99 和 =E2 =80 =99。简而言之,这个解决方案根本不起作用。【参考方案13】:

我知道它的老问题,但这应该会有所帮助

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

【讨论】:

【参考方案14】:

Martin Murphy 的(非工作)代码的一些改进版本:

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", "");

【讨论】:

以上是关于C#:用于解码 Quoted-Printable 编码的类?的主要内容,如果未能解决你的问题,请参考以下文章

编码问题:解码Python中的Quoted-Printable字符串

Quoted-Printable编码的邮件解码,vb.net代码怎么写,谢谢

在 Java 中解码“引用可打印”字符串

在 C# 中将 Base64 字节数组解码为图像

Java:在quoted-printable中编码字符串

如何测试文本片段是不是是 Quoted-printable 编码的