处理自动换行的电子邮件(Content-Type: text/plain)

6
我正在尝试将电子邮件处理到我的应用程序中,一切都很顺利,直到我收到一个用户的电子邮件,其邮件服务器强制执行邮件文本的自动换行。我知道自动换行是RFC规范的一部分,所以我只是在寻找处理它的最佳方式,以获得漂亮的显示消息。
原始电子邮件:
这里是我的主要问题。当我发送一条消息时,文本被奇怪地分开了。它几乎看起来像消息本身已经破裂了。但我不确定为什么会这样,因为我的原始电子邮件看起来根本不像那样。
接收到的电子邮件如下(使用CRLF来标记邮件服务器插入它们的位置):
这里是我的主要问题。当我发送一条消息时,文本被奇怪地分开了。它几乎看起来像消息本身已经破裂了。但我不确定为什么会这样,因为我的原始电子邮件看起来根本不像那样。
我的处理代码运行以下步骤,然后将结果插入数据库。
$dirty_string = nl2br($dirty_string);
$config = HTMLPurifier_Config::createDefault();
$config->set('AutoFormat.RemoveEmpty', 'true');
$config->set('AutoFormat.RemoveEmpty.RemoveNbsp', 'true');
$config->set('HTML.Allowed', 'a[href],br,p');
$purifier = new HTMLPurifier($config);
$clean_string = $purifier->purify($dirty_string);

以下是显示的结果。如果我的页面上的div不够宽,浏览器会自动换行,但是nl2br()中的换行符会导致下一行很短。
以下是我的主要问题。当我发送电子邮件时,文本会被奇怪地分割。它几乎看起来像消息本身已经损坏了。我不确定为什么会这样,因为我的原始电子邮件与此完全不同。
我想也许我可以将双CRLF更改为新段落,并删除所有单个CRLF以将行连接到一个单一的行中,这样word-wrap就可以正确显示。但是,如果有人在电子邮件中发布以下项目列表,那么该列表将会中断。
这是我的列表 - 项目1 - 项目2 等等...
非常感谢您的帮助。

你提到的RFC规范是否也给出了每行的宽度? - ErJab
是的,http://www.ietf.org/rfc/rfc2822.txt。该规范指出,行的长度不应超过78个字符。但实际上并不那么容易,因为您还需考虑邮件服务器不会在单词中间截断一行的情况。 - Matt D.
5个回答

1

邮件解析可能是一个看似简单,但实际上充满了奇怪边缘情况的问题的典型例子。然而,这并不是一个新问题,因此有很多现有的解决方案可以很好地工作。一些选项:

也许你已经编写了一个很棒的解析器,只需要这一个小改变就能完美解决问题,但更有可能的是,使用已经存在的工具来完成工作会为你节省大量时间和心力。


你个人使用过这些中的任何一个吗?或者你会推荐其中一个吗?它们中没有一个看起来过于复杂。 - Matt D.
@MatthewDevine 我对这些中没有特别的推荐,我的电子邮件往来大多不是用 PHP。 - blahdiblah
1
我使用了MailParse并取得了很好的结果。强烈推荐它。 - Christian Riesen
为了回答这个问题,请跳过Plancake:它可以很好地解析电子邮件正文,但问题行的换行符仍然完全保留。 - John Larson
1
哎呀——PHP Mime Mail Parser 在解析纯文本时也无法删除有问题的换行符!而且由于它包装了 MailParse,我认为这对手头的问题也没有帮助。也许一些启发式的技巧是解决问题的方法?我现在考虑解析 HTML 正文并在 <br> 上分割以获取真正的行,PHP Mime Mail Parser 和 Plancake 都似乎做得很好。 - John Larson

0
这样怎么样:对于任何一行,如果其下一行包含单词且不以空格字符开头(例如列表中的缩进),则检查该行的长度是否在65到80个字符之间。如果是,则删除尾随的CR(如果行尾不包含空格或标点符号,则添加一个空格)。这将解决大多数换行问题,并保留大多数列表的格式。

如果您有更好的想法,我很乐意听取! - Aerik
这种解决方案更像是最后的努力。这样的解决方案会迫使不断进行微调。我知道你永远无法达到100%,但仍然如此。 - Matt D.
我认为没有人真正解决了这个问题,所以我坚持我的原始答案。我相信没有真正干净的解决方案。 - Aerik

0
你可以尝试使用TinyMCE编辑器来查看电子邮件消息。它会正确地格式化它。我曾经使用TinyMCE几次输入数据并将其保存到数据库中,每次在检索数据后都能正确地显示它,无论格式有多奇怪。

TinyMCE 需要用户交互吗?这个处理过程将全部自动化。 - Matt D.
我介绍TinyMCE的原因是它可以为您完成所有格式设置,而无需您处理任何内容(除了为基本XSS保护转义HTML之外)。我不理解您的评论,因为您说有人会“查看”电子邮件。那不是用户交互吗? - stan
该消息也会通过电子邮件发送给用户。 - Matt D.

0

这样的黑客攻击怎么样:删除任何78的倍数位置上的CLRF字符,(+再加5个字符来解决这个问题:邮件服务器不会在单词中间截断一行)。

因此,您应该查找这些位置的CLRF字符:

  • 78 79 80 81 82 83 并且
  • 156 157 158 159 160 161 并且
  • 以此类推。

当然,这是基于最长的单词长度为5个字符。您应该根据需要解析的电子邮件进行调整。


0
这是一个非常好的完成任务的函数:
function PlaintextEmailBrokenLineCombine($lineSet, $startIndex = 0) {
    $result = '';
    $lineCount = count($lineSet);
    for($i=$startIndex; $i < $lineCount; $i++) {
        $thisLine = $lineSet[$i];
        $nextLine = ($i < $lineCount-1 ? $lineSet[$i+1] : '');
        $nextLineFirstWord = substr($nextLine, 0, strpos($nextLine, ' '));

        $lineSeparator = "\n"; // we assume until we detect invocation of the 78char rule
        if(strlen($thisLine) + strlen($nextLineFirstWord) + 1 > 75) {
            // A line break was PROBABLY put in here where a space once was, so switch back:
            $lineSeparator = ' ';
        }
        $result .= $thisLine . ($i == $lineCount-1 ? '' : $lineSeparator); // no separator for the last line
    }
    return $result;
}

这有点玄学,因为它期望从纯文本电子邮件中返回一系列行。以下是用法:

$Parser = new MimeMailParser();
$Parser->setText($rawEmailText); 
$plaintext = $Parser->getMessageBody('text'); // or however you get it, many ways
$lineSet = explode("\n", $plaintext);
$niceText = PlaintextEmailBrokenLineCombine($lineSet);

$niceText是你想要的:它是一种相当准确的方法,可以获取你想要的文本,并去除那些讨厌的服务器添加的换行符,并用原始空格替换。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接