ようこそゲストさん

CPA-LABテクニカル

2008/06/16(月) PHPマルチバイト文字連結の罠(JIS,ISO-2022-JP)

Qdmail開発覚書。インターネットメールで日本語を使用する場合、iso-2022-jp(JIS)と呼ばれる文字セットを使用することが一般的である。しかし、日本語メールの処理をしようとして、iso-2022-jpで文字列処理を行うと、エスケープシーケンスのせいで、ものすごい非効率、場合によってはエラーが発生する可能性がある。ちなみにutf-8,euc-jp,shif-jisは原則としてエスケープシーケンスを使用しないので大丈夫である。

どんな不具合?どんな非効率?

'あい' == 'あ' . 'い' が false になる時
PHPでのiso-2022-jpの処理につき、以下の $a , $b は等価ではない。
$a = mb_convert_encoding('あい','iso-2022-jp');
$b = mb_convert_encoding('あ','iso-2022-jp') 
   . mb_convert_encoding('い','iso-2022-jp');;
下記コードを実行すると、not equal となる。$a も $b も 「あい」なのに*1
$a = mb_convert_encoding('あい','iso-2022-jp');
$b = mb_convert_encoding('あ','iso-2022-jp') . mb_convert_encoding('い','iso-2022-jp');;

if( $a == $b){
	echo "equal";
}else{
	echo "not equal";
}

echo "<p>";
echo '$a='.$a;
echo "</p><p>";
echo '$b='.$b;
echo "</p>";
(PHP5.2.5)

その理由はiso-2022-jpで使われるエスケープシーケンスにある。
iso-2022-jpでは、マルチバイトを表すのに以下のような取り決めがある。
1B 24 42   *2
の3バイトがマルチバイトの始まり、
1B 28 42
の3バイトがマルチバイトの終わり(というかASCIIの始まり)
つまり同じ「あい」という文字を表すのに、2通りの表現方法が考えられる。
パターンA)
(エスケープ始まり)あい(エスケープ終わり)

パターンB)
(エスケープ始まり)(エスケープ終わり)(エスケープ始まり)(エスケープ終わり)
パターンBは明らかに非効率である。なんせ、ひとつの(エスケープ)で3バイトだ。
しかしながら、上記コードの $b はまさに、このB)パターンで、変数に入力されている。*3

だから、同じ「あい」が代入されている変数同士の比較で、not equal となってしまうのである。

Qdmailもバージョン0.8.4までは、この非効率バージョンだった。今では深く反省している。*4

原因

文字列連結においてマルチバイト処理が考慮されているのであれば、上記の$bは、$aの形式、すなわちパターンA)になるのであろうけれども、PHPでは、そこまでの面倒はみてくれない*5。PHPは単純に、文字列のコードをバイナリで連結するだけである。

回避方法

いったんutf-8なりeuc-jpなどのエスケープシーケンスを利用しない文字列に変換するのが、もっともてっとり早いと思う。
qdmailでは、メールのsubjectを1文字づつ分解した上で連結していたところ、上記の不具合がでたので、1文字づつは分解せず、分析のためのポインタを用意し、mb_substrで一気に*6抽出するようにした。
(mime変換を自作したのでこんなことに悩まなくてはならないのよん)

というわけで

まあ、iso-2022-jpをPHPで加工する人は多くないと思うけど。
もし他の言語など、エスケープシーケンスを利用する文字セットを扱う予定のプログラムは要注意ということで。

ちなみに、yahooメールに、上記B)パターンでsubjectのmimeエンコードをすると、メール一覧画面でタイトル表示が乱れますデス。

高機能日本語メールライブラリQdmail0.8.5a以降は大丈夫です*7

他の文字セット

繰り返しになるが、utf-8,euc-jp,shif-jisはエスケープシーケンスを使用しないので、上記の問題は発生しない。

*1 : ブラウザのエンコードはJISで試してみましょう。

*2 : 16進数

*3 : それはbin2hex関数などで確認すればわかる。

*4 : 指摘していただいたkeiさんに深く感謝

*5 : もっとも面倒みてくれていたら別の不満があったかも知れない

*6 : しかし何度も

*7 : のはず

1: 名無しさん 2008年10月17日(金) 午後6時11分

まさにこの症状のせいで悩まされていました。
代入単位でエスケープシーケンスが付いてしまうんですね…
おかげで解決する事ができましたっ


名前:  非公開コメント   

  • TB-URL  http://www.cpa-lab.com/tech/092/tb/