LoginSignup
2
0

More than 5 years have passed since last update.

[PHP] mb_decode_mimeheader() は Quoted-printable なヘッダのデコードに失敗する

Last updated at Posted at 2017-01-13

現象

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_mimeheaderimap_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デコードを正しく実装するのは難しいので、既存のライブラリやメーラーでもたまにバグが見つかります。
できるだけまともなライブラリを選んで使うようにしたいところです。

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0