ようこそゲストさん

CPA-LABテクニカル

メッセージ欄

分類 【php5】 で検索

一覧で表示する

2008/07/01(火) mb_encode_mimeheaderの都市伝説を検証する

はてブ情報 はてブに登録 はてブ数 php5spok
わかりやすくするために、1文字だけMIMEエンコードしてみましょう。後で、長文もテストしてみます。
ソースコードはutf-8で保存しています。
code.1
$internal = 'utf-8';
$input    = 'utf-8';
$target   = 'iso-2022-jp';

mb_language('ja');
mb_internal_encoding($internal);
$word = "あ";
$word = mb_convert_encoding($word,$input,mb_detect_encoding($word));
echo $a=mb_encode_mimeheader($word,$target);
その結果
 =?ISO-2022-JP?B?GyRCJCIbKEI=?=
ただしく変換されています。
code.2
$internal = 'euc-jp';
$input    = 'utf-8';
$target   = 'iso-2022-jp';
以下、省略
 =?ISO-2022-JP?B?Pz8=?=
失敗しています。
code.3
$internal = 'iso-2022-jp';
$input    = 'iso-2022-jp';
$target   = 'iso-2022-jp';
以下、省略
 =?ISO-2022-JP?B?GyRCJCIbKEI=?=
もちろん正しく変換されています。
code.4
$internal = 'iso-2022-jp';
$input    = 'iso-2022-jp';
$target   = 'utf-8';
以下、省略
 =?UTF-8?B?44GC?=
これも正しく動作しています。

すべての組み合わせまでは試していませんので、皆様も試していただければ幸いです。
次はインプットする文章を長文にして2行に渡るMIMEエンコードがうまくいくかどうか試してみましょう。
以下のテストコードの文字コードはutf-8を前提にしています。ソースコード自体を他の文字コードで保存する場合は、$charset_sorce='utf-8';の部分を該当の文字コードに置き換えて下さい。
なお、下記コードの中で「おaa」などという文章を試しているのは、MIME変換処理において、マルチバイト文字の途中でぶったぎるかどうか、という点も合わせて検証しているからです*1
<?php

$Mime = new MimeTest();
$Mime->language='ja';
$charset_sorce='utf-8';

$charset[]=array(
    'internal'=>'utf-8',
    'input'=>'utf-8',
    'target'=>'iso-2022-jp',
);
$charset[]=array(
    'internal'=>'euc-jp',
    'input'=>'euc-jp',
    'target'=>'iso-2022-jp',
);
$charset[]=array(
    'internal'=>'euc-jp',
    'input'=>'euc-jp',
    'target'=>'utf-8',
);
$charset[]=array(
    'internal'=>'iso-2022-jp',
    'input'=>'iso-2022-jp',
    'target'=>'utf-8',
);

$charset[]=array(
    'internal'=>'euc-jp',
    'input'=>'iso-2022-jp',
    'target'=>'iso-2022-jp',
);
$charset[]=array(
    'internal'=>'utf-8',
    'input'=>'iso-2022-jp',
    'target'=>'iso-2022-jp',
);
$charset[]=array(
    'internal'=>'utf-8',
    'input'=>'euc-jp',
    'target'=>'iso-2022-jp',
);
$charset[]=array(
    'internal'=>'iso-2022-jp',
    'input'=>'euc-jp',
    'target'=>'iso-2022-jp',
);

$word[] = "お問い合わせありがとうございます【○●サイト】";
$word[] = "おa問い合わせありがとうございます【○●サイト】";
$word[] = "おaa問い合わせありがとうございます【○●サイト】";

foreach($charset as $chars){
    foreach($word as $wd){
        echo "<pre>";
        print_r($chars);
        print_r($Mime->run($chars,$wd,$charset_sorce));
        echo "</pre>";
    }
}

class MimeTest{

    var $language = 'ja';
    var $charset_internal = null;
    var $charset_input    = null;
    var $charset_target   = null;

    function run($charset,$word,$charset_sorce){

        $this->charset_internal = $charset['internal'];
        $this->charset_input = $charset['input'];
        $this->charset_target = $charset['target'];
        mb_language($this->language);
        mb_internal_encoding($this->charset_internal);
        $word = mb_convert_encoding($word,$this->charset_input,$charset_sorce);
        $ret_mime = mb_encode_mimeheader($word,$this->charset_target);

        $ret_mime_array = preg_split('/\r?\n/is',$ret_mime);
            foreach($ret_mime_array as $ret_mime_line){
                $enc = strtoupper($this->charset_target);
                $ret_strip = preg_replace(array('/=\?'.$enc.'\?B\?/','/={0,3}\?=$/'),'',trim($ret_mime_line));
                $ret_base64[]=$ret_strip;
                $ret_base64_len[]=strlen($ret_strip);
                $ret_decode[] = base64_decode($ret_strip);
            }
        return array(
            $ret_mime,
            mb_convert_encoding(mb_decode_mimeheader($ret_mime),$charset_sorce,$this->charset_internal),
            implode(' - ',$ret_base64),
            implode(' - ',$ret_base64_len),
            mb_convert_encoding(implode('',$ret_decode),$charset_sorce,$this->charset_target),
        );
    }
}
結論の出力は長くなるので省略しますが、次のような結論になります。
1 mb_internal_encodingの文字コード
2 mb_encode_mimeheaderに渡す文字列の文字コード
3 mime変換する最終文字コード

とした場合。

1=2 であれば正常稼働。
1≠2であれば異常(文字化け)

となり、3は1と2から独立して指定できる。
ということがわかります。
また、マルチバイトの途中でMIME変換をぶった切るということもないようですので、「1=2」というバッドノウハウさえ守れば、mb_encode_mimeheaderは、安心して使用できるかと思います。

Qdmailでは、mb_encode_mimeheaderが安心して使えるかどうかわからなかった時期に制作しましたので、mb_encode_mimeheaderについては同等のメソッドを自作しています。

*1 : 結論としては、問題ありませんでした。

mbstring.cのPHP_FUNCTION(mb_encode_mimeheader)
の初めの方に次のような記述があります。
  string.no_encoding = MBSTRG(current_internal_encoding);
ここで、「インプットされる文字列の文字コード=インターナル文字コード」という処理を行っているわけですね。
ここで、もしphp.ini等において mb_internal_encoding が設定されておらず、かつ、mb_language が'ja'(日本語)に設定されている場合は、euc-jpがセットされます。だからこそ、この記事の前に、PHPの日本語デフォルトはEUC-JPの記事を書いていたのです~。
この記事を書くための覚書です。これから下はあまり意味はありません。

mb_*関数群では、mbfl_string構造体でもって、マルチバイトの値を管理している。
mbfl_string構造体は次のようなデータを持つ。
  language // 言語情報 
  encoding // エンコーディング情報
  文字列ポインタ // 文字列のデータ
  len // 長さ
また、mime_header_encoder_data構造体は次のような構造となっている。(注釈はスポックによる)
mbfl_convert_filter構造体には、from文字コード→to文字コードの変換メソッド名(の一部)が入る。
struct mime_header_encoder_data {
	mbfl_convert_filter *conv1_filter; // input文字コード→wcharのコンバート
	mbfl_convert_filter *block_filter; // MIMEの=?iso-2022-jp部分(encoded-block)実際はコンバートしない
	mbfl_convert_filter *conv2_filter; // wchar→outcodeへのコンバート
	mbfl_convert_filter *conv2_filter_backup;
	mbfl_convert_filter *encod_filter; // outcode→transfer_enc(BorQP)へのコンバート 
	mbfl_convert_filter *encod_filter_backup;
	mbfl_memory_device outdev;
	mbfl_memory_device tmpdev;
	int status1;
	int status2;
	int prevpos;
	int linehead;
	int firstindent;
	int encnamelen;
	int lwsplen;
	char encname[128];
	char lwsp[16];
};

PHP_FUNCTION(mb_encode_mimeheader)string.no_encodingにカレントエンコーディングセット。

mbfl_mime_header_encode

mime_header_encoder_newtransfer block(Base64orQP),Output,encoded block,Input code blockの4つの変換の下準備

mbfl_mime_header_encodelinefeed,indent前処理

mime_header_encoder_newincode → outcode → transfer_encoding のメソッド名セット

戻り:mbfl_mime_header_encode順番にincode → outcode → transfer_encoding
解析用ソース抜き出し置き場
PHP_FUNCTIONmb_encode_mimeheader.ZIP

1: ELF 『よくきたblogな人ですが,私の件の記事は網羅性は高くないです. もっと細かいことを書いたものがありまして,「超・極める! PH...』 (2008/07/15 17:01)

2: spok 『ELFさん はじめまして。プロの方からコメントをいただけて光栄です。 私の記事が間違っていなかったようで安心しております。 さっ...』 (2008/07/17 24:22)

2008/01/22(火) Zend Studioでのデバックトレース

はてブ情報 はてブに登録 はてブ数 php5spok
ステップ実行に感激したものの、ステップオーバー、ステップイントゥ、ステップアウトの意味がよくわからない。調べると以下のよう。

  • ステップイントゥ:とにかく1つずつ実行。他の関数が呼び出されたら、当然、そちらに移る。細かいが時間がかかる。
  • ステップアウト:その関数のreturnまで一気に実行して、呼び出しもとに戻る。ステップイントゥでスしょうもない関数にまで入り込んでしまったら、これを使うと元の関数に戻れる。
  • テップオーバー:1つづ実行するが、他の関数が呼び出された場合には、そちらには一気に実行する。つまり呼び出した関数のステップ実行はしない。

続きを読む

2008/01/22(火) ZendStudio5.5買ってみた

はてブ情報 はてブに登録 はてブ数 php5spok
奮発してあこがれの統合環境、ZendStudio5.5を買った。
しかし、ZendPlatformがインストールできない。問い合わせたところ、
Zend Platform 2.2.2がphp 5.2.x系に対応してい
ないことが原因と考えられます。
だと。(当方の環境はphp5.2.3)
リモートデバッグができるというから買ったのに。。。。。はい、事前に調べない私が悪いです。

同じメールで
Zend Platformではなく、Zend Debuggerをインストールしていただくと
いう方法がございます。 
ということなので、Zend Debuggerを入れてみた。。。。。ら

ZendDebuggerすごいよ。ステップ実行に感激。これで十分です。

しかし、エディタがしょぼい。秀丸に使い慣れているとすこぶるよろしくない。
しばらくコーディングは、今までとおり、まめFile5+秀丸でいっておきましょう。会計士は保守的なのだ。