第236章 base64をデコードする


今回は、base64でエンコードされた文字列をS-JISに戻して読めるようにします。 実はメールのサブジェクトは「=?ISO-2022-JP?B?.........?=」が1組だけではなく 複数組入っていることがあります。1バイト文字と2バイト文字が混在していたりすると 複数組存在することがあります。混在していても1組のこともあります。これが、プログラムを 難しくします。



「=?ISO-2022-JP?B?...?=」が2組含まれているサブジェクトを デコードしたところです。



では、どのようにプログラムを書けばよいのでしょうか。

まず、「=?IDO-2022-JP?B?......?=」の....部分を取り出せたとして、これを デコードする方法を考えましょう。

まず......部分には「A-Z, a-z, 0-9, +, /」と字あわせのための「=」しか 含まれていないはずです。これを数値になおします。

もしA-Zならば数値は0x00-0x19となります。 (第235章の表参照)これは変換する文字の アスキーコードからAのアスキーコードを引いてやればよいことになります。
(数値に変換する文字がAなら0, Bなら1, Cなら2,....)

次にa-zなら変換後は0x1a-0x33となります。ということは変換する文字のアスキーコードから aのアスキーコードを引いて0x1aをたせばよいことになります。

もし0-9ならば変換後は0x34-0x3dとなります。 従って変換する文字のアスキーコードから0のアスキーコードを引いて 0x34をたせばよいことがわかります。

変換する文字が+なら0x3eに/ならば0x3fに変換します。

また、数あわせのための「=」が来ることがありますが、これは'\0'に してしまえばよいです。

従って数値に変換する関数は次のような感じになります。

char charconv(char c) { char str[256]; if (c >= 'A' && c <= 'Z') return (c - 'A'); if (c >= 'a' && c <= 'z') return (c - 'a' + 0x1a); if (c >= '0' && c <= '9') return (c - '0' + 0x34); if (c == '+') return 0x3e; if (c == '/') return 0x3f; if (c == '=') return '\0'; wsprintf(str, "不正な文字[%c]を検出しました", c); MessageBox(NULL, str, "Error", MB_OK); return '\0'; }

次に、数値に変換されたものをつなぎ合わせて?前から8ビットずつに 区切りなおします。char型の変数a1, a2, a3, a4にそれぞれ変換された数値が 入っているものとします。char型は1バイト(8ビット)です。 それぞれには6ビットまでしか使っていません。上位2ビットは0です。 たとえばa1, a2, a3, a4を2進で表して

a1=(00110010) a2=(00011110) a3=(00101010) a4=(00110011)

だったとします。これから8ビットの組3つを生成します。 これをX[0], X[1], X[2]とすると
X[0]はa1を左に2ビットシフトさせてa2を右に4つシフトさせたものを たせばよいことになります。

a1 << 2 .....(11001000) a2 >> 4.......(00000001) たすと........(11001001)

同じようにX[1]はa2を4つ左にシフトさせa3を2つ右シフトさせてたします。

X[2]はa3を左に6つシフトさせa4を足します。

実はこのような演算はSDKでプログラムを書いている人は 無意識のうちに毎回のように行っているのです。(マクロとして)

さて、これを関数にすると

void decode(char *lpszStr, char *lpszResult) { int len, i, iR; char a1, a2, a3, a4; len = strlen(lpszStr); i = 0; iR = 0; while (1) { if (i >= len) break; a1 = charconv(lpszStr[i]); a2 = charconv(lpszStr[i+1]); a3 = charconv(lpszStr[i+2]); a4 = charconv(lpszStr[i+3]); lpszResult[iR] = (a1 << 2) + (a2 >>4); lpszResult[iR + 1] = (a2 << 4) + (a3 >>2); lpszResult[iR + 2] = (a3 << 6) + a4; i += 4; iR += 3; } return; }

とこんな感じになります。lpszStrにエンコードされた文字列、 lpszResultにはデコードされたコードを格納するバッファのアドレスです。 lpszResultをS-JISに変換してやれば日本語として読めるようになります。

ここまでは、比較的簡単にたどり着けます。ところが、簡単なようで なかなか実現するのが難しいのが「=?ISO-2022-JP?B?.......?=」 から....を調べるところです。これが1組だけなら簡単です。ところが これが複数組存在してなおかつこの組の間に1バイト文字が存在すると どうでしょうか。

筆者は最初次のように考えました。

文字列を「?」でトークンに切り分けます。
トークンが「ISO-2022-JP」で次のトークンが「B」ならば その次のトークンが........部分である、その次のトークンは「=」 なので無視。

これでもやってできないことはありませんが、場合分けが かなり複雑になります。(筆者は正味20時間以上かけて作った!)

そこで次に考えたのが「=」をデリミッタにしてトークンに切り分ける方法です。
トークンの先頭に「?ISO-2022-JP?B?」があれば、そのトークン の15バイト目からがデコードすべき文字列となります。

また、.......部分の文字あわせのために末尾に「=」があると (.....==?=)「?」だけのトークンが出現します。

このへんを念頭に置いて関数化すると

void GetString(char *szOrg, char *szResult) { char *token; char szBuf[1024], szBuf2[1024]/*, szBuf3[1024]*/; int len, mod; BOOL bCopy = TRUE; //?をコピーしてよいか token = strtok(szOrg, "=\r\n\t"); while (token != NULL) { if (strstr(token, "?ISO-2022-JP?B?") != token && strstr(token, "?iso-2022-jp?B?") != token) { if (strcmp(token, "?") != 0 && bCopy == TRUE) { MyJisToSJis(token, szBuf); strcat(szResult, szBuf); } } else { strcpy(szBuf, token + 15); if (szBuf[strlen(szBuf) - 1] == '?') szBuf[strlen(szBuf) - 1] = '='; len = strlen(szBuf); mod = len % 4; switch (mod) { case 1: strcat(szBuf, "==="); bCopy = FALSE; break; case 2: strcat(szBuf, "=="); bCopy = FALSE; break; case 3: strcat(szBuf, "="); bCopy = FALSE; break; } decode(szBuf, szBuf2); MyJisToSJis(szBuf2, szBuf); strcat(szResult, szBuf); } bCopy = TRUE; token = strtok(NULL, "=\r\n\t"); } return; }

こんな感じになります。また、......部分の末尾が「?」の時は これを文字あわせの「=」に変えてやります。この上で文字列の長さを 調べて4の倍数になっていないときは「=」を補充して4の倍数とします。 あとは、decode関数でデコードして、MyJisToSJis関数でS-JISに変換します。 これをszResultに継ぎ足してやります。

また、トークンに「?ISO-2022-JP?B?」が含まれていないときは、 そのままszResultに継ぎ足せばよいように思われるかもしれません。 しかし、中にはサブジェクトにいきなりJISコードを書いてくる メーラーもあります。それで一応S-JISに変換してから継ぎ足します。

これで99%完成です。100%でないのは、サブジェクトに「==件名です==」のように 1バイト文字の「=」が含まれている場合、これを無視して「件名です」となってしまいます。 さて、これを修正するにはどうしたらよいでしょうか。いろいろ試してみてください。

では、今回のプログラムを見てみましょう。

// base6401.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)...", IDM_END END POPUP "オプション(&O)" BEGIN MENUITEM "BASE64デコード(&D)...", IDM_DECODE END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYDLG DIALOG DISCARDABLE 0, 0, 201, 135 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "BASE64デコード" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,7,7,187,55,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL PUSHBUTTON "デコード",IDC_BUTTON1,72,114,50,14 DEFPUSHBUTTON "クリア",IDOK,15,114,50,14 PUSHBUTTON "閉じる",IDCANCEL,129,114,50,14 EDITTEXT IDC_EDIT2,7,65,187,38,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL END

ごく普通のメニューとダイアログのリソース・スクリプトです。

// base6401.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include <mbstring.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDecodeProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void decode(char *, char *); char charconv(char); void MyJisToSJis(char *, char *); void GetString(char *, char *); char szClassName[] = "base6401"; //ウィンドウクラス HINSTANCE hInst; int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;//インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるbase64", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 300, //幅 200, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } //ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_DECODE: DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDecodeProc); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; } LRESULT CALLBACK MyDecodeProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static HWND hEdit1, hEdit2; int len; HGLOBAL hMem, hMem2; char *lpszOrg, *lpszResult; switch (msg) { case WM_INITDIALOG: hEdit1 = GetDlgItem(hDlg, IDC_EDIT1); hEdit2 = GetDlgItem(hDlg, IDC_EDIT2); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; case IDOK: Edit_SetText(hEdit1, ""); Edit_SetText(hEdit2, ""); return TRUE; case IDC_BUTTON1: len = GetWindowTextLength(hEdit1); hMem = GlobalAlloc(GHND, len + 1); lpszOrg = (char *)GlobalLock(hMem); hMem2 = GlobalAlloc(GHND, len + 1); lpszResult = (char *)GlobalLock(hMem2); Edit_GetText(hEdit1, lpszOrg, len + 1); GetString(lpszOrg, lpszResult); Edit_SetText(hEdit2, lpszResult); GlobalUnlock(hMem2); GlobalFree(hMem2); GlobalUnlock(hMem); GlobalFree(hMem); return TRUE; } return FALSE; } return FALSE; } void decode(char *lpszStr, char *lpszResult) { int len, i, iR; char a1, a2, a3, a4; len = strlen(lpszStr); i = 0; iR = 0; while (1) { if (i >= len) break; a1 = charconv(lpszStr[i]); a2 = charconv(lpszStr[i+1]); a3 = charconv(lpszStr[i+2]); a4 = charconv(lpszStr[i+3]); lpszResult[iR] = (a1 << 2) + (a2 >>4); lpszResult[iR + 1] = (a2 << 4) + (a3 >>2); lpszResult[iR + 2] = (a3 << 6) + a4; i += 4; iR += 3; } return; } char charconv(char c) { char str[256]; if (c >= 'A' && c <= 'Z') return (c - 'A'); if (c >= 'a' && c <= 'z') return (c - 'a' + 0x1a); if (c >= '0' && c <= '9') return (c - '0' + 0x34); if (c == '+') return 0x3e; if (c == '/') return 0x3f; if (c == '=') return '\0'; wsprintf(str, "不正な文字[%c]を検出しました", c); MessageBox(NULL, str, "Error", MB_OK); return '\0'; } void MyJisToSJis(char *lpOrg, char *lpDest) { int i = 0, iR = 0, c; BOOL bSTART = FALSE;//JIS->SJIS変換が必要かどうか while (1) { if (lpOrg[i] == 0x1b && lpOrg[i + 1] == 0x24 && lpOrg[i + 2] == 0x42 || lpOrg[i] == 0x1b && lpOrg[i + 1] == 0x24 && lpOrg[i + 2] == 0x40) { bSTART = TRUE; i += 3; continue; } if (lpOrg[i] == 0x1b && lpOrg[i + 1] == 0x28 && lpOrg[i + 2] == 0x42 || lpOrg[i] == 0x1b && lpOrg[i + 1] == 0x28 && lpOrg[i + 2] == 0x4a) { bSTART = FALSE; i += 3; continue; } if (lpOrg[i] >= 0x21 && lpOrg[i] <= 0x7e && bSTART) { c = MAKEWORD(lpOrg[i + 1], lpOrg[i]); lpDest[iR] = (char)HIBYTE(_mbcjistojms(c)); lpDest[iR + 1] = (char)LOBYTE(_mbcjistojms(c)); i += 2; iR += 2; continue; } if (i == 0 && lpOrg[0] == '\n') { lpDest[iR] = '\r'; lpDest[iR + 1] = '\n'; i++; iR += 2; continue; } if (lpOrg[i] == '\n' && lpOrg[i - 1] != '\r') { lpDest[iR] = '\r'; lpDest[iR + 1] = '\n'; i++; iR += 2; continue; } if (lpOrg[i] == '=') { i++; continue; } if (lpOrg[i] == '\0') { lpDest[iR] = lpOrg[i]; break; } lpDest[iR] = lpOrg[i]; iR++; i++; } return; } void GetString(char *szOrg, char *szResult) { char *token; char szBuf[1024], szBuf2[1024]; int len, mod; BOOL bCopy = TRUE; //?をコピーしてよいか token = strtok(szOrg, "=\r\n\t"); while (token != NULL) { if (strstr(token, "?ISO-2022-JP?B?") != token && strstr(token, "?iso-2022-jp?B?") != token) { if (strcmp(token, "?") != 0 && bCopy == TRUE) { MyJisToSJis(token, szBuf); strcat(szResult, szBuf); } } else { strcpy(szBuf, token + 15); if (szBuf[strlen(szBuf) - 1] == '?') szBuf[strlen(szBuf) - 1] = '='; len = strlen(szBuf); mod = len % 4; switch (mod) { case 1: strcat(szBuf, "==="); bCopy = FALSE; break; case 2: strcat(szBuf, "=="); bCopy = FALSE; break; case 3: strcat(szBuf, "="); bCopy = FALSE; break; } decode(szBuf, szBuf2); MyJisToSJis(szBuf2, szBuf); strcat(szResult, szBuf); } bCopy = TRUE; token = strtok(NULL, "=\r\n\t"); } return; }

関数の説明のところでも書きましたが、サブジェクトに「=」が入っていても きちんと処理してくれるように工夫してみてください。
[SDK第3部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 25/Oct/1999 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。