On 平 11 / 8 / 31 午後 9:08 
 Subject: [vcpp 00032031] Re: Base64の Decode方法
 In <1...@kded5.kde.daikin.co.jp>
 simatani = Keisuke SHIMATANI writes:
simatani>今晩は、嶋谷です。

遅くなりました。
今晩は,吉田です。

simatani>申し訳ないです。少し長くなりますが、MFC Programmer's Source Book の
simatani>CBase64 を掲載させていただきます。

ありがとうございます。


*BASE64について*

base64 エンコーディングは,0-63の数字を,

 大文字の A-Z, 小文字の a-z, 数字の0-9,+/
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

に割り当てて,バイナリファイルをアスキーへエンコーディングする方法です。
エンコードする一行が76文字となっています。うろ覚えですいませんが,76文字
で正しいのかどうかは,Encodeルーチンを参照して下さい。

simatani>// The size of the output buffer must not be less than
simatani>// 3/4 the size of the input buffer. For simplicity,
simatani>// make them the same size.

これは,以下のバイト数へデコードされるといった意味になります。

``ABCD"  -> (2^6)^4 = (2^8)^3 -> 3 bytes
``ABC="  -> 2 bytes
``AB=="  -> 1 bytes
 ``=" はパディングです。0とは意味的に異なります。
4の倍数にならない場合,base64エンコード時にパディングを使います。


*ふまえて,CBASE64について*

simatani> int nDecode[ 256 ];

nDecodeは,strchr(table, ch); では速度的に厳しいので変換
テーブルを持っているのでしょう。少なくとも,strchrよりも正
常に見えます。

simatani> ASSERT( szDecoding != NULL );
simatani> ASSERT( szOutput != NULL );

呼び出し元でメモリが足りない場合のチェックを怠っている等,
偶然 NULL ポインタになってしまっても,アクセス違反を起こ
さないコードになっていますね。

simatani> if( sInput.GetLength() == 0 )
simatani> return 0;

これは長さが0の行をスキップしています。

< SNIP >
simatani> nDecode[ m_sBase64Alphabet[ i ] ] = i;

テーブルの作成。

simatani> nDecode[ m_sBase64Alphabet[ i ] | 0x80 ] = i; // Ignore 8th bit

この部分は初めて知りました。

simatani> memset( szOutput, 0, sInput.GetLength() + 1 );

<後で説明>

simatani> if( nDigit < -1 ) 
simatani> {
simatani> return 0;

入力が正しく無い場合にはエラーとなるらしいですね。

simatani> else if( nDigit >= 0 ) 
simatani> // i (index into output) is incremented by write_bits()
simatani> write_bits( nDigit & 0x3F, 6, szOutput, i );

入力が正しい場合のみ,デコードしています。
write_bitsでは,カレントビット数をチェックしていますね。

simatani>行単位の処理の場合ですね。

CBase64では行単位でしか受け付けません。また,無関係な文字は受け付けま
せん。まっとうな方法ですが,支障がある場合もあるでしょう。ですが,
CStringの参照を引数にとったReadStringでは,"\r\n"を削除しますから,
なんら問題はありません。

*ふまえて,私の書いた利用方法のサンプルコードについて
  & 質問への私なりのお答え *

simatani>> CString    rcvbuf;  /* デコーダ出力 */
simatani>> CFile      output;  /* バイナリ出力 */
simatani>> int        b_len;
simatani>> char      *b_buf;

-- ここで入力ファイルと出力ファイルをオープンして下さい。

simatani>>   while( file.ReadString( &line ) ){
simatani>
simatani>ファイルの終わりまで達すると(戻り値NULLで)ループを抜けるのですね。

CStringを引数にとる,ReadStringでは,FALSEでファイルの終端になります。
NULLとは若干異なります。fgetsならばそうなのですけど。

EOFフラグを確認しても良いでしょう。
ファイルに,Ctrl+Z (0x1a)が含まれると,ファイルの終端として扱っている
様子なので,streamや,FileMapping,バイナリモードでの,peek, getcが必
要になる場合もあるかも知れません。今回のケースや,通常ではCStdioFileで
問題はありません。

simatani>GetLength() して、長さが0だったら再び制御式を評価するのですね。
simatani>break ではだめですか。

問題がなければそれでもいいです,
場合によっては,continue,
場合によっては,breakでは無いでしょうか?

CBase64の中でも,Length() == 0) return 0; としていますから,
if( b_len == 0 ) とした方が良いですね。なぜ,b_len == 0なのかを
チェックする手段が欲しいところですが。。
LOWORD,HIWORDで戻り値をマスクするのは格好が良くありませんので,
private:
   int m_errno; // 追加 Decode が return 0; の理由を調べる
public:
   int Fail() { return m_errno; }
としたいとも思います。

まぁ,普通はここまでする必要は無いのでしょう。

simatani>>     // Alloc 失敗のチェックを省略
simatani>>     b_buf = rcvbuf.GetBuffer( line.Length() );
simatani>
simatani>rcvbuf のバッファへポインタの取得ですか?
simatani>バッファのサイズもここで確保しているのですか?

バッファのサイズについては,memsetがありますので,line.Length() + 1
とする必要がありますね。

simatani>デコード結果が、b_buf に入るのですか?

そうです。返却値は,やはり,出力された長さであっています。

simatani>>     TRY {
simatani>
simatani>ここに、出力ファイルのオープンは、いりますか?

このループの一番そとです,前にオープンしています。

simatani>output.Close(); もいりますか?

これは最後です。

simatani>楽に済ませたいのですが、TRYを使えば何故楽になるのか分かりません。

int flag = 0;
if( !flag && !FuncA() ) flag = 1; // ファイルエラー
if( !flag && !FuncB() ) flag = 2; // メモリエラー
if( !flag && !FuncC() ) flag = 1; // ファイルエラー

if( flag ) { // エラーがおきました

といったコードを書く必要が無くなるのでは?と思っています。
親関数から呼び出した,子の関数の中のエラーをCATCHする事も可能です。

simatani>残念ながらソースを見ても分からないのです・・・。m(_ _)m

CBase64の場合には行単位(正しくは,意味的な単位ごと(パディング等,
正しい長さでエンコード・デコードを行える))に処理している様なので,
CStdioFileがやはり適当では無いでしょうか?

simatani>もう少しにらんでみます。

(^^;;
openの場所も分かりましたし,とりあえず実行して結果を見てはいかがで
せうか?

ではでは。

--- 
S.Yoshida v...@ha.bekkoame.ne.jp