メールの送信時に「添付」ボタンを押すと「ファイルを開く」ダイアログ
ボックスが出てきますので、これでテキストファイルを選択します。
今回のプログラムでは、一回の送信で1つのファイルしか添付できません。
また、都合上50キロを超えるファイルは添付できません。
では、メールにファイルを添付するにはどうすればよいのでしょうか。
実際に送られてきたメールを観察するとかなりのことがわかってきます。
ネットスケープについてくるメーラーなどは簡単にメールのソース
(原文)が見られるので便利です。
メールのヘッダに
をいれてこのメールがmultipartであることを示します。 *******の部分は絶対に偶然これと同じ文字列が現れないような 文字列を指定します。メーラーを観察すると送信のたびにこの文字列が 変わっているようです。これが区切りとなります。MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="********"
本文中に
というような感じで複数のファイルを本文中に埋め込みます。 区切り文字列の前に「-」記号を2つつけます。 一番最後は区切り文字列の最後にも「-」を2つつけます。空行 --区切り文字列 Content-Type: 内容のタイプ; name="ファイル名" 付加的説明 添付するファイル 空行 内容 --区切り文字列 Content-Type: 内容のタイプ; name="ファイル名" 付加的説明 空行 内容 --区切り文字列--
テキストファイルを送信する場合、メーラーによっては テキストファイルの内容を本文中に展開してしまうものもあります。 これがいやなときは付加的説明に
を付け加えます。これを省略するとattachmentがinlineに解釈されるようです。Content-Disposition: attachment; filename="ファイル名"
また、テキストファイルの場合は内容をそのまま書き込むものと base64でエンコードして書き込むものがあるようです。
テキストファイル以外のものはbase64などでエンコードして書き込むのが 一般的です。base64でエンコードしてある場合は
を付け加えます。Content-Transfer-Encoding: base64
では、プログラムを見てみましょう。
新規メールのダイアログボックスに「添付」ボタンと、添付メールの ファイル名を示すエディットコントロールが増えました。// mail07.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "送信(&S)...", IDM_SEND MENUITEM "受信(&R)...", IDM_RCV MENUITEM SEPARATOR MENUITEM "終了(&X)...", IDM_END END POPUP "オプション(&O)" BEGIN MENUITEM "SMTPの設定(&S)...", IDM_SETSMTP MENUITEM "POP3の設定(&P)...", IDM_SETPOP END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYDLG DIALOG DISCARDABLE 0, 0, 187, 53 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "文字列入力" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,7,7,173,20,ES_AUTOHSCROLL DEFPUSHBUTTON "OK",IDOK,7,32,50,14 PUSHBUTTON "キャンセル",IDCANCEL,130,32,50,14 END MYSETDLG DIALOG DISCARDABLE 0, 0, 187, 89 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "設定" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,78,7,102,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,78,25,102,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT3,78,43,102,12,ES_AUTOHSCROLL DEFPUSHBUTTON "OK",IDOK,27,68,50,14 PUSHBUTTON "キャンセル",IDCANCEL,103,68,50,14 LTEXT "SMTPアドレス",IDC_STATIC,7,7,41,8 LTEXT "差出人メールアドレス",IDC_STATIC,7,29,63,8 LTEXT "返信",IDC_STATIC,7,47,15,8 END MYPOPDLG DIALOG DISCARDABLE 0, 0, 158, 77 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "POP3設定" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,53,7,98,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,53,22,98,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT3,53,37,98,12,ES_PASSWORD | ES_AUTOHSCROLL DEFPUSHBUTTON "OK",IDOK,7,56,50,14 PUSHBUTTON "キャンセル",IDCANCEL,101,56,50,14 LTEXT "POP3サーバー",IDC_STATIC,7,11,44,8 LTEXT "ユーザー名",IDC_STATIC,7,26,35,8 LTEXT "パスワード",IDC_STATIC,7,41,32,8 END MYNEWMAIL DIALOG DISCARDABLE 0, 0, 187, 215 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "新規メール" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,43,7,137,13,ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,43,25,137,13,ES_AUTOHSCROLL EDITTEXT IDC_EDIT3,7,47,173,110,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL DEFPUSHBUTTON "送信",IDOK,7,194,50,14 PUSHBUTTON "取り消し",IDCANCEL,130,194,50,14 LTEXT "あて先",IDC_STATIC,7,7,20,8 LTEXT "件名",IDC_STATIC,7,25,15,8 PUSHBUTTON "添付",IDC_ATTACH,7,169,50,14 EDITTEXT IDC_EDIT4,62,169,118,14,ES_AUTOHSCROLL END
コモンダイアログ(「ファイルを開く」ダイアログ)を使う場合 WIN32_LEAN_AND_MEANを定義してあるのでcommdlg.hを インクルードするのを忘れないでください。// mail07.cpp #ifndef STRICT #define STRICT #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #define ID_EDIT 100 #define ID_STATUS 101 #include <windows.h> #include <winsock2.h> #include <windowsx.h> #include <commctrl.h> #include <stdio.h> #include <stdlib.h> #include <mbstring.h> #include <commdlg.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MySettingProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyPopSetProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyNewMailProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void MySnd(HWND, char *, char *, char *, char *, char *); void MyRcv(HWND, HWND, HWND, HWND); int GetMailSize(char *); void MyJisToSJis(char *, char *); void MySJisToJis(char *, char *); void GetSubject(char *, char *); void GetFrom(char *, char *); void GetDate(char *, char *); void GetString(char *, char *); void decode(char *, char *); char charconv(char); void InsertColumn(HWND); void InsertItem(HWND, int, int, char *); void ShowContents(HWND, HWND, HWND, int); void MyPopConnect(HWND, HWND); void MyPopDisconnect(HWND); void encode(char *, char *, BOOL); char convtobase(char); void SelectAttachFile(HWND, char *, char *); char szClassName[] = "mail07"; //ウィンドウクラス char szStr[1024], szStrRcv[1024 * 50], szResult[1024 * 50]; char szServerName[256], szFrom[256], szReplyTo[256];//SMTP char szPopServer[256], szUserName[64], szPass[64];//POP3 HINSTANCE hInst; SOCKET s; //ソケット(POP3用) BOOL bPOP = NULL; //POP3接続中かどうか
MySnd関数の引数がふたつ増えました。
encode関数の引数がひとつ増えました。最後の引数を FALSEにすると出力に「=?ISO-2022-JP?B?」「?=」を付加しません。 今回のプログラムでは、直接関係ありませんが将来、ファイルを base64エンコードして添付するときの備えです。
WinMain, InitApp, InitInstance, WndProcの各関数に変更はありません。
ちょっとだらだらと長くなってしまいました。最初の説明を読めば 添付のところはわかると思います。蛇足ですがダブルクォーテーション自身は 「\"」であらわします。void MySnd(HWND hStatus, char *lpszTo, char *lpszSubject, char *lpszMail, char *lpszFile, char *lpszTitle) { WSADATA wsaData; LPHOSTENT lpHost; LPSERVENT lpServ; SOCKET s; int iProtocolPort; SOCKADDR_IN sockadd; char *seps = "\r", *token; BOOL bAttach; HANDLE hFile; DWORD dwFSize, dwReadSize; HGLOBAL hMem; char *lpszBuf; if (strcmp(lpszFile, "") == 0) bAttach = FALSE; else bAttach = TRUE; if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) { MessageBox(NULL, "エラーです", "Error", MB_OK); return; } if (strcmp(szServerName, "") == 0) { MessageBox(NULL, "サーバーアドレスを入力してください。", "サーバー", MB_OK); DialogBox(hInst, "MYDLG", hStatus, (DLGPROC)MyDlgProc); strcpy(szServerName, szStr); } lpHost = gethostbyname(szServerName); if (lpHost == NULL) { wsprintf(szStr, "%sが見つかりません", szServerName); MessageBox(NULL, szStr, "Error", MB_OK); return; } s = socket(PF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { MessageBox(NULL, "ソケットをオープンできません", "Error", MB_OK); return; } lpServ = getservbyname("mail", NULL); if (lpServ == NULL) { MessageBox(NULL, "ポート指定がされていないので、デフォルトを使います", "OK", MB_OK); iProtocolPort = htons(IPPORT_SMTP); } else { iProtocolPort = lpServ->s_port; } sockadd.sin_family = AF_INET; sockadd.sin_port = iProtocolPort; sockadd.sin_addr = *((LPIN_ADDR)*lpHost->h_addr_list); if (connect(s, (PSOCKADDR)&sockadd, sizeof(sockadd))) { MessageBox(NULL, "サーバーソケットに接続失敗", "Error", MB_OK); return; } memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); SendMessage(hStatus, SB_SETTEXT, 0 | 0, (LPARAM)szStrRcv); strcpy(szStr, "HELO "); strcat(szStr, szServerName); strcat(szStr, "\r\n"); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); SendMessage(hStatus, SB_SETTEXT, 0 | 0, (LPARAM)szStrRcv); if (strcmp(szFrom, "") == 0) { MessageBox(NULL, "差出人メールアドレスを入力してください", "差出人", MB_OK); DialogBox(hInst, "MYDLG", hStatus, (DLGPROC)MyDlgProc); strcpy(szFrom, szStr); } wsprintf(szStr, "MAIL FROM : <%s>\r\n", szFrom); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); SendMessage(hStatus, SB_SETTEXT, 0 | 0, (LPARAM)szStrRcv); wsprintf(szStr, "RCPT TO :<%s>\r\n", lpszTo); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); SendMessage(hStatus, SB_SETTEXT, 0 | 0, (LPARAM)szStrRcv); strcpy(szStr, "DATA\r\n"); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); SendMessage(hStatus, SB_SETTEXT, 0 | 0, (LPARAM)szStrRcv); strcpy(szStr, "X-Mailer: Nekodemo_Wakaru-Mailer\r\n"); send(s, szStr, strlen(szStr), 0); if (strcmp(szReplyTo, "") != 0) { wsprintf(szStr, "Reply-To: %s\r\n", szReplyTo); send(s, szStr, strlen(szStr), 0); } MySJisToJis(lpszSubject, szStr); encode(szStr, lpszSubject, TRUE); wsprintf(szStr, "Subject: %s \r\n", lpszSubject); send(s, szStr, strlen(szStr), 0); if (bAttach) { strcpy(szStr, "MIME-VERSION: 1.0\r\n"); send(s, szStr, strlen(szStr), 0); strcpy(szStr, "Content-Type: multipart/mixed; boundary=\"****KUMEI\"\r\n"); send(s, szStr, strlen(szStr), 0); strcpy(szStr, "--****KUMEI\r\n"); send(s, szStr, strlen(szStr), 0); strcpy(szStr, "Content-Type: text/plain; charset=ISO-2022-JP\r\n"); send(s, szStr, strlen(szStr), 0); } //ここでもう一度「\r\n」を送信しておかないと //本文に日本語を使う場合1行目が文字化けします strcpy(szStr, "\r\n"); send(s, szStr, strlen(szStr), 0); token = strtok(lpszMail, seps); while (token != NULL) { if (token[0], '\n') { strcpy(szStr, token + 1); } else { strcpy(szStr, token); } strcat(szStr, "\r\n"); send(s, szStr, strlen(szStr), 0); token = strtok(NULL, seps); } if (bAttach) { strcpy(szStr, "--****KUMEI\r\n"); send(s, szStr, strlen(szStr), 0); wsprintf(szStr, "Content-Type: text/plain; name=\"%s\"\r\n", lpszTitle); send(s, szStr, strlen(szStr), 0); wsprintf(szStr, "Content-Disposition: attachment; filename=\"%s\"\r\n", lpszTitle); send(s, szStr, strlen(szStr), 0); strcpy(szStr, "\r\n"); send(s, szStr, strlen(szStr), 0); hFile = CreateFile(lpszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); dwFSize = GetFileSize(hFile, NULL); hMem = GlobalAlloc(GHND, dwFSize + 1024); lpszBuf = (char *)GlobalLock(hMem); ReadFile(hFile, lpszBuf, dwFSize, &dwReadSize, NULL); strcat(lpszBuf, "\r\n"); send(s, lpszBuf, strlen(lpszBuf), 0); strcpy(szStr, "--****KUMEI--\r\n"); send(s, szStr, strlen(szStr), 0); CloseHandle(hFile); GlobalUnlock(hMem); GlobalFree(hMem); } strcpy(szStr, ".\r\n"); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); SendMessage(hStatus, SB_SETTEXT, 0 | 0, (LPARAM)szStrRcv); strcpy(szStr, "QUIT\r\n"); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); SendMessage(hStatus, SB_SETTEXT, 0 | 0, (LPARAM)szStrRcv); closesocket(s); WSACleanup(); SendMessage(hStatus, SB_SETTEXT, 0 | 0, (LPARAM)"通信を終了しました"); bAttach = FALSE; return; }
最後のふたつの引数は、添付されるファイルの名前です。(フルパス付とファイル名のみ)
MyDlgProc, MySettingProc, MyPopSetProc, MyRcv, GetMailSize, MyJisToSJis の各関数に変更はありません。
「添付」ボタンが押されたときSelectAttachFile関数(後述)を呼んで ファイル名をhAttachに表示しています。LRESULT CALLBACK MyNewMailProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static HWND hTo, hSubject, hMail, hStatus, hAttach; char szTo[256], szSubject[256], *lpszMail, *lpszResult; static char lpstrFile[MAX_PATH], lpstrTitle[MAX_PATH]; int iLen; HGLOBAL hMem, hResult; switch (msg) { case WM_INITDIALOG: hTo = GetDlgItem(hDlg, IDC_EDIT1); hSubject = GetDlgItem(hDlg, IDC_EDIT2); hMail = GetDlgItem(hDlg, IDC_EDIT3); hAttach = GetDlgItem(hDlg, IDC_EDIT4); hStatus = (HWND)lp; return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hTo, szTo, sizeof(szTo)); Edit_GetText(hSubject, szSubject, sizeof(szSubject)); iLen = Edit_GetTextLength(hMail) + 1; hMem = GlobalAlloc(GHND, iLen); hResult = GlobalAlloc(GHND, iLen + 1024); if (hMem == NULL || hResult == NULL) { MessageBox(hDlg, "メモリの確保に失敗しました", "Error", MB_OK); return FALSE; } lpszMail = (char *)GlobalLock(hMem); lpszResult = (char *)GlobalLock(hResult); Edit_GetText(hMail, lpszMail, iLen); MySJisToJis(lpszMail, lpszResult); MySnd(hStatus, szTo, szSubject, lpszResult, lpstrFile, lpstrTitle); GlobalUnlock(hMem); GlobalFree(hMem); GlobalUnlock(hResult); GlobalFree(hResult); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; case IDC_ATTACH: SelectAttachFile(hDlg, lpstrFile, lpstrTitle); Edit_SetText(hAttach, lpstrTitle); break; } return FALSE; } return FALSE; }
MySJisToJis, GetSubject, GetFrom, GetDate, GetString, decode, charconv, InsertColumn, InsertItem, ShowContents, MyPopConnect, MyPopDisconnect の各関数に変更はありません。
bHeadがFALSEの時は「=?ISO-2022-JP?B?」「?=」を付加しません。void encode(char *lpszOrg, char *lpszDest, BOOL bHead) { int i = 0, iR = 0; if (bHead) { strcpy(lpszDest, "=?ISO-2022-JP?B?"); iR = 16; } while (1) { if (lpszOrg[i] == '\0') { break; } lpszDest[iR] = convtobase((lpszOrg[i]) >> 2); if (lpszOrg[i + 1] == '\0') { lpszDest[iR + 1] = convtobase(((lpszOrg[i] & 0x3) << 4) ); lpszDest[iR + 2] = '='; lpszDest[iR + 3] = '='; lpszDest[iR + 4] = '\0'; break; } lpszDest[iR + 1] = convtobase(((lpszOrg[i] & 0x3) << 4) + ((lpszOrg[i + 1]) >> 4)); if (lpszOrg[i + 2] == '\0') { lpszDest[iR + 2] = convtobase((lpszOrg[i + 1] & 0xf) << 2); lpszDest[iR + 3] = '='; lpszDest[iR + 4] = '\0'; break; } lpszDest[iR + 2] = convtobase(((lpszOrg[i + 1] & 0xf) << 2) + ((lpszOrg[i + 2]) >> 6)); lpszDest[iR + 3] = convtobase(lpszOrg[i + 2] & 0x3f); lpszDest[iR + 4] = '\0'; i += 3; iR += 4; } if (bHead) strcat(lpszDest, "?="); return; }
convtobase関数に変更はありません。
「ファイルを開く」ダイアログで添付ファイルを選択する関数です。void SelectAttachFile(HWND hDlg, char *lpstrFile, char *lpstrTitle) { OPENFILENAME ofn; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hDlg; ofn.lpstrFilter = "テキスト(*.txt)\0*.txt\0全て(*.*)\0*.*\0\0"; ofn.lpstrFile = lpstrFile; ofn.lpstrFileTitle = lpstrTitle; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrDefExt = "txt"; ofn.lpstrTitle = "添付ファイルの選択"; ofn.nMaxFileTitle = MAX_PATH; GetOpenFileName(&ofn); return; }
メールソフト作りはウィンドウズのプログラム作りというよりは、むしろ 文字列との戦い?のプログラムですね。
Update 17/Nov/1999 By Y.Kumei