mb_encode_mimeheader関数で文字化けする php8.1 対応版
PHPバージョン8.1は、ノーメンテで使い続けているソースコードに対して厳しいだけでなく、常に修正し続けているソースでもエラーとなってしまう事が有ります。
今回は、メール送信時のメールヘッダーを形成する際に使う、mb_encode_mimeheader関数について。
PHP8.0までエラーも警告も無い状態で使っていたメール送信ロジックだったのですが、PHP8.1ではメール送信者やタイトルで化けるようになりました。
100%化けるトラブルではないので、テストケースによってはスルーされてしまうかもしれません。
で、調べてみると、PHP8.1だからエラーが出るという資料が無く、過去のバージョンでの対応策ならありました。
1.mb_internal_encodingをISO-2022-JPに指定し、変換処理後に元に戻す
2.mb_encode_mimeheaderに渡す前に、文字数を既定の74バイト以下に加工して渡す
3.そもそもUTF-8メールならばこのようなことは起きない。
らしいですね。
確かにUTF-8で出力したメールは化けていません。Outlookとスマホ、自前のガラケーで確認しました。
UTF-8メールにしてしまおうかとも考えましたが、ガラケーが対応できないことが多いらしいので、3Gガラケーが日本から消える2025年まではISO-2022-JPメールとすることにしました、が・・・
茨の道を選んでしまいました。
仕様ではmb_encode_mimeheaderが勝手に74バイトごとに区切ってエンコードしてくれるはずなんですが、エンコードした結果文字列を直後にmb_decode_mimeheaderで戻しても元の文字列にならない文字列があります。
なので、上記手法2の考えで、74バイト=全角37文字以下で化けない文字数を探ったら、おおよそ全角20~24文字前後まで少なくすると化けないようになったんですが、全角20文字と設定した場合にちょうど20文字の文字列を処理するとお化け文字が追加されるんです。
なので、手法1の処理も追加したら、トラブった文字列はクリアー出来ましたが別の文字列が化けるようになりました。
困った。
なので、一度エンコードした文字をデコードし、それらが異なった場合は文字数を減らして処理し、変換前後で同じ文字数になるまで対象文字列を短くするようループさせるロジックとしてみたら、どんな文字列でも化けない関数が出来ました。
まぁ、全角10文字ぐらいで処理すれば化けないんですが、メールのヘッダということを考えると転送量を少なくするロジックが良いはずなので、出来るだけバイト数が少なくなるようにしておくべきかと。
// スクリプトのエンコード
define("ENCODE","UTF-8");
// メールに使うエンコード
define("TRANS", "ISO-2022-JP-MS");
date_default_timezone_set("Asia/Tokyo");
mb_internal_encoding(ENCODE);
mb_regex_encoding(ENCODE);
mb_language("ja");
// ----------------------------------------------------------------- Function -*
// mail header FUNCTION
// ----------------------------------------------------------------------------*
function trans_and_encode_mimeheader($str) {
// 既定では半角74バイト=全角37文字まで文字化けは起きないはず
$split = 37;
// まずは対象文字列をiso-2022-jpへエンコード
$str = mb_convert_encoding($str, TRANS, ENCODE);
// 内部エンコードもiso-2022-jpに
mb_internal_encoding(TRANS);
// 出力用配列初期化
$return = array();
// 処理する文字の文字数
$pos = 0;
// 文字数が与えられた文章の文字数まで繰り返す
while ( $pos < mb_strlen($str,TRANS)):
// 短くする文字数
$back = 0;
// 変換前後の文字列が一致するまでループさせる
do {
// 処理する文字列を切り出す
$out = mb_strimwidth($str, $pos, ($split - $back), "", TRANS);
// エンコード
$enc = mb_encode_mimeheader($out, TRANS, "B");
// デコード
$dec = mb_decode_mimeheader($enc);
// 減らす文字数をインクリメント
$back++;
} while($out!==$dec);
// 出力配列にセット
$return[] = $enc;
// 次に処理する文字開始位置をセット
$pos += mb_strlen($out, TRANS);
endwhile;
// 内部エンコードをUTF-8に戻す
mb_internal_encoding(ENCODE);
// 出来上がった配列をヘッダに適した形に整形して引き渡す
return implode("\n ", $return);
} // end of function ----------
とりあえず今はこれでうまく動いています。
