来自PHP的电子邮件已破坏主题标题编码

时间:2010-12-08 16:17:05

标签: php encoding mime email-headers

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

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

这是我的标题。请注意,我正在Content-Type使用charset=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 个答案:

答案 0 :(得分:76)

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


Content-Type 中指定的字符编码仅描述邮件正文的字符编码,但不描述标题。您需要将encoded-word syntaxquoted-printable encodingBase64 encoding一起使用:

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

您可以使用imap_8bit作为引用的可打印编码,使用base64_encode作为Base64编码:

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

答案 1 :(得分:51)

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()函数拆分文本,将每个部分分别编码为<encoded-word>标记,并folds它们之间的空格。编码的单词是=?<charset>?<encoding>?<encoded-text>?=,其中:

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

编码到编码的单词更复杂,因为规范要求每个编码字令牌长度最多为75个字节,并且每行包含任何编码字令牌的长度必须至多为76个字节(包括开头时的空白)延续线)。 不要自己实现编码。您真正需要知道的是iconv_mime_encode()尊重规范。

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

替代

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

您也可以使用mb_encode_mimeheader()作为Paul Norman answered,但很容易错误地使用它。

  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. 实施可能有错误。即使使用正确,您也可能会损坏输出。至少这是手册页上的许多评论所说的。我没有设法找到任何问题,但我知道编码的单词的实现是棘手的。 如果您在mb_encode_mimeheader()iconv_mime_encode()中发现潜在或实际的错误,请在评论中告知我们。

  4. 使用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()的愚蠢接口一起使用。

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

    除了受试者以外的标题需要不同的治疗

    请注意,对于可能包含非ASCII字符的所有标头,您不能假定对标头的整个内容进行编码是正常的。例如。 From,To,Cc,Bcc和Reply-To可能包含它们包含的地址的名称,但只能编码名称,而不能编码地址。原因是<encoded-word>令牌可以仅取代<text><ctext><word>令牌,并且仅在某些情况下(请参阅§5 of RFC 2047)。

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

答案 2 :(得分:17)

对于UTF-8字符串,

mb_encode_mimeheader()在这里很有用,例如

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

答案 3 :(得分:-2)

使用适当的字符集保存php文件。

就我而言,在Sublime Text中,我使用了以下选项:

档案&gt;使用编码保存&gt;西方(ISO-8859-1)[巴西葡萄牙语]

这样做,您不需要使用任何命令。