SwiftMailerをiso-2022-jp対応にする

  • ちょっと古いのですが、SwiftMailer(バージョン3系)を使うことになりました。
  • すべてutf-8でいくなら問題なさそうですが、他のマルチバイト言語のことはあまり考慮されていないようですね。
  • ソースに登場するのは「iso-8859-1」と「utf-8」だけのようです。
  • 最近のメール事情を思うと、utf-8エンコードでも大丈夫そうな気はしますが、やっぱり日本のスタンダードであるiso-2022-jpに対応しておくべきと思われますので、修正しました。

修正その1 本文部分のエンコード

  • Charsetの設定自体は可能なので、本文部分はextendsして、エンコードするようにしました。
class my_SwiftMessage extends Swift_Message
{
    public function setBody($body) {
        $body = mb_convert_encoding($body, $this->getCharset(), mb_detect_encoding($body));
        parent::setBody($body);
    }
}

修正その2 ヘッダ部分のエンコード

  • 特に目立ってしまうのがメールヘッダの Subject: だと思います。
  • これも修正その1と同じようにextendsしてオーバーライドすべきと思ったのですが、インスタンス化している箇所が深いところにあるので、仕方なくSwift_Message_Headersを修正しました。

getEncodedメソッドの修正箇所

elseif ($this->encoding == "B") //Need to Base64 encode
{
  //元ソース部分をコメントアウト
  //See the comments in the elseif() above since the logic is the same (refactor?)
  //$spec = "=?" . $this->getCharset() . "?B?";
  //$end = "?=";
  //$used_length = strlen($name) + 2 + strlen($spec) + 2;
  //$encoded_value[$key] = Swift_Message_Encoder::instance()->base64Encode(
  //  $row, (75-(strlen($spec)+5)), ($key > 0 ? 0 : (76-($used_length+3))), true, $this->LE);
  
  //置換部分 mb_internal_encoding()が汚い
  // mb_encode_mimeheaderでencode
  $internal_encoding_save = mb_internal_encoding();
  mb_internal_encoding(mb_detect_encoding($row));
  $encoded_value[$key] = mb_encode_mimeheader($row, $this->getCharset(), 'B', $this->LE);
  mb_internal_encoding($internal_encoding_save);
}

困った人 : mb_encode_mimeheader

困ったことその1

  • 渡す文字列のエンコードを指定できない仕様。正確には、mb_internal_encodingで設定できます。その関係で上記コードのように、ちょっとややこしいことになっています。
  • ここで感じたのですが、mb_convert_encodingはto_encodingとfrom_encodingを指定できるのに、mb_encode_mimeheaderはto_encoding相当のcharsetしかありません。
  • そもそも用途が違うといえばそうなのですが、変換するということはfromとtoが必要になるので、結局はmb_internal_encodingで設定するハメになっていますよね。

困ったことその2

  • はじめ、Swift_Message::setSubjectを上記setBodyと同じようにオーバーライドして、mb_convert_encodingするようにしました。
  • しかし、長いサブジェクトを設定すると途中から文字化けするという問題が発生しました。
  • 詳しくは後述しますが、結論としては「エンコード変換はmb_encode_mimeheaderに任せる」ということです。

長いサブジェクトをJIS+Base64エンコードすると途中から文字化けする件

  • 下記ソースで検証
<?php
   $subject_utf = 'サブジェクト';
   
   // ソースはutf-8で記述
   mb_internal_encoding('utf-8');
   
   // 先にJISにエンコード変換してencode_mimeheader
   $subject_jis = mb_convert_encoding($subject_utf, 'iso-2022-jp', 'utf-8');
   echo mb_encode_mimeheader($subject_jis, 'iso-2022-jp') . PHP_EOL;
   
   // utf8のままencode_mimeheader (encode_mimeheaderに変換を任せる)
   echo mb_encode_mimeheader($subject_utf, 'iso-2022-jp') . PHP_EOL;
  • 結果はどちらも同じなので問題ないように思える
=?ISO-2022-JP?B?GyRCJTUlViU4JSclLyVIGyhC?=
=?ISO-2022-JP?B?GyRCJTUlViU4JSclLyVIGyhC?=
  • 1行目を修正し、長い文字列をセットすると・・・
<?php
    $subject_utf = '長いサブジェクトの場合は結果が異なります';
  • 結果が異なってしまいます。文字列が長い場合は複数行に分割されますが、分割境界部分が異なります。この場合、1行目末尾の「JF4k」と「GyhC」と2行目の冒頭部分が異なります。
=?ISO-2022-JP?B?GyRCRDkkJCU1JVYlOCUnJS8lSCROPmw5ZyRPN2syTCQsMFskSiRqJF4k?=
 =?ISO-2022-JP?B?ORsoQg==?=
=?ISO-2022-JP?B?GyRCRDkkJCU1JVYlOCUnJS8lSCROPmw5ZyRPN2syTCQsMFskSiRqGyhC?=
 =?ISO-2022-JP?B?GyRCJF4kORsoQg==?=
  • JISコードのシフトイン・シフトアウトコード(?)あたりの問題だと思うのですが、この違いにより、サブジェクトの後半が文字化けしてしまいます。(受信するメールソフト/メールアプリによっては補正してくれる場合もありますが、そこは頼るところではないでしょう)

facebook slideshare rubygems github qiita