【问题标题】:Email from PHP has broken Subject header encoding来自 PHP 的电子邮件已损坏主题标头编码
【发布时间】:2011-05-22 08:09:15
【问题描述】:

我的 PHP 脚本向用户发送电子邮件,当电子邮件到达他们的邮箱时,主题行 ($subject) 在我的主题文本末尾添加了诸如 a^£ 之类的字符。这显然是编码问题。电子邮件内容本身很好,只是主题行坏了。

我已经搜索了所有内容,但找不到如何正确编码我的主题

这是我的标题。请注意,我将Content-Typecharset=utf-8Content-Transfer-Encoding: 8bit 一起使用。

//set all necessary headers
$headers = "From: $sender_name<$from>\n";
$headers .= "Reply-To: $sender_name<$from>\n";
$headers .= "X-Sender: $sender_name<$from>\n";
$headers .= "X-Mailer: PHP4\n"; //mailer
$headers .= "X-Priority: 3\n"; //1 UrgentMessage, 3 Normal
$headers .= "MIME-Version: 1.0\n";
$headers .= "X-MSMail-Priority: High\n";
$headers .= "Importance: 3\n";
$headers .= "Date: $date\n";
$headers .= "Delivered-to: $to\n";
$headers .= "Return-Path: $sender_name<$from>\n";
$headers .= "Envelope-from: $sender_name<$from>\n";
$headers .= "Content-Transfer-Encoding: 8bit\n";
$headers .= "Content-Type: text/plain; charset=UTF-8\n";

【问题讨论】:

  • 您有没有想过使用phpmailer.worxware.com 这样可以省去很多麻烦。
  • 除了提供的答案,请注意,根据the docs,您应该使用 CRLF (\r\n) 分隔多个标头,而不仅仅是 LF (\n)。

标签: php encoding mime email-headers


【解决方案1】:

更新   要获得更实用和最新的答案,请查看Palec’s answer


Content-Type中指定的字符编码只描述了消息体的字符编码,不描述消息头。您需要将encoded-word syntaxquoted-printable encodingBase64 encoding 一起使用:

encoded-word = "=?" charset "?" encoding "?" encoded-text "?="

您可以将imap_8bit 用于quoted-printable 编码,将base64_encode 用于Base64 编码:

"Subject: =?UTF-8?B?".base64_encode($subject)."?="
"Subject: =?UTF-8?Q?".imap_8bit($subject)."?="

【讨论】:

  • gumbo,我不明白 base64 和 imap_8bit 的区别?我什么时候应该使用一种或另一种?会是这样吗: $subject = '=?UTF-8?B?'.base64_encode($subject).'?=this is the subject';还是因为我不需要主题文本所在的“?=”?
  • @user535256:不,实际的主题需要使用其中一种编码进行编码。你选择哪一个是你的决定。 Quoted-printable 更具可读性,因为大多数可打印的 ASCII 字符都被保留了;但如果您的主题可能包含大量非 ASCII 字符,则将占用更多空间,因为每个字节将被 =xx 的一个三字节序列替换。
  • 你也可以使用quoted_printable_encode(),根据文档,类似于imap_8bit(),除了这个不需要IMAP模块工作
  • 虽然基本思想没问题,但这种方法违反了 RFC 的较长输入。规定每个编码字 (=?…?…?…?=) 的长度最多为 75 个字符,包含编码字的行的长度最多为 76 个字符(包括续行开头的空格)。有必要将文本编码为更多的单词并折叠字段以使其适合限制。
  • 请注意,由于RFC6532,您最初所做的现在应该与实现它的电子邮件客户端一起使用,但是,rfc 是最近的(2012 年),所以我猜很少有客户端实现它。跨度>
【解决方案2】:

TL;DR

$preferences = ['input-charset' => 'UTF-8', 'output-charset' => 'UTF-8'];
$encoded_subject = iconv_mime_encode('Subject', $subject, $preferences);
$encoded_subject = substr($encoded_subject, strlen('Subject: '));
mail($to, $encoded_subject, $message, $headers);

mb_internal_encoding('UTF-8');
$encoded_subject = mb_encode_mimeheader($subject, 'UTF-8', 'B', "\r\n", strlen('Subject: '));
mail($to, $encoded_subject, $message, $headers);

问题及解决办法

Content-TypeContent-Transfer-Encoding 标头仅适用于邮件正文。对于标头,有一种机制可以在RFC 2047 中指定它们的编码。

您应该通过iconv_mime_encode() 对您的Subject 进行编码,它自 PHP 5 起就存在:

$preferences = ["input-charset" => "UTF-8", "output-charset" => "UTF-8"];
$encoded_subject = iconv_mime_encode("Subject", $subject, $preferences);

更改input-charset 以匹配您的字符串$subject 的编码。您应该将output-charset 保留为UTF-8。在 PHP 5.4 之前,使用array() 而不是[]

现在$encoded_subject 是(没有尾随换行符)

Subject: =?UTF-8?B?VmVyeSBsb25nIHRleHQgY29udGFpbmluZyBzcGVjaWFsIGM=?=
 =?UTF-8?B?aGFyYWN0ZXJzIGxpa2UgxJvFocSNxZnFvsO9w6HDrcOpPD4/PSsqIHA=?=
 =?UTF-8?B?cm9kdWNlcyBzZXZlcmFsIGVuY29kZWQtd29yZHMsIHNwYW5uaW5nIG0=?=
 =?UTF-8?B?dWx0aXBsZSBsaW5lcw==?=

对于$subject 包含:

Very long text containing special characters like ěščřžýáíé<>?=+* produces several encoded-words, spanning multiple lines

它是如何工作的?

iconv_mime_encode() 函数拆分文本,将每个部分分别编码为 &lt;encoded-word&gt; 标记和 folds 它们之间的空格。编码字为=?&lt;charset&gt;?&lt;encoding&gt;?&lt;encoded-text&gt;?= 其中:

您可以通过iconv("CP1250", "UTF-8", base64_decode("QWhvaiwgc3bsdGU=")) 或直接通过iconv_mime_decode("=?CP1250?B?QWhvaiwgc3bsdGU=?=", 0, "UTF-8")=?CP1250?B?QWhvaiwgc3bsdGU=?= 解码为UTF-8 字符串Ahoj, světe(捷克语为Hello, world)。

编码成编码字更复杂,因为规范要求每个编码字标记最长为 75 个字节,包含任何编码字标记的每一行最长不得超过 76 个字节(包括开头的空白)续行)。 不要自己实现编码。您真正需要知道的是iconv_mime_encode() 遵守规范。

有趣的相关阅读是维基百科文章Unicode and email

替代方案

一个基本的选择是只使用一组受限制的字符。 ASCII 保证可以工作。 ISO Latin 1 (ISO-8859-1),如user2250504 suggested,可能也可以工作,因为它通常在未指定编码时用作后备。但是这些字符集非常小,您可能无法编码您想要的所有字符。此外,RFC 没有说明拉丁语 1 是否应该工作。

mb_encode_mimeheader()也可以用Paul Norman answered,但是很容易用错。

  1. 您必须使用mb_internal_encoding() 来设置mbstring 函数内部使用的编码。 mb_* 函数期望输入字符串采用这种编码。注意:mb_encode_mimeheader() 的第二个参数与输入字符串无关(尽管手册上说了)。它对应于编码词中的&lt;charset&gt;(参见上面的它是如何工作的?)。输入字符串在被传递到 B 或 Q 编码之前从内部编码重新编码为这个。

    自 PHP 5.6 起可能不需要设置内部编码,因为底层 mbstring.internal_encoding 配置选项已被弃用,取而代之的是 default_charset 选项,该选项已默认设置为 UTF-8。请注意,这只是一个默认值,在您的代码中依赖默认值可能是不合适的。

  2. 您必须在输入字符串中包含标题名称和冒号。 RFC 对行长施加了严格的限制,而且它也必须适用于第一行!另一种方法是使用第五个参数($indent;截至 2015 年 9 月的最后一个参数),但这更不方便。

  3. 实现可能有错误。即使正确使用,您也可能会得到损坏的输出。至少手册页上的许多 cmets 都是这么说的。我没有找到任何问题,但我知道编码单词的实现很棘手。 如果您在 mb_encode_mimeheader()iconv_mime_encode() 中发现潜在或实际的错误,请在 cmets 中告诉我。

使用mb_encode_mimeheader() 至少还有一个好处:它并不总是对所有标题内容进行编码,这样可以节省空间并使文本易于阅读。只有非 ASCII 部分才需要编码。类似于上面iconv_mime_encode() 示例的输出是:

Subject: Very long text containing special characters like
 =?UTF-8?B?xJvFocSNxZnFvsO9w6HDrcOpPD4/PSsqIHByb2R1Y2VzIHNldmVyYWwgZW5j?=
 =?UTF-8?B?b2RlZC13b3Jkcywgc3Bhbm5pbmcgbXVsdGlwbGUgbGluZXM=?=

mb_encode_mimeheader()的使用示例:

mb_internal_encoding('UTF-8');
$encoded_subject = mb_encode_mimeheader("Subject: $subject", 'UTF-8');
$encoded_subject = substr($encoded_subject, strlen('Subject: '));
mail($to, $encoded_subject, $message, $headers);

这是 TL;DR 在这篇文章顶部的 sn-p 的替代方法。它不只是为Subject: 保留空间,而是将其放在那里,然后将其删除,以便能够与mail() 的愚蠢界面一起使用。

如果你比 iconv 更喜欢 mbstring 函数,你可能想要使用mb_send_mail()。它在内部使用mail(),但会自动对消息的主题和正文进行编码。再次,use with care

主题以外的标题需要不同的处理

请注意,您不能假设对可能包含非 ASCII 字符的所有标头都进行编码是可以的。例如。 From、To、Cc、Bcc 和 Reply-To 可以包含它们所包含地址的名称,但只有名称可以被编码,而不是地址。原因是&lt;encoded-word&gt; 令牌可能只替换&lt;text&gt;&lt;ctext&gt;&lt;word&gt; 令牌,并且仅在某些情况下(参见§5 of RFC 2047)。

在其他标题中编码非 ASCII 文本是一个相关但不同的问题。 如果您想了解有关此主题的更多信息,请搜索。如果您没有找到答案,请再问一个问题并在 cmets 中向我指出。

【讨论】:

  • 这条线救了我:iconv_mime_decode("=?CP1250?B?QWhvaiwgc3bsdGU=?=", 0, "UTF-8"), +1 只为那条线。
【解决方案3】:

mb_encode_mimeheader() 用于 UTF-8 字符串在这里可能很有用,例如

$subject = mb_encode_mimeheader($subjectText,"UTF-8");

【讨论】:

  • 我在使用 mb-encode-mimeheader 时遇到了奇怪的效果:=?UTF-8?B? 前缀没有添加到我的主题字符串的开头,而是在中间的某个位置。所以我恢复到手动构建编码单词的语法,就像 Gumbo 展示的那样。
  • @Jpsy 没关系。只需使用非 ASCII 字符或仅对这些字符进行编码就足够了。但您必须注意intermediate spaces are getting collapsed 可能会导致意外结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-18
  • 2011-09-22
  • 1970-01-01
  • 2011-07-23
  • 2011-11-20
  • 2013-04-18
相关资源
最近更新 更多