要件として
- クソ長い日本語文字列をメールの差出人名として表示したい
なぜUTF-8の送りたいのか
「メール送信する際にiso-2022-jpで送ればいいじゃない。」
って声が聞こえてきそうですが、iso-2022-jpだと
環境依存文字、絵文字、他の言語(ハングル文字、タイ語)が化ける
日本語かどうか判定するのもめんどくさいと思ったので
ならいっそのことUTF-8統一で全メール送ろう と思った次第です。
実装
環境は以下
Symfony 2
PHP 5
Swiftmailer 5.2.1(だと思う)
ちょいと古いSymfonyを使用してる手前、書き方が最新ではないかもしれません
\Swift::init(function () {
\Swift_DependencyContainer::getInstance()
->register('mime.qpheaderencoder')
->asAliasOf('mime.base64headerencoder');
\Swift_Preferences::getInstance()->setCharset(Constant::UTF8);
});
$transport = new \Swift_SmtpTransport(
127.0.0.1,
25
);
$mailer = new \Swift_Mailer($transport);
$message = (new \Swift_Message())
->setFrom($fromAddress, $fromName)
->setTo($to)
->setSubject($subject)
->setBody($body, 'text/plain')
;
$mailer->send($message);
ざっくりとこんな感じですかね。
問題
とまぁ、例によって普通に実装したんですが、この状態だと日本語文字が化けることがあるんですね
一定数以上長い日本語文字を件名や、差出人名に入れると化けます。
原因
単純に言うとSwiftmailerのバグかと思われます。
base64エンコード + UTF-8 だとエンコードされた日本語1文字の途中で改行がされることがあり、そのタイミングから文字が化ける
※ 最新のswiftmailerのコード見ても「この処理がバグってるのだろう」という部分はそのままだったのでおそらく最新でも発生する
対応
「ライブラリのバグです。」
「じゃあ、仕方ないね。」
で片付けられないこの世の中。
多少無理してでもやります。
改行しているコードを直すのは厳しそうだったので、改行されないようにします
ゴリ押し解決
まずBase64エンコードしてる部分を改行仕様を弄ります
if (0 >= $maxLineLength || 76 < $maxLineLength) {
$maxLineLength = 76;
}
をこうしてやります
if (0 >= $maxLineLength || 998 < $maxLineLength) {
$maxLineLength = 998;
}
次にメールヘッダの一行の最大文字数を弄ります
abstract class Swift_Mime_Headers_AbstractHeader implements Swift_Mime_Header
{
...
/**
* The maximum length of a line in the header.
*
* @var int
*/
private $_lineLength = 78;
...
protected function encodeWords(Swift_Mime_Header $header, $input, $usedLength = -1)
{
...
$header->setMaxLineLength(76); // Forcefully override
...
}
protected function getTokenAsEncodedWord($token, $firstLineOffset = 0)
{
...
if ($firstLineOffset >= 75) { //Does this logic need to be here?
$firstLineOffset = 0;
}
...
$encodedTextLines = explode("\r\n",
$this->_encoder->encodeString(
$token, $firstLineOffset, 75 - $encodingWrapperLength, $this->_charset
)
);
...
}
...
}
をこうしてやります
abstract class Swift_Mime_Headers_AbstractHeader implements Swift_Mime_Header
{
...
/**
* The maximum length of a line in the header.
*
* @var int
*/
private $_lineLength = 998;
...
protected function encodeWords(Swift_Mime_Header $header, $input, $usedLength = -1)
{
...
$header->setMaxLineLength(998); // Forcefully override
...
}
protected function getTokenAsEncodedWord($token, $firstLineOffset = 0)
{
...
if ($firstLineOffset >= 997) { //Does this logic need to be here?
$firstLineOffset = 0;
}
...
$encodedTextLines = explode("\r\n",
$this->_encoder->encodeString(
$token, $firstLineOffset, 997 - $encodingWrapperLength, $this->_charset
)
);
...
}
...
}
これすると何が起きる?
簡単に申しますと、メールヘッダが998文字まで入ります('ω')
つまり998文字までなら改行されないので改行バグには当たらないです・・・・・('ω')
🙇🙇🙇根本解決にはなっていない🙇🙇🙇
最後に
脳筋ごり押し対応で無理やりバグを避けてみましたが
これは実際、根本解決に至っていない + ライブラリを改変しているので推奨できるやり方ではございません。
また、もっとベストな方法で実装できる知見をお持ちの方がいらっしゃいましたら、ご教授いただけると幸いです。。。
(実際エンコード前段階で区切り文字を挿入しておくみたいなアプローチもあったかなぁ…