Мой PHP script отправляет электронную почту пользователям, и когда письмо прибывает в их почтовые ящики, строка темы ($subject
) имеет символы, такие как a^£
, добавленные в конец моего текста темы. Это явно и проблема кодирования. Сам контент сообщения электронной почты в порядке, только строка темы сломана.
Это мой заголовок. Обратите внимание, что Im использует Content-Type
с charset=utf-8
и Content-Transfer-Encoding: 8bit
.
Ответ 2
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-Type
и Content-Transfer-Encoding
применяются только к телу вашего сообщения. Для заголовков существует механизм для указания их кодировки, указанный в RFC 2047.
Вы должны закодировать свой Subject
через 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ěte
(Hello, world
на чешском языке) через iconv("CP1250", "UTF-8", base64_decode("QWhvaiwgc3bsdGU="))
или непосредственно через iconv_mime_decode("=?CP1250?B?QWhvaiwgc3bsdGU=?=", 0, "UTF-8")
.
Кодирование в кодированные слова более сложно, так как спецификация требует, чтобы каждый токен с кодированным словом составлял не более 75 байт, и каждая строка, содержащая любой токен с кодированным словом, должна иметь длину не более 76 байт (включая пробел в начале линия продолжения). Не выполняйте кодирование самостоятельно. Все, что вам действительно нужно знать, это то, что iconv_mime_encode()
соответствует спецификации.
Интересное связанное чтение - статья Википедии Юникод и электронная почта.
Альтернативы
Рудиментарный вариант - использовать только ограниченный набор символов. ASCII гарантированно работает. ISO Latin 1 (ISO-8859-1), как предлагается user2250504, вероятно, будет работать, потому что он часто используется в качестве резервной, когда не указывается кодировка. Но эти наборы символов очень малы, и вы, вероятно, не сможете кодировать все символы, которые вы хотите. Более того, RFC ничего не говорят о том, должен ли работать латинский 1 или нет.
Вы также можете использовать mb_encode_mimeheader()
, поскольку Пол Норман ответил, но его легко использовать неправильно.
-
Вы должны использовать mb_internal_encoding()
, чтобы установить внутреннюю кодировку функций mbstring. Функции mb_*
ожидают ввода строк в этой кодировке. Остерегайтесь: второй параметр mb_encode_mimeheader()
не имеет ничего общего с входной строкой (несмотря на то, что говорится в руководстве). Это соответствует <charset>
в закодированном слове (см. Раздел "Как это работает" выше). Входная строка перекодируется из внутренней кодировки в это, прежде чем будет передана в кодировку B или Q.
Настройка внутренней кодировки может не потребоваться с PHP 5.6, поскольку базовый параметр конфигурации mbstring.internal_encoding
устарел в пользу default_charset
, который по умолчанию установлен в UTF-8. Обратите внимание, что это только по умолчанию, и может быть нецелесообразно полагаться на значения по умолчанию в вашем коде.
-
Вы должны указать имя заголовка и двоеточие во входной строке. RFC накладывает сильный предел на длину строки, и он должен также удерживаться для первой строки! Альтернативой является обсуждение пятого параметра ($indent
, последний по состоянию на сентябрь 2015 года), но это еще менее удобно.
-
У реализации могут быть ошибки. Даже если они используются правильно, вы можете получить поврежденный выход. По крайней мере, это то, что многие комментарии на странице руководства говорят. Мне не удалось найти никаких проблем, но я знаю, что реализация закодированных слов сложна. Если вы найдете потенциальные или фактические ошибки в 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()
.
Если вам нравятся функции mbstring лучше, чем значки, вы можете использовать mb_send_mail()
. Он использует mail()
внутренне, но автоматически кодирует тему и тело сообщения. Опять же, используйте с осторожностью.
Заголовки, отличные от темы, требуют различного лечения
Обратите внимание, что вы не должны предполагать, что кодировка всего содержимого заголовка в порядке для всех заголовков, которые могут содержать символы, отличные от ASCII. Например. From, To, Cc, Bcc и Reply-To могут содержать имена для адресов, которые они содержат, но могут быть закодированы только имена, а не адреса. Причина в том, что токен <encoded-word>
может заменять только теги <text>
, <ctext>
и <word>
и только при определенных обстоятельствах (см. §5 RFC 2047).
Кодирование текста, отличного от ASCII, в других заголовках - это связанный, но другой вопрос. Если вы хотите узнать больше об этой теме, выполните поиск. Если вы не найдете ответа, задайте другой вопрос и укажите мне его в комментариях.