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有时,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字符串