メールの符号化について

受信したメールをprocmail経由でphpに渡してゴニョゴニョする時に失敗した件のまとめ。

まずメール送信の仕組みから分かっていなかった。

初期のメールではASCIIの7bitデータのみが想定されていたため、現在でも添付のバイナリやマルチバイトである日本語は7bitに変換(MIMEエンコード)してやる必要があるわけ。MIMEエンコードには主にBase64とQuoted-printableの変換方法がある(Base64とQuoted-printableはともに可逆)。

で、今回失敗したのは、通常procmail経由でphpに渡ってくるメールは7bit(今回はQuoted-printable)で符号化されたデータなのに、日本語であることを前提としていたためメールの解析に失敗したというわけ。

ちなみにメールがどの方法でエンコードされているかはメールのソースを見れば分かる。日本語のメールは最初から日本語で送られてくるわけでは決してない!gmail等のメーラーがメールヘッダーの以下の部分を見てよしなに変換してくれていたってわけ!

Content-Transfer-Encoding: quoted-printable

ここには通常「7bit」が指定されることが多い。7bitだと一概にこれでエンコードされている!とは言えないぽい(少し調べてみたけどよく分からんかった)。

またphpのmb_send_mailとか使うと「7bit」で送信されてその際にmb_languageの結果に基づいてエンコードされるんだけど、languageがJapaneseの場合は、「ISO-2022-JP/Base64」の組み合わせでエンコードされるぽい。

で、今回の件をよくよく調べたところ、メールの本文はMail_mimeDecode::decodeで取得しており、このクラスはContent-Transfer-Encodingを考慮するため、問題が無いはずであることが分かった。

なのでMail_mimeDecodeを要調査。

調査結果
結果的に問題はなかった。phpの解析部分に問題があっただけでMail_mimeDecodeは正しくquoted-printableの本文をデコードしていた。
以降メモ。
Mail_mimeDecodeはヘッダのContent-Transfer-Encodingを元にbodyをデコードする(オプションのdecode_bodies=>trueとすること)。今までbashのmailコマンドでテストしていたんだけどmailだとContent-Transfer-Encodingが指定できない?ぽいのでphpのmail関数を使うこと。以下のような感じ。

<?php
$subject = "サブジェクト";
$body = <<<EOF
本文
EOF;

$to = "to";
$from = "from";

mb_language('japanese');
mb_internal_encoding('utf-8');

$subject = mb_encode_mimeheader($subject);
$body = mb_convert_encoding($body,"Quoted-Printable","UTF-8");

$header = "From: ".$from."\n".
          "Mime-Version: 1.0\n".
          "Content-Type: text/plain; charset=utf-8\n".
          "Content-Transfer-Encoding: quoted-printable";

mail($to, $subject, $body, $header, "-f".$from);

参考サイト
エンコード・コレクション (メール、テキスト関連)
quoted-printable文字列の変換 - [サンプルコード/PHP] ぺんたん info
PHPでメールを送る – ぱんぴーまっしぐら