現象
MIMEエンコードされたメールヘッダを、PHPのmb_decode_mimeheader関数でデコードすると、半角スペースがアンダースコアでデコードされてしまうことがあります。
これはmb_decode_mimeheader
のバグと思われます。
解決方法
imap拡張をインストールして、imap_mime_header_decode関数を使いましょう。
解説
MIME(Multipurpose Internet Mail Extensions)というメールの仕様があります。
MIMEでは、メールのSubject等にASCIIの範囲外の文字を含める際は、所定の符号化を行う必要があります。
符号化方式としては、Quoted-printable方式と、Base64方式があります。
mb_decode_mimeheader
には、Quoted-printable方式で符号化されたヘッダのデコードに問題があります。
MIMEの仕様(RFC2047)では、「4.2. The "Q" encoding」において以下のように定めています。
The 8-bit hexadecimal value 20 (e.g., ISO-8859-1 SPACE) may be represented as "_" (underscore, ASCII 95.).
MIME文字列中で、半角スペースは=20
という文字列にエンコードされることもあれば、_
(アンダースコア)にエンコードされることもあります。
下記は、「テストThis is valid subject of MIME header」という文字列をQuoted-printableでMIMEエンコードしたものです。
=?utf-8?Q?=E3=83=86=E3=82=B9=E3=83=88This_is_valid_subject_of_MIM?=
=?utf-8?Q?E_header?=
しかし、この文字列に対してmb_decode_mimeheader
を実行すると、「テストThis_is_valid_subject_of_MIME_header」という文字列が返ってきます。
半角スペースがアンダースコアとして表現されることもある、という仕様に沿っていないのです。
では、どうするか。一番手軽な解決方法は、imap拡張を入れて、imap_mime_header_decode
関数を使うことです。
imap拡張はその名の通り、IMAPプロトコルをPHPで扱うためのライブラリですが、メール用のユーティリティ関数も含まれています。
imapの使用例
imap拡張はPHPにデフォルトでは含まれないので、別途インストールが必要です。
以下は、imap_mime_header_decode
関数と、それに類似の関数を使って、MIMEエンコードされた文字列をデコードする例です。
<?php
$subject = <<<TXT
=?utf-8?Q?=E3=83=86=E3=82=B9=E3=83=88This_is_valid_subject_of_MIM?=
=?utf-8?Q?E_header?=
TXT;
var_dump(mb_decode_mimeheader($subject)); // string(45) "テストThis_is_valid_subject_of_MIME_header"
var_dump(imap_utf8($subject)); // string(45) "テストThis is valid subject of MIME header"
var_dump(decode_mime_header($subject)); // string(45) "テストThis is valid subject of MIME header"
function decode_mime_header($str) {
$decoded = '';
$parts = imap_mime_header_decode($str);
foreach ($parts as $part) {
$decoded .= $part->text;
}
return $decoded;
}
imapにはimap_utf8 という関数もあり、出力はUTF-8限定ですが、正しく動作します。
ただし、imap_utf8
が上手く動かないという情報も見かけるので、imap_mime_header_decode
を使っておいたほうが無難かと思います。
imap_mime_header_decode
は、入力の各行の情報を格納したstdClass
のオブジェクトの配列を返します。
mb_decode_mimeheader
やimap_utf8
と同じ感覚で使えるよう、関数にラップすると使いやすいと思います。
やってはいけないワークアラウンド
imap拡張を入れずとも、以下のような関数を用意すれば対応できるのでは? と思うかもしれません。
function decode_mimeheader($str) {
return mb_decode_mimeheader(str_replace('_', '=20', $str));
}
しかし、上記関数は、文字列がQuoted-printableでMIMEエンコードされている場合にしか正しく動作しません。
例えば、「Subject: mb_decode_mimeheader」という、MIMEエンコードされていないヘッダに上記関数を適用すると、「Subject: mb=20decode=20mimeheader」となってしまいます。
MIMEデコードを正しく実装するのは難しいので、既存のライブラリやメーラーでもたまにバグが見つかります。
できるだけまともなライブラリを選んで使うようにしたいところです。