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

daz*_*166 51 php encoding mime email-headers

我的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";
Run Code Online (Sandbox Code Playgroud)

Gum*_*mbo 79

更新    如需更实用和最新的答案,请查看Palec的答案.


Content-Type中指定的字符编码仅描述邮件正文的字符编码,但不描述标题.您需要将编码字语法quoted-printable编码Base64编码一起使用:

encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
Run Code Online (Sandbox Code Playgroud)

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

"Subject: =?UTF-8?B?".base64_encode($subject)."?="
"Subject: =?UTF-8?Q?".imap_8bit($subject)."?="
Run Code Online (Sandbox Code Playgroud)

  • 你也可以使用[quoted_printable_encode()](http://uk.php.net/function.quoted-printable-encode.php),根据doc,*类似于`imap_8bit()`,除了这个不要求IMAP模块工作*. (3认同)
  • @ user535256:不,实际主题需要使用其中一种编码进行编码.你选择哪一个是你的决定.*Quoted-printable*更具可读性,因为大多数可打印的ASCII字符都被保留; 但如果您的主题可能包含大量非ASCII字符,则需要更多空间,因为每个字节将被一个三字节序列"= xx"替换. (2认同)
  • 尽管基本思想尚可,但此方法违反了RFC要求较长的输入。指定每个编码的单词(`=?…???? ...?==`)的最大长度为75个字符,包含编码的单词的行的长度最大为76个字符(包括连续行开头的空格) )。有必要将文本编码为更多的单词,并折叠字段以使其适合限制。 (2认同)

Pal*_*lec 56

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);
Run Code Online (Sandbox Code Playgroud)

要么

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

问题和解决方案

Content-TypeContent-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);
Run Code Online (Sandbox Code Playgroud)

更改input-charset以匹配字符串的编码$subject.你应该离开output-charsetUTF-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==?=
Run Code Online (Sandbox Code Playgroud)

对于$subject包含:

Very long text containing special characters like ?š??žýáíé<>?=+* produces several encoded-words, spanning multiple lines
Run Code Online (Sandbox Code Playgroud)

它是如何工作的?

iconv_mime_encode()函数拆分文本,将每个部分分别编码为一个<encoded-word>令牌并折叠它们之间的空白.编码的单词是=?<charset>?<encoding>?<encoded-text>?=在哪里:

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

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

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

备择方案

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

你可以使用mb_encode_mimeheader(),正如保罗诺曼回答的那样,但很容易错误地使用它.

  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(),请让我知道在评论.

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

Subject: Very long text containing special characters like
 =?UTF-8?B?xJvFocSNxZnFvsO9w6HDrcOpPD4/PSsqIHByb2R1Y2VzIHNldmVyYWwgZW5j?=
 =?UTF-8?B?b2RlZC13b3Jkcywgc3Bhbm5pbmcgbXVsdGlwbGUgbGluZXM=?=
Run Code Online (Sandbox Code Playgroud)

用法示例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);
Run Code Online (Sandbox Code Playgroud)

这是TL中的片段的替代; DR在此帖子之上.Subject:它实际上将其放在那里然后将其删除,以便能够将其与mail()愚蠢的界面一起使用,而不仅仅是保留空间.

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

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

请注意,对于可能包含非ASCII字符的所有标头,您不能认为对标头的整个内容进行编码是正常的.例如From,To,Cc,Bcc和Reply-To可能包含它们包含的地址的名称,但只能编码名称,而不能编码地址.其原因是,<encoded-word>令牌可以只更换<text>,<ctext><word>令牌,只有在特定情况下(参见RFC 2047的§5).

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


Pau*_*man 18

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

$subject = mb_encode_mimeheader($subjectText,"UTF-8");
Run Code Online (Sandbox Code Playgroud)

  • 使用mb-encode-mimeheader时我遇到了奇怪的效果:``=?UTF-8?B?``前缀没有被添加到我的主题字符串的开头,但是在中间的某个地方.所以我恢复了Gumbo所示的手动构建编码字语法. (2认同)
  • @Jpsy很好。仅使用非ASCII字符甚至仅使用那些字符对这些单词进行编码就足够了。但您必须注意,[中间空间正在崩溃](http://stackoverflow.com/a/1294391/53114)可能会导致意外结果。 (2认同)