今回のお題
先日引き受けた相談事は「メールを使ってデータベースにレコードを登録したい」とのこと。レコードを本文としたメールを投げて、そいつがデータベースに格納されるカラクリが欲しいってんですね。例えば売上データやらアンケート結果やら、あちこちで収集したデータをメールでかき集めるのが目的なのでしょう。「そんなもん、どこぞにWeb-serviceをホストしておいてHTTPで投げればいぃんじゃね?」と答えたところ、できるだけチープに実現したく、サーバ立てるのは避けたいそうな。
なるほど、そう言うことなら納得です。テキトーなメールアカウントを1つ用意し、みんなが寄ってたかってそいつめがけてメールを投げつけ、小さなアプリケーションがメールを読んでデータベースに流し込むってスンポーですか。「登録したいレコードって定型なの? どれも同じフォーマットなら実装楽なんだけど」と訊くと依頼主曰く「いや、ゆくゆくはいろんな目的に利用することになりそうなので、フォーマットはできるだけ潰しの利くものにしてほしい」と。「んー…それじゃXMLでやっちゃいましょうか」ってことになり、「POP3からメールを取り出し、そこに書かれたXMLを解釈してデータベースに流し込む」小さなお試しアプリケーションを作ることになりました。
「で、〆切はいつ?」「できれば明日までに」うひー…
POP3からメールを取り出す
まずはPOP3サーバーからメールを取り出すところから始めます。デキトーなメールアカウントに以下のようなメールを投げておきます。
しばらく待って届いた頃を見計らってtelnetでPOP3に繋いで読み込んでみます。
+OK Qpopper (version 4.0.9) at localhost starting. <9999.1345119383@localhost> USER episteme ■ +OK Password required for episteme. PASS abracadaburachichinnpuipui ■ +OK episteme has 3 visible messages (0 hidden) in 6481 octets. LIST ■ +OK 3 visible messages (6481 octets) 1 1451 2 2436 3 2594 . RETR 3 ■ +OK 2594 octets Return-Path: <episteme@cppll.jp> ...省略... X-UIDL: Do6"!I)=!!+0A"!0`P"! <?xml version='1.0' encoding='iso-2022-jp' ?> <words> <word en='dog' ja='B$$$LB' /> <word en='cat' ja='B$M$3B' /> </words> . DELE 3 ■ +OK Message 3 has been deleted. QUIT ■ +OK Pop server at localhost signing off.
"■"のついた行は僕が入力した部分です。POP3に繋がったら:
- USER <ユーザ名>とPASS <パスワード>を送信
- LISTで得られた各メールIDに対し
- RETR <メールID>でナカミを取り出し
- 必要ならDELE <メールID>で削除して
- QUITで抜ける
この手順を生socketでちまちま書いてもさほどの手間はかからんけども、いかんせん〆切が迫っているのでBoost.Asioで済ませます。必要なのはXMLだけですから、RETRで得られる文字列のうち"<?xml..."から"."直前までを切り出します。
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <iterator>
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
// Boost
#include <boost/asio.hpp>
using namespace std;
using namespace boost::asio;
/*
* モロモロの定数値
*/
// POP3 サーバのアドレスとポート
const string pop3_address = "mail.dokkanoserver.com";
const string pop3_port = "110";
// POP3 アカウントとパスワード
const string mail_user = "episteme";
const string mail_pass = "abracadabra";
inline bool starts_with(const string& str, const string& pattern) {
return str.find(pattern) == 0;
}
template<typename OutputIterator>
OutputIterator get_xml_from_pop3(const string& address, const string& port,
const string& user, const string& pass,
OutputIterator out) {
string line;
io_service io;
ip::tcp::iostream pop3(address,port);
vector<int> mail_id;
getline(pop3, line);
// USER
pop3 << "USER " << user << endl;
getline(pop3, line);
if ( !starts_with(line,"+OK") ) goto quit;
// PASS
pop3 << "PASS " << pass << endl;
getline(pop3, line);
if ( !starts_with(line,"+OK") ) goto quit;
// LIST
pop3 << "LIST" << endl;
getline(pop3,line);
if ( !starts_with(line,"+OK") ) goto quit;
// メールIDを読み取る
for (;;) {
getline(pop3,line);
if ( !line.empty() && line[0] == '.' ) break;
istringstream stream(line);
int id;
stream >> id;
mail_id.push_back(id);
}
// RETR <メールID> でメールを読む
for ( vector<int>::size_type i = 0; i < mail_id.size(); ++i ) {
pop3 << "RETR " << mail_id[i] << endl;
bool has_xml = false;
ostringstream stream;
// "<?xml..." から "."までを抽出
for (;;) {
getline(pop3, line);
if ( !line.empty() && line[0] == '.' ) break;
if ( line.find("<?xml") == 0 ) {
has_xml = true;
}
if ( has_xml ) stream << line << endl;
}
if ( has_xml ) {
*out++ = stream.str();
// DELE <メールID> で削除
pop3 << "DELE " << mail_id[i] << endl;
getline(pop3,line);
}
}
quit:
// QUIT
pop3 << "QUIT" << endl;
return out;
}
int main() {
vector<string> msgs;
get_xml_from_pop3(pop3_address, pop3_port, mail_user, mail_pass, back_inserter(msgs));
wcout << L"----------- I've got these XMLs ------------------" << endl;
for_each(begin(msgs), end(msgs), [](const string& body) { cout << body << endl; });
}

