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

我的PHP脚本发送电子邮件给用户,当电子邮件到达他们的邮箱时,主题行( $subject )具有添加到我的主题文本末尾的字符。 这显然是编码问题。 电子邮件内容本身很好,只是主题行被打破。

我已经搜遍了,但无法find如何正确编码我的主题

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

更新为了更实用和最新的答案,看看Palec的答案 。


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

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

您可以将imap_8bit用于引用可打印的编码,Base64编码用于Base64编码:

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

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); 

问题和解决scheme

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以匹配string$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==?= 

for $subject包含:

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

它是如何工作的?

iconv_mime_encode()函数分割文本,分别将每个片段编码成一个<encoded-word>标记并折叠它们之间的空白。 编码字是=?<charset>?<encoding>?<encoded-text>?=其中:

  • <encoding>B (对于Base 64 – 参见base64_encode() )或Q (对于Quoted-printable – 参见quoted_printable_encode() ),
  • <encoded-text>是使用<encoding>string编码的,解码后的charset是<charset>

您可以通过iconv("CP1250", "UTF-8", base64_decode("QWhvaiwgc3bsdGU="))解码=?CP1250?B?QWhvaiwgc3bsdGU=?=转换为UTF-8stringAhoj, světeHello, world捷克语Hello, world iconv("CP1250", "UTF-8", base64_decode("QWhvaiwgc3bsdGU="))直接通过iconv_mime_decode("=?CP1250?B?QWhvaiwgc3bsdGU=?=", 0, "UTF-8")

对编码字进行编码更为复杂,因为规范要求每个编码字标记长度最多为75个字节,而每行包含任何编码字标记的长度最多不得超过76个字节(包括连续行起始处的空白)。 不要自己实现编码。 所有你真正需要知道的是iconv_mime_encode()尊重规范。

有趣的相关阅读是维基百科文章Unicode和电子邮件 。

备择scheme

一个基本的select是只使用一组受限制的字符。 ASCII保证工作。 如用户2250504所build议的 ,ISO Latin 1(ISO-8859-1)可能也会起作用,因为当没有指定编码时,经常用作后备。 但是这些字符集非常小,你可能无法编码你想要的所有字符。 此外,RFC不说拉丁文1是否应该工作。

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

  1. 您必须使用mb_internal_encoding()来设置mbstring函数的内部使用的编码。 mb_*函数希望inputstring在这个编码中。 注意: mb_encode_mimeheader()的第二个参数与inputstring无关(尽pipe手册中有说明)。 它对应于编码字中的<charset> (请参阅上面的工作原理? )。 在传递给B或Q编码之前,inputstring从内部编码被重新编码为这个编码。

    设置内部编码可能不需要,因为PHP 5.6,因为底层mbstring.internal_encodingconfiguration选项已被弃用,默认情况下default_charset选项已被设置为UTF-8。 请注意,这只是一个默认设置,可能不适合在代码中使用默认值。

  2. 您必须在inputstring中包含标题名称和冒号。 RFC对线路长度施加了很大的限制,它也必须适用于第一线! 另一种方法是摆弄第五个参数( $indent ;截至2015年9月的最后一个参数),但这更不方便。

  3. 实施可能有错误。 即使正确使用,您可能会损坏输出。 至less这是手册页上的许多评论。 我还没有设法find任何问题,但我知道编码词的实现是棘手的。 如果您在mb_encode_mimeheader()iconv_mime_encode()发现潜在或实际的错误,请在评论中告知我。

使用mb_encode_mimeheader()还有至less一个好处:它不总是对所有的头部内容进行编码,这节省了空间,并使文本变得可读。 编码仅适用于非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中的代码片段,在这篇文章的顶部。 为了能够将它与mail()的愚蠢的接口一起使用,它实际上将它放在那里,然后将其删除。

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

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

请注意,对于可能包含非ASCII字符的所有标题,您不得假定对标题的全部内容进行编码是可以的。 例如,来自,收件人,抄送,密送和回复可能包含他们包含的地址的名称,但只有名称可能被编码,而不是地址。 原因是<encoded-word>标记可能只取代<text><ctext><word>标记,并且只能在某些情况下(参见RFC 2047的§5 )。

对其他头文件中的非ASCII文本进行编码是一个相关但不同的问题。 如果你想知道更多关于这个话题,search。 如果您找不到答案,请提出另一个问题,并在评论中指向我。

对于UTF-8string, mb_encode_mimeheader()可以在这里很有用,例如

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

保存与适当的字符集的PHP文件。

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

文件>保存与编码>西方(ISO-8859-1)[巴西葡萄牙语]

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

在我的情况下,这是诀窍:

$ subject ='=?windows-1251?B?'base64_encode($ subject)。'?=';

只要更换

窗户-1251

与其他编码(utf-8或其他)

Interesting Posts