52

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

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

这是我的标题。请注意,我正在使用Content-Typewithcharset=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";
4

3 回答 3

83

更新   有关更实用和最新的答案,请查看Palec 的答案


Content-Type中指定的字符编码只描述了消息体的字符编码,不描述消息头的字符编码。您需要使用带引号的可打印编码Base64 编码的编码字语法

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

您可以使用imap_8bit引号的可打印编码和base64_encodeBase64 编码:

"Subject: =?UTF-8?B?".base64_encode($subject)."?="
"Subject: =?UTF-8?Q?".imap_8bit($subject)."?="
于 2010-12-08T16:24:52.167 回答
63

TL;博士

$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-Type和标Content-Transfer-Encoding头仅适用于您的邮件正文。对于标头,有一种机制可以指定RFC 2047中指定的编码。

你应该对你的Subjectvia进行编码iconv_mime_encode(),它从 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()函数拆分文本,将每个部分分别编码为一个<encoded-word>标记并折叠它们之间的空白。编码字在=?<charset>?<encoding>?<encoded-text>?=哪里:

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

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

有趣的相关阅读是 Wikipedia 文章Unicode and email

备择方案

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

您也可以使用mb_encode_mimeheader(),正如Paul Norman 回答的那样,但很容易错误地使用它。

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

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

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

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

使用 至少还有一个好处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 在这篇文章顶部的片段的替代方案。它不只是为 保留空间Subject: ,而是将其放在那里,然后将其删除,以便能够将其与mail()的愚蠢界面一起使用。

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

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

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

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

于 2014-12-25T14:52:52.413 回答
20

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

$subject = mb_encode_mimeheader($subjectText,"UTF-8");
于 2010-12-08T21:31:10.610 回答