使用 7BIT 内容传输编码解析电子邮件正文 - PHP
Posted
技术标签:
【中文标题】使用 7BIT 内容传输编码解析电子邮件正文 - PHP【英文标题】:Parsing Email Body with 7BIT Content-Transfer-Encoding - PHP 【发布时间】:2012-09-22 19:54:29 【问题描述】:我最近一直在实现一些基于 php/IMAP 的电子邮件处理功能,并且除了消息正文解码(在某些情况下)之外,大多数功能都运行良好。
我认为,到目前为止,我已经记住了一半的 RFC 2822(“Internet 消息格式”文档指南),阅读了六个开源 CMS 的电子邮件处理代码,并阅读了大量论坛帖子、博客文章等处理用 PHP 处理电子邮件。
我还分叉并完全重写了一个用于 PHP 的类 Imap,并且该类可以很好地处理电子邮件——我有一些有用的方法来检测自动回复(用于不在办公室、旧地址等) ,解码base64和8bit消息等
但是,我根本无法可靠地工作的一件事(或者,有时,根本无法)是当带有 Content-Transfer-Encoding: 7bit
的消息进入时。
似乎不同的电子邮件客户端/服务将7BIT
解释为不同的东西。我收到了一些据称是7BIT
的电子邮件,实际上 Base64 编码。我得到了一些实际上引用-可打印编码。还有一些没有以任何方式编码。还有一些是 html,但未指示为 HTML,它们也被列为 7BIT
...
以下是使用 7 位编码接收的消息正文的一些示例(片段):
1:
A random message=20
Sent from my iPhone
2:
PGh0bWwgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwi
IHhtbG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9m
3:
tangerine apricot pepper.=0A=C2=A0=0ALet me know if you have any availabili=
ty over the next month or so. =0A=C2=A0=0AThank you,=0ANames Withheld=0A908=
-319-5916=0A=C2=A0=0A=C2=A0=0A=C2=A0=0A=0A=0A______________________________=
__=0AFrom: Names Witheld =0ATo: Names Withheld=
这些都是全部以“7Bit”编码发送的(嗯,至少根据 PHP/imap_*
),但它们显然需要更多解码才能将它们传递为纯文本。有什么方法可以可靠地将所有采用 7Bit 编码的消息转换为纯文本?
【问题讨论】:
如果每个人都只是发送纯文本电子邮件,并使用一个漂亮、简单的客户端,如 ios 版 Mail 或命令行上的mail
,世界会变得更美好:)
这些是损坏的消息。 7 位表示纯 ascii:消息中的所有字符都应该是纯 US-ASCII,没有额外的编码。有些东西在骗你。您当然可以尝试进行启发式检测。
另外,您应该使用 Thunderbird 之类的客户端下载原始 MIME 消息并查看它,以确保 PHP 中的某些内容不会欺骗您。
【参考方案1】:
$structure = imap_fetchstructure;
不是$encoding = $structure->encoding
但是$encoding = $structure->parts[ $p ]->encoding
我想我遇到了同样的问题,现在已经解决了。 (7bit 没有转换为 UTF-8,一直得到 ASCII)我以为我有 7bit,但是将代码更改为“BUT”我得到了$encoding=4
,而不是$encoding=0
,这意味着我必须imap_qprint($body)
和@ 987654327@得到我想要的。
无论如何检查编码号! (应该是 4 而不是零)
【讨论】:
这显然是不完整的代码...... $p 应该代表什么?$encoding = $structure->parts[ $p ]->encoding
将返回未定义的 $p。【参考方案2】:
花了更多时间后,我决定写一些启发式检测,正如 Max 在我最初问题的 cmets 中建议的那样。
我在Imap.php 中构建了一个更强大的decode7Bit()
方法,它会遍历一堆常见的编码字符(如=A0
)并将它们替换为它们的UTF-8 等价物,然后还会解码消息,如果它们看起来像是 base64 编码的:
/**
* Decodes 7-Bit text.
*
* PHP seems to think that most emails are 7BIT-encoded, therefore this
* decoding method assumes that text passed through may actually be base64-
* encoded, quoted-printable encoded, or just plain text. Instead of passing
* the email directly through a particular decoding function, this method
* runs through a bunch of common encoding schemes to try to decode everything
* and simply end up with something *resembling* plain text.
*
* Results are not guaranteed, but it's pretty good at what it does.
*
* @param $text (string)
* 7-Bit text to convert.
*
* @return (string)
* Decoded text.
*/
public function decode7Bit($text)
// If there are no spaces on the first line, assume that the body is
// actually base64-encoded, and decode it.
$lines = explode("\r\n", $text);
$first_line_words = explode(' ', $lines[0]);
if ($first_line_words[0] == $lines[0])
$text = base64_decode($text);
// Manually convert common encoded characters into their UTF-8 equivalents.
$characters = array(
'=20' => ' ', // space.
'=E2=80=99' => "'", // single quote.
'=0A' => "\r\n", // line break.
'=A0' => ' ', // non-breaking space.
'=C2=A0' => ' ', // non-breaking space.
"=\r\n" => '', // joined line.
'=E2=80=A6' => '…', // ellipsis.
'=E2=80=A2' => '•', // bullet.
);
// Loop through the encoded characters and replace any that are found.
foreach ($characters as $key => $value)
$text = str_replace($key, $value, $text);
return $text;
这取自我在 GitHub 上的 Imap class for PHP 的 1.0-beta2 版本。
如果您对提高效率有任何想法,请告诉我。我最初尝试通过quoted_printable_decode()
运行所有内容,但有时 PHP 会抛出模糊且无用的异常,因此我放弃了这种方法。
【讨论】:
非常感谢您发布此消息。很好的解释和很好的评论。我很感激。 我喜欢它——这是 PHP 中这个问题的一个非常简单的 str_replace() 解决方案,谢谢。【参考方案3】:我知道这是一个老问题....但是我现在遇到了这个问题,似乎 PHP 现在有了解决方案。
这个函数imap_fetchstructure()会给你编码的类型。
0 7BIT
1 8BIT
2 BINARY
3 BASE64
4 QUOTED-PRINTABLE
5 OTHER
从那里你应该能够创建一个这样的函数来解码消息
function _encodeMessage($msg, $type)
if($type == 0)
return mb_convert_encoding($msg, "UTF-8", "auto");
elseif($type == 1)
return imap_8bit($msg); //imap_utf8
elseif($type == 2)
return imap_base64(imap_binary($msg));
elseif($type == 3)
return imap_base64($msg);
elseif($type == 4)
return imap_qprint($msg);
//return quoted_printable_decode($msg);
else
return $msg;
你可以这样调用这个函数
$struct = imap_fetchstructure($conn, $messageNumber, 0);
$message = imap_fetchbody($conn, $messageNumber, 1);
$message = _encodeMessage($message, $struct->encoding);
echo $message;
我希望这对某人有帮助:)
【讨论】:
请注意,这是我在回答中提到的Imap 库中使用的技术;但是,PHP 几乎总是说消息是 7BIT 编码的,即使不是,所以通常需要进行我的回答中提到的手动解码:(以上是关于使用 7BIT 内容传输编码解析电子邮件正文 - PHP的主要内容,如果未能解决你的问题,请参考以下文章