web-dev-qa-db-ja.com

PHPからの件名ヘッダーのエンコードが壊れています

My 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";
51
daza166

Updateより実用的で最新の回答については、 Palecの回答 をご覧ください。


Content-Typeで指定された文字エンコードは、メッセージ本文の文字エンコードのみを記述し、ヘッダーは記述しません。 encoded-Word構文quoted-printableとともに使用する必要がありますencoding または Base64 encoding

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

imap_8bitquoted-printableエンコーディングおよび base64_encode 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);

または

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 で指定されたエンコードを指定するメカニズムがあります。

iconv_mime_encode() を介してSubjectをエンコードする必要があります。これはPHP 5:

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

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==?=

以下を含む$subjectの場合:

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

どのように機能しますか?

iconv_mime_encode()関数はテキストを分割し、各断片を <encoded-Word> トークンと folds の間に個別にエンコードします。エンコードされた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()が仕様を尊重していることだけです

関連する興味深い読み物は、ウィキペディアの記事 nicodeおよびメール です。

代替案

基本的なオプションは、制限された文字セットのみを使用することです。 ASCIIは動作が保証されています。ISOLatin 1(ISO-8859-1)、 ser2250504推奨 )は、おそらくエンコードもしない場合のフォールバックとして使用されるため、おそらく動作します。しかし、これらの文字セットは非常に小さいため、必要なすべての文字をエンコードできない可能性があります。また、RFCでは、Latin 1が機能するかどうかについては何も言及していません。

mb_encode_mimeheader()Paul Normanが答えました として使用することもできますが、間違って使用するのは簡単です。

  1. mb_internal_encoding() を使用して、mbstring関数の内部で使用されるエンコードを設定する必要があります。 mb_*関数は、このエンコーディングに入力文字列があることを想定しています。注意:mb_encode_mimeheader()の2番目のパラメーターは、入力文字列とは何の関係もありません(マニュアルの記載にもかかわらず)。これは、エンコードされたWordの<charset>に対応します(上記のどのように動作しますか?を参照)。入力文字列は、BまたはQエンコードに渡される前に、内部エンコードからこのエンコードに再エンコードされます。

    PHP 5.6以降、内部エンコーディングの設定は必要ないかもしれません。これは、UTFに設定されている mbstring.internal_encoding オプションの代わりに、基礎となる default_charset 構成オプションが廃止されたためですデフォルトでは-8なので、これは単なるデフォルトであり、コードのデフォルトに依存することは不適切である可能性があることに注意してください。

  2. 入力文字列にヘッダー名とコロンを含める必要があります。 RFCは行の長さに強い制限を課しており、最初の行にも適用する必要があります!別の方法として、5番目のパラメーター($indent; 2015年9月の最後のパラメーター)をいじることがありますが、これはさらに便利ではありません。

  3. 実装にバグがある可能性があります。正しく使用した場合でも、出力が破損する可能性があります。少なくともこれは、マニュアルページの多くのコメントが言っていることです。問題を見つけることはできませんでしたが、エンコードされた単語の実装には注意が必要です。 mb_encode_mimeheader()またはiconv_mime_encode()で潜在的または実際のバグを見つけた場合は、コメントでお知らせください。

mb_encode_mimeheader()を使用することには、少なくとも1つの利点があります。常にすべてのヘッダーコンテンツをエンコードするとは限らないため、スペースを節約し、テキストを人間が読めるようにします。エンコードは、非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() を使用しますが、メッセージの件名と本文を自動的にエンコードします。繰り返しますが、 注意して使用してください

Subject以外のヘッダーには異なる処理が必要です

非ASCII文字を含む可能性のあるすべてのヘッダーについて、ヘッダーのコンテンツ全体のエンコードがOKであると想定してはならないことに注意してください。例えば。 From、To、Cc、Bcc、およびReply-Toには、含まれるアドレスの名前が含まれる場合がありますが、アドレスではなく名前のみがエンコードされます。理由は、<encoded-Word>トークンが<text><ctext>、および<Word>トークンに置き換わることがあり、特定の状況下でのみです( RFC 2047の§5を参照 )。

他のヘッダー内の非ASCIIテキストのエンコードは、関連するが異なる質問です。 このトピックについて詳しく知りたい場合は、検索してください。答えが見つからない場合は、別の質問をし、コメントでそれを指摘してください。

56
Palec

mb_encode_mimeheader() UTF-8文字列の場合、ここで役立ちます。

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