Подтвердить что ты не робот

Электронная почта с PHP нарушила кодировку заголовка субъекта

Мой PHP script отправляет электронную почту пользователям, и когда письмо прибывает в их почтовые ящики, строка темы ($subject) имеет символы, такие как a^£, добавленные в конец моего текста темы. Это явно и проблема кодирования. Сам контент сообщения электронной почты в порядке, только строка темы сломана.

Я искал все, но не смог найти , как правильно закодировать мой объект.

Это мой заголовок. Обратите внимание, что Im использует 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";
4b9b3361

Ответ 1

Обновить. Для получения более практичного и актуального ответа просмотрите ответ Palecs.


Указанная кодировка символов в Content-Type описывает только кодировку символов тела сообщения, но не заголовок. Вы должны использовать синтаксис кодированного слова с помощью quoted- кодировка для печати или кодировка Base64:

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

Вы можете использовать imap_8bit для кодируемой кавычки и base64_encode для кодировки Base64:

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

Ответ 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>?= где:

  • <encoding> является либо B (для Base 64 - см. base64_encode()) или Q (для Quoted-printable - см. quoted_printable_encode()),
  • <encoded-text> - строка, закодированная с помощью <encoding>, которая имеет кодировку <charset> после декодирования.

Вы можете декодировать =?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, в других заголовках - это связанный, но другой вопрос. Если вы хотите узнать больше об этой теме, выполните поиск. Если вы не найдете ответа, задайте другой вопрос и укажите мне его в комментариях.

Ответ 3

mb_encode_mimeheader() для строк UTF-8 могут быть полезны здесь, например.

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

Ответ 4

Сохраните файл php с соответствующей кодировкой.

В моем случае в Sublime Text я использовал следующую опцию:

Файл > Сохранить с кодировкой > Western (ISO-8859-1) [для бразильского португальца]

Выполняя это, вам не нужно использовать какую-либо команду.