mb_send_mail、mb_encode_mimeheaderの文字化けのまとめ(半角カタカナなど)

出力される文字コード
変換前の文字コード
半角カタカナが????に文字化けする場合
38バイト目(74文字)以降で文字化けする場合
まとめ
[参考記事] メールテキストの1行の文字数制限(最大1,000文字、78文字以下であるべき)
[参考記事] mb_send_mailでCCやBCCを指定する 表示名を指定する

出力される文字コード

mb_send_mail、mb_encode_mimeheaderともに標準の設定ではUTF-8の文字コードでエンコードされます。

Subject: =?UTF-8?B?xxxxxxxxxxxxxxxxxxxxxxx?=
From: =?UTF-8?B?xxxxxxxxx==?=<from@example.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: BASE64

件名・本文・メール送信者名はすべてUTF-8をBase64エンコードされて送信されています。

UTF-8で送信すると、受信するメーラーによって文字化けが発生します。

文字化けしないようにするには、ISO-2022-JP(JISコード)にする必要があります。
UTF-8ではなく、ISO-2022-JP(JISコード)で送信するには、mb_languageを設定します。

PHPコードでは

mb_language("Japanese");

または

mb_language("ja");

.htaccessまたはhttpd.confでは

php_value mbstring.language "Japanese"

php.iniでは

mbstring.language = Japanese

mb_languageをJapaneseまたはJaに設定するとmb_send_mail、mb_encode_mimeheaderの挙動が変わり、メールソースは次のようになります。

Subject: =ISO-2022-JP?B?xxxxxxxxxxxxxxxxxxxxxxx?=
From: =?ISO-2022-JP?B?xxxxxxxxx==?=<from@example.com>
Content-Type: text/plain; charset=ISO-2022-JP
Content-Transfer-Encoding: 7bit

mb_languageのマニュアルには次のように書かれています。

mb_language

mb_language ([ string $language = mb_language() ] ) : mixed
現在の言語を設定あるいは取得します。

パラメータ
language
e-mail メッセージのエンコーディングとして使用します。有効な言語は、"Japanese", "ja","English","en", "uni" (UTF-8) です。 mb_send_mail() は、e-mail をエンコードする際にこの設定を使用します。

言語とその設定は、Japanese の場合は ISO-2022-JP/Base64、uni の場合は UTF-8/Base64、English の場合は ISO-8859-1/quoted printable です。

出力される文字コードはmb_languageにより決まり、mb_send_mail、mb_encode_mimeheaderに渡される文字列から自動的にエンコードがされます。
mb_encode_mimeheaderは第2引数に指定した場合、その設定が優先されます。デフォルトはmb_languageで決まる文字コードです。

変換前の文字コード

変換前の文字コードは?というと、『mb_internal_encodingで決まる』というのは間違いです。

マニュアルには次のようにあります。

mb_send_mail

mb_send_mail ( string $to , string $subject , string $message [, mixed $additional_headers = NULL [, string $additional_parameter = NULL ]] ) : bool
email を送信します。ヘッダと本文は mb_language() の設定に基づき変換、エンコードされます。 これは mail() のラッパー関数です。詳細は、 mail() を参照ください。

mb_encode_mimeheader

mb_encode_mimeheader ( string $str [, string $charset = mb_language() によって決まる値 [, string $transfer_encoding = "B" [, string $linefeed = "\r\n" [, int $indent = 0 ]]]] ) : string
MIME ヘッダエンコーディング方式によって文字列 str をエンコードします。

パラメータ
str
エンコードする文字列。 mb_internal_encoding() と同じエンコーディングにしなければいけません。

charset
charset は、str の変換後の文字セット名です。デフォルトは、現在の NLS 設定 (mbstring.language) によって決まります。

transfer_encoding
transfer_encoding は MIME エンコーディングの 方式を指定します。"B" (Base64) または "Q" (Quoted-Printable) のどちらかでなければなりません。 デフォルトは "B" です。

linefeed
linefeed は EOL (行末) のマーカで、 mb_encode_mimeheader() が行を折りたたむ (≫ RFC 用語で、 ある一定より長い行を複数行に分割することを言います。 分割する長さは、現在 74 文字に固定されています) 際に利用します。 デフォルトは "\r\n" (CRLF) です。

indent
最初の行の字下げ (ヘッダで str の前におく文字数)。

マニュアルも間違っています。

『出力される文字コードであればそのまま、出力される文字コードと異なればmb_internal_encodingで決まる』
というのが、正しい挙動のようです。

つまりmb_encode_mimeheaderの第1引数の『mb_internal_encoding() と同じエンコーディングにしなければいけません。』は間違いです。

渡す文字列の文字コードがUTF-8であれば、ISO-2022-JPに変換して渡すか、mb_internal_encodingをUTF-8に設定する。
渡す文字列の文字コードがShift-JISであれば、ISO-2022-JPに変換して渡すか、mb_internal_encodingをSJISに設定する。
ということになります。

PHPコードでは

mb_internal_encoding("UTF-8");

.htaccessまたはhttpd.confでは

php_value mbstring.internal_encoding UTF-8

php.iniでは

mbstring.internal_encoding UTF-8

半角カタカナが????に文字化けする場合

ISO-2022-JP(JISコード)には半角カタカナがありません。
全角文字が文字化けせず半角カタカナのみが文字化けするのは、ISO-2022-JPに変換する箇所で文字化けが発生しています。

PHPの文字エンコードISO-2022-JPで、半角カタカナを扱うにはISO-2022-JP-MSを使用します。

このISO-2022-JP-MSとは、ISO-2022-JPに対しCP932全文字(重複を除くWindows-31Jの文字)が扱えるよう拡張をした文字コードです。

件名・本文・メール送信者名それぞれをmb_send_mail、mb_encode_mimeheaderに渡す前にISO-2022-JP-MSに変換します。

$email_from_name = mb_convert_encoding($email_from_name,"ISO-2022-JP-MS","UTF-8");
$email_body = mb_convert_encoding($email_body,"ISO-2022-JP-MS","UTF-8");
$email_subject = mb_convert_encoding($email_subject,"ISO-2022-JP-MS","UTF-8");

$header="From: " .mb_encode_mimeheader($email_from_name) ."<from@example.com>";
mb_send_mail($email_to,$email_subject,$email_body,$header);

このようにmb_internal_encodingで指定された文字コードではなく、mb_languageで決まる文字コード(JapaneseであればISO-2022-JP)でも正しく動作するため、mb_encode_mimeheaderのマニュアルにある『mb_internal_encoding() と同じエンコーディングにしなければいけません。』は間違いです。

ただし半角カタカナは機種依存文字のため、受け取り側のメーラーが半角カタカナを表示できなければ、そもそも半角カタカナは表示できません。

38バイト目(74文字)以降で文字化けする場合

これはかつてあったPHPのバグです。

RFCで定義されているメールの仕様では、メールテキストの1行の文字数は78文字以下であるべきとあります。
[参考記事] メールテキストの1行の文字数制限(最大1,000文字、78文字以下であるべき)

mb_encode_mimeheaderは、文字数が長いと次のように38バイトごとに分割されてエンコードされます。

=?ISO-2022-JP?B?GyRCIzEjMiMzIzQjNSM2IzcjOCM5IzAjMSMyIzMjNCM1IzYjNyM4GyhC?=
 =?ISO-2022-JP?B?GyRCIzkjMCMxIzIjMyM0IzUjNiM3IzgjOSMwIzEjMiMzIzQjNSM2GyhC?=
 =?ISO-2022-JP?B?GyRCIzcjOCM5IzAjMSMyIzMjNCM1IzYjNyM4IzkjMBsoQg==?=

この分割された部分で文字化けが発生していたようです。
(現在、事象を確認できないため未確認)

このため38バイト以下で文字列を分割してmb_encode_mimeheaderでそれぞれを変換するというラッパーが必要だったようです。

まとめ

mb_language("Japanese");

を記述する。
(これにより出力文字コードがISO-2022-JPになります。)

mb_send_mail、mb_encode_mimeheaderに渡す文字列をISO-2022-JP-MSにエンコードする。

mb_convert_encoding($email_from_name,"ISO-2022-JP-MS","UTF-8");

(これにより半角カタカナの文字化けが解消されます。)

ただしmb_encode_mimeheaderのマニュアル通りに仕様変更された場合、この設定では挙動がおかしくなることが考えられます。

mb_language("Japanese");

$email_to = "to@example.com";

$email_from_name = "テスト test テスト";
$email_body = "テスト test テスト";
$email_subject = "テスト test テスト";

$email_from_name = mb_convert_encoding($email_from_name,"ISO-2022-JP-MS","UTF-8");
$email_body = mb_convert_encoding($email_body,"ISO-2022-JP-MS","UTF-8");
$email_subject = mb_convert_encoding($email_subject,"ISO-2022-JP-MS","UTF-8");

$header="From: " .mb_encode_mimeheader($email_from_name) ."<from@example.com>";
mb_send_mail($email_to,$email_subject,$email_body,$header);

スポンサーリンク

関連記事

スポンサーリンク

Tera Term Pro

ホームページ製作・web系アプリ系の製作案件募集中です。

上に戻る