HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

mb_encode_mimeheader/mb_decode_mimeheaderする際には内部文字エンコーディングに注意

マニュアルをちゃんと読むと書いてあるのだが。

前者のmb_encode_mimeheader()は、以下のように書いてある。

パラメータ

str
 エンコードする文字列。 mb_internal_encoding() と同じエンコーディングにしなければいけません。

つまり、マルチバイト文字を扱う限り、mb_internal_encoding()による内部文字エンコーディングの設定が必須なのだ。

ついでに、後者のmb_decode_mimeheader()のマニュアルも見てみる。

返り値

内部文字エンコーディングでデコードされた文字列を返します。

「内部文字エンコーディング」と明記してある。

というわけで、ちょっと試してみるために、以下のようなシナリオに沿ったプログラムを書いてみる。

共通部品/処理

<?php

/*
 * コマンド「od -c」を模したダンプを出力する関数。
 */
function od($str) {
    for ($i = 0; $i < strlen($str); ++$i) {
        if ($i % 16 === 0) {
            if ($i > 0) {
                echo PHP_EOL;
            }
            printf("%08o", $i);
        }
        $s = substr($str, $i, 1);
        if (ctype_graph($s) || $s === " ") {
            printf(" %3s", $s);
        } else {
            printf(" %03o", ord($s));
        }
    }
    if ($i % 16 !== 15) {
        echo PHP_EOL;
    }
    printf("%08o\n", $i);
}
<?php

// ** このファイルはもちろん「UTF-8」で保存する **

// 初期設定
mb_internal_encoding("UTF-8");

// メールで送る文字列の件名
$str = "あいうえおかきくけこさしすせそたちつてとなにぬねの";

シナリオ1

ソースコードは以下の通り。

<?php

include("od.php");
include("init.php");

// ** このファイルはもちろん「UTF-8」で保存する **

// 文字列の文字エンコーディング変換
$convStr = mb_convert_encoding($str, "ISO-2022-JP", "UTF-8");

// エンコード処理
$encStr = mb_encode_mimeheader($convStr, "ISO-2022-JP");
echo "// エンコードされた文字列" . PHP_EOL;
var_dump($encStr);

echo PHP_EOL;

// デコード処理
$decStr = mb_decode_mimeheader($encStr);
echo "// デコードされた文字列" . PHP_EOL;
var_dump($decStr);
od($decStr);

実行結果。

// エンコードされた文字列
string(115) "=?ISO-2022-JP?B?GyRCJCIkJCQmJCgkKiQrJC0kLyQxJDMkNSQ3JDkkOyQ9JD8kQSREJEYk?=
 =?ISO-2022-JP?B?SCRKJEskTCRNJE4bKEI=?="

// デコードされた文字列
string(68) "あいうえおかきくけこさしすせそたちつてH$J$K$L$M$N"
00000000 343 201 202 343 201 204 343 201 206 343 201 210 343 201 212 343
00000020 201 213 343 201 215 343 201 217 343 201 221 343 201 223 343 201
00000040 225 343 201 227 343 201 231 343 201 233 343 201 235 343 201 237
00000060 343 201 241 343 201 244 343 201 246   H   $   J   $   K   $   L
00000100   $   M   $   N
00000104

あれ、文字化けした。

多分、mb_decode_mimeheader()でデコードするときに以下のような処理をしているのだろうと思われる。

<?php
mb_internal_encoding("UTF-8");
var_dump(
    mb_convert_encoding(base64_decode("GyRCJCIkJCQmJCgkKiQrJC0kLyQxJDMkNSQ3JDkkOyQ9JD8kQSREJEYk"), mb_internal_encoding(), "ISO-2022-JP")
    . mb_convert_encoding(base64_decode("SCRKJEskTCRNJE4bKEI="), mb_internal_encoding(), "ISO-2022-JP"));
string(68) "あいうえおかきくけこさしすせそたちつてH$J$K$L$M$N"

同じ結果になった。

というわけで、これではダメ。

シナリオ2

ソースコードは以下の通り。

<?php

include("od.php");
include("init.php");

// ** このファイルはもちろん「UTF-8」で保存する **

// 文字列の文字エンコーディング変換
$convStr = mb_convert_encoding($str, "ISO-2022-JP", "UTF-8");

// 内部文字エンコーディングを変更
$origInternalEncoding = mb_internal_encoding();
mb_internal_encoding("ISO-2022-JP");

// エンコード処理
$encStr = mb_encode_mimeheader($convStr, "ISO-2022-JP");
echo "// エンコードされた文字列" . PHP_EOL;
var_dump($encStr);

// 内部文字エンコーディングを戻す
mb_internal_encoding($origInternalEncoding);

echo PHP_EOL;

// 内部文字エンコーディングを変更
mb_internal_encoding("ISO-2022-JP");

// デコード処理
$decStr = mb_decode_mimeheader($encStr);
echo "// デコードされた文字列" . PHP_EOL;
var_dump($decStr);
od($decStr);

// 内部文字エンコーディングを戻す
mb_internal_encoding($origInternalEncoding);

実行結果。

// エンコードされた文字列
string(123) "=?ISO-2022-JP?B?GyRCJCIkJCQmJCgkKiQrJC0kLyQxJDMkNSQ3JDkkOyQ9JD8kQSREGyhC?=
 =?ISO-2022-JP?B?GyRCJEYkSCRKJEskTCRNJE4bKEI=?="

// デコードされた文字列
string(56) "あいうえおかきくけこさしすせそたちつてとなにぬねの"
00000000 033   $   B   $   "   $   $   $   &   $   (   $   *   $   +   $
00000020   -   $   /   $   1   $   3   $   5   $   7   $   9   $   ;   $
00000040   =   $   ?   $   A   $   D   $   F   $   H   $   J   $   K   $
00000060   L   $   M   $   N 033   (   B
00000070

一見よさそうだが、ダンプを見ると、「ISO-2022-JP」になっている。システムの内部文字エンコーディングは「UTF-8」だったはずだ。

これもダメ。

シナリオ3

ソースコードは以下の通り。

<?php

include("od.php");
include("init.php");

// ** このファイルはもちろん「UTF-8」で保存する **

// 文字列の文字エンコーディング変換
$convStr = mb_convert_encoding($str, "ISO-2022-JP", "UTF-8");

// 内部文字エンコーディングを変更
$origInternalEncoding = mb_internal_encoding();
mb_internal_encoding("ISO-2022-JP");

// エンコード処理
$encStr = mb_encode_mimeheader($convStr, "ISO-2022-JP");
echo "// エンコードされた文字列" . PHP_EOL;
var_dump($encStr);

// 内部文字エンコーディングを戻す
mb_internal_encoding($origInternalEncoding);

echo PHP_EOL;

// デコード処理
$decStr = mb_decode_mimeheader($encStr);
echo "// デコードされた文字列" . PHP_EOL;
var_dump($decStr);
od($decStr);

実行結果。

// エンコードされた文字列
string(123) "=?ISO-2022-JP?B?GyRCJCIkJCQmJCgkKiQrJC0kLyQxJDMkNSQ3JDkkOyQ9JD8kQSREGyhC?=
 =?ISO-2022-JP?B?GyRCJEYkSCRKJEskTCRNJE4bKEI=?="

// デコードされた文字列
string(75) "あいうえおかきくけこさしすせそたちつてとなにぬねの"
00000000 343 201 202 343 201 204 343 201 206 343 201 210 343 201 212 343
00000020 201 213 343 201 215 343 201 217 343 201 221 343 201 223 343 201
00000040 225 343 201 227 343 201 231 343 201 233 343 201 235 343 201 237
00000060 343 201 241 343 201 244 343 201 246 343 201 250 343 201 252 343
00000100 201 253 343 201 254 343 201 255 343 201 256
00000113

よさそうである。文字化けしていないし、ダンプを見ても「UTF-8」になっている。念のため確認で以下を実行してみた(コンソールの文字エンコーディングUTF-8)。

$ echo -n 'あいうえおかきくけこさしすせそたちつてとなにぬねの' | od -c
0000000 343 201 202 343 201 204 343 201 206 343 201 210 343 201 212 343
0000020 201 213 343 201 215 343 201 217 343 201 221 343 201 223 343 201
0000040 225 343 201 227 343 201 231 343 201 233 343 201 235 343 201 237
0000060 343 201 241 343 201 244 343 201 246 343 201 250 343 201 252 343
0000100 201 253 343 201 254 343 201 255 343 201 256
0000113
$ 

同じ結果だ。

まとめ

というわけで、まとめると、以下のようにして各関数を呼び出さないといけない。