web-dev-qa-db-fra.com

E-mail de PHP a cassé l'encodage de l'en-tête du sujet

Mon script PHP envoie un e-mail aux utilisateurs et lorsque l'e-mail arrive dans leur boîte aux lettres, la ligne d'objet ($subject) a des caractères comme a^£ ajouté à la fin de mon sujet. C'est évidemment un problème d'encodage. Le contenu du message électronique lui-même est correct, seule la ligne d'objet est rompue.

J'ai cherché partout mais je ne trouve pas comment encoder correctement mon sujet.

Ceci est mon en-tête. Notez que j'utilise Content-Type avec charset=utf-8 et 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";
51
daza166

Mise à jour Pour une réponse plus pratique et à jour, jetez un œil à réponse de Palec .


Le codage de caractères spécifié dans Content-Type ne décrit que le codage de caractères du corps du message mais pas l'en-tête. Vous devez utiliser la syntaxe encoded-Word avec soit quoted-printable encoding or the encodage Base64 :

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

Vous pouvez utiliser imap_8bit pour l'encodage quoted-printable et base64_encode pour l'encodage Base64:

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

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);

ou

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

Problème et solution

Les en-têtes Content-Type Et Content-Transfer-Encoding S'appliquent uniquement au corps de votre message. Pour les en-têtes, il existe un mécanisme pour spécifier leur codage spécifié dans RFC 2047 .

Vous devez encoder votre Subject via iconv_mime_encode() , qui existe depuis PHP 5:

$preferences = ["input-charset" => "UTF-8", "output-charset" => "UTF-8"];
$encoded_subject = iconv_mime_encode("Subject", $subject, $preferences);

Modifiez input-charset Pour faire correspondre l'encodage de votre chaîne $subject. Vous devez laisser output-charset En tant que UTF-8. Avant PHP 5.4, utilisez array() au lieu de [].

Maintenant, $encoded_subject Est (sans retour à la ligne)

Subject: =?UTF-8?B?VmVyeSBsb25nIHRleHQgY29udGFpbmluZyBzcGVjaWFsIGM=?=
 =?UTF-8?B?aGFyYWN0ZXJzIGxpa2UgxJvFocSNxZnFvsO9w6HDrcOpPD4/PSsqIHA=?=
 =?UTF-8?B?cm9kdWNlcyBzZXZlcmFsIGVuY29kZWQtd29yZHMsIHNwYW5uaW5nIG0=?=
 =?UTF-8?B?dWx0aXBsZSBsaW5lcw==?=

pour $subject contenant:

Very long text containing special characters like ěščřžýáíé<>?=+* produces several encoded-words, spanning multiple lines

Comment ça marche?

La fonction iconv_mime_encode() divise le texte, code chaque pièce séparément en un jeton <encoded-Word> et plis l'espace entre eux. Le mot encodé est =?<charset>?<encoding>?<encoded-text>?= Où:

Vous pouvez décoder =?CP1250?B?QWhvaiwgc3bsdGU=?= En chaîne UTF-8 Ahoj, světe (Hello, world En tchèque) via iconv("CP1250", "UTF-8", base64_decode("QWhvaiwgc3bsdGU=")) ou directement via iconv_mime_decode("=?CP1250?B?QWhvaiwgc3bsdGU=?=", 0, "UTF-8").

L'encodage en mots codés est plus compliqué, car la spécification requiert que chaque jeton de mot codé ait une longueur maximale de 75 octets et que chaque ligne contenant un jeton de mot codé ait une longueur maximale de 76 octets (y compris le blanc au début d'une ligne de continuation). ). N'implémentez pas l'encodage vous-même. Tout ce que vous devez vraiment savoir, c'est que iconv_mime_encode() respecte les spécifications.

L'article connexe intéressant est l'article Wikipedia nicode et email .

Alternatives

Une option rudimentaire consiste à n'utiliser qu'un ensemble restreint de caractères. ASCII est garanti de fonctionner. ISO Latin 1 (ISO-8859-1), comme suggéré par l'utilisateur2250504 , fonctionnera probablement aussi, car il est souvent utilisé comme solution de repli lorsque aucun encodage n'est spécifié. Mais ces jeux de caractères sont très petits et vous ne pourrez probablement pas encoder tous les caractères que vous voudrez. De plus, les RFC ne disent pas si Latin 1 devrait fonctionner ou non.

Vous pouvez également utiliser mb_encode_mimeheader() , comme Paul Norman a répond , mais il est facile de l'utiliser de manière incorrecte.

  1. Vous devez utiliser mb_internal_encoding() pour définir le codage utilisé en interne par les fonctions mbstring. Les fonctions mb_* S'attendent à ce que les chaînes d'entrée soient dans cet encodage. Attention: Le deuxième paramètre de mb_encode_mimeheader() n'a rien à voir avec la chaîne d'entrée (malgré ce que dit le manuel). Il correspond au <charset> Dans le mot encodé (voir Comment ça marche? ci-dessus). La chaîne d'entrée est recodée du codage interne vers celui-ci avant d'être transmise au codage B ou Q.

    La définition d'un encodage interne peut ne pas être nécessaire car PHP 5.6, car l'option de configuration sous-jacente mbstring.internal_encoding a été déconseillée au profit de default_charset , qui a été défini par défaut sur UTF-8. Notez qu'il ne s'agit que d'une valeur par défaut et qu'il peut être inapproprié de s'appuyer sur les valeurs par défaut de votre code.

  2. Vous devez inclure le nom d'en-tête et les deux-points dans la chaîne d'entrée. Le RFC impose une limite stricte sur la longueur de ligne et il doit aussi tenir pour la première ligne! Une alternative est de jouer avec le cinquième paramètre ($indent; Dernier en date de septembre 2015), mais c'est encore moins pratique.

  3. L'implémentation peut contenir des bogues. Même s'il est utilisé correctement, vous pourriez obtenir une sortie interrompue. C'est du moins ce que disent de nombreux commentaires sur la page de manuel. Je n'ai pas réussi à trouver de problème, mais je sais que la mise en œuvre des mots encodés est délicate. Si vous trouvez des bogues potentiels ou réels dans mb_encode_mimeheader() ou iconv_mime_encode(), faites-le moi savoir dans les commentaires.

Il y a aussi au moins un avantage à utiliser mb_encode_mimeheader(): il n'encode pas toujours tout le contenu de l'en-tête, ce qui économise de l'espace et laisse le texte lisible par l'homme. L'encodage n'est requis que pour les parties non ASCII. La sortie analogue à l'exemple iconv_mime_encode() ci-dessus est:

Subject: Very long text containing special characters like
 =?UTF-8?B?xJvFocSNxZnFvsO9w6HDrcOpPD4/PSsqIHByb2R1Y2VzIHNldmVyYWwgZW5j?=
 =?UTF-8?B?b2RlZC13b3Jkcywgc3Bhbm5pbmcgbXVsdGlwbGUgbGluZXM=?=

Exemple d'utilisation de 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);

Ceci est une alternative à l'extrait de code TL; DR en haut de ce message. Au lieu de simplement réserver l’espace pour Subject:, Il le place en fait puis le supprime afin de pouvoir l’utiliser avec l’interface stupide de mail().

Si vous préférez les fonctions mbstring que celles iconv, vous pouvez utiliser mb_send_mail() . Il utilise mail() en interne, mais code automatiquement le sujet et le corps du message. Encore une fois, à utiliser avec précaution .

Les en-têtes autres que le sujet nécessitent un traitement différent

Notez que vous ne devez pas supposer que le codage de tout le contenu d'un en-tête est OK pour tous les en-têtes qui peuvent contenir des caractères non ASCII. Par exemple. From, To, Cc, Bcc et Reply-To peuvent contenir des noms pour les adresses qu'ils contiennent, mais seuls les noms peuvent être codés, pas les adresses. La raison en est que le jeton <encoded-Word> Peut remplacer uniquement les jetons <text>, <ctext> Et <Word>, Et uniquement dans certaines circonstances (voir §5 de RFC 2047 ).

L'encodage de texte non ASCII dans d'autres en-têtes est une question connexe mais différente. Si vous souhaitez en savoir plus sur ce sujet, recherchez. Si vous ne trouvez pas de réponse, posez une autre question et pointez-moi dans les commentaires.

56
Palec

mb_encode_mimeheader () pour les chaînes UTF-8 peut être utile ici, par ex.

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