来自PHP的电子邮件已破坏主题头编码
我的PHP脚本发送电子邮件给用户,当电子邮件到达他们的邮箱时,主题行( $subject
)具有添加到我的主题文本末尾的字符。 这显然是编码问题。 电子邮件内容本身很好,只是主题行被打破。
我已经搜遍了,但无法find如何正确编码我的主题 。
这是我的标题。 请注意,我正在使用Content-Type
和charset=utf-8
和Content-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-Type
和Content-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ěte
( Hello, 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所回答的 ,但是错误地使用它很容易。
-
您必须使用
mb_internal_encoding()
来设置mbstring函数的内部使用的编码。mb_*
函数希望inputstring在这个编码中。 注意:mb_encode_mimeheader()
的第二个参数与inputstring无关(尽pipe手册中有说明)。 它对应于编码字中的<charset>
(请参阅上面的工作原理? )。 在传递给B或Q编码之前,inputstring从内部编码被重新编码为这个编码。设置内部编码可能不需要,因为PHP 5.6,因为底层
mbstring.internal_encoding
configuration选项已被弃用,默认情况下default_charset
选项已被设置为UTF-8。 请注意,这只是一个默认设置,可能不适合在代码中使用默认值。 -
您必须在inputstring中包含标题名称和冒号。 RFC对线路长度施加了很大的限制,它也必须适用于第一线! 另一种方法是摆弄第五个参数(
$indent
;截至2015年9月的最后一个参数),但这更不方便。 -
实施可能有错误。 即使正确使用,您可能会损坏输出。 至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或其他)