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()
вместо []
.
Now $encoded_subject
is (без завершающего перевода строки)
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()
соблюдает спецификацию.
Интересное прочтение статьи Wikipedia Юникод и электронная почта .
Альтернативы
Элементарным вариантом является использование только ограниченного набора символов.ASCII гарантированно работает.ISO Latin 1 (ISO-8859-1), как user2250504 предложил , вероятно, тоже будет работать, потому что он часто используется как запасной вариант, когда не указана кодировка.Но эти наборы символов очень малы, и вы, вероятно, не сможете закодировать все символы, которые вам нужны.Более того, в RFC ничего не говорится о том, должен ли работать Latin 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чем iconv, вы можете использовать mb_send_mail()
.Он использует mail()
для внутреннего использования, но автоматически кодирует тему и текст сообщения.Опять же, используйте с осторожностью .
Заголовки, отличные от темы, требуют другой обработки
Обратите внимание, что вы не должны предполагать, что кодирование всего содержимого заголовка в порядке для всех заголовковкоторые могут содержать не-ASCII символы.Например, From, To, Cc, Bcc и Reply-To могут содержать имена адресов, которые они содержат, но могут кодироваться только имена, а не адреса.Причина в том, что токен <encoded-word>
может заменять только токены <text>
, <ctext>
и <word>
и только при определенных обстоятельствах (см. §5 RFC 2047 ).
Кодирование текста не-ASCII в других заголовках - это связанный, но другой вопрос. Если вы хотите узнать больше об этой теме, выполните поиск.Если вы не нашли ответа, задайте другой вопрос и укажите на него в комментариях.