mogmail - メイン処理
・pop3.h
1: // Copyright (c) 2011 Mog Project. All rights reserved.
2:
3: #ifndef _MOG_MAIL_POP3_H_
4: #define _MOG_MAIL_POP3_H_
5: #include <string>
6: #include <boost/scoped_ptr.hpp>
7: #pragma warning(disable : 4244)
8: #include <boost/date_time/posix_time/posix_time.hpp>
9: #pragma warning(default : 4244)
10:
11: // Forward declaration.
12: namespace mog {
13: namespace net { class Connecter; }
14: namespace mail {
15:
16: // Mail headers struct.
17: struct MailHeader {
18: boost::posix_time::ptime date; // Universal Time, Coordinated
19: std::string from_name;
20: std::string from_address;
21: std::string subject;
22: };
23:
24: // A class manages POP3 communication.
25: class POP3Client {
26: public:
27: POP3Client(
28: std::string const& server, std::string const& port,
29: std::string const& username, std::string const& password);
30: ~POP3Client();
31: MailHeader GetHeader(int index);
32: int num_mails() const { return num_mails_; }
33:
34: private:
35: boost::scoped_ptr<net::Connecter> connecter_;
36: bool is_active_;
37: int num_mails_;
38:
39: void Disconnect();
40: };
41:
42: } // namespace mail
43: } // namespace mog
44: #endif // _MOG_MAIL_POP3_H_
・pop3.cc
1: // Copyright (c) 2011 Mog Project. All rights reserved.
2:
3: #include "pop3.h"
4:
5: #include <boost/xpressive/xpressive.hpp>
6: #include <boost/foreach.hpp>
7: #include <boost/system/system_error.hpp>
8: #include <boost/algorithm/string.hpp>
9:
10: #include "base64.h"
11: #include "character_converter.h"
12: #include "connecter.h"
13:
14: namespace xp = boost::xpressive;
15:
16: namespace mog {
17: namespace mail {
18:
19: // Decoding MIME.
20: std::string DecodeString(std::string str) {
21: xp::sregex const regex = "=?"
22: >> (xp::s1=+((xp::set='=', '+', '-', '/')|xp::alpha|xp::digit)) >> '?'
23: >> (xp::s2=+((xp::set='=', '+', '-', '/')|xp::alpha|xp::digit)) >> '?'
24: >> (xp::s3=+((xp::set='=', '+', '-', '/')|xp::alpha|xp::digit)) >> "?=";
25: xp::smatch match;
26: while (xp::regex_search(str, match, regex)) {
27: // Supportecd only ISO-2022-JP and Base64
28: if (!boost::iequals(std::string("ISO-2022-JP"), match[1].str())) { throw std::runtime_error("Character-set is supported only ISO-2022-JP."); }
29: if (!boost::iequals(std::string("B"), match[2].str())) { throw std::runtime_error("Encoding method is supported only BASE64."); }
30: str = match.prefix() +
31: mog::util::CharacterConverter::JIStoSJIS(mog::util::Base64::Decode(match[3])) +
32: match.suffix();
33: }
34: return str;
35: }
36:
37: ////////////////////////////////////////////////////////////////////////////////
38: // POP3Client
39: POP3Client::POP3Client(
40: std::string const& server, std::string const& port,
41: std::string const& username, std::string const& password)
42: : is_active_(false), num_mails_(0) {
43:
44: std::string line;
45: xp::sregex const reg_ok = xp::bos >> "+OK " >> (xp::s1=*~xp::_s) >> *xp::_;
46: xp::smatch match;
47:
48: try {
49: // Create session.
50: connecter_.reset(new ::mog::net::Connecter(server, port));
51: connecter_->ReadLine(&line);
52: if (!regex_match(line, reg_ok)) { throw std::runtime_error("Failed to create session."); }
53:
54: connecter_->WriteLine("USER " + username);
55: connecter_->ReadLine(&line);
56: if (!regex_match(line, reg_ok)) { throw std::runtime_error("Failed to user authentication."); }
57:
58: connecter_->WriteLine("PASS " + password);
59: connecter_->ReadLine(&line);
60: if (!regex_match(line, reg_ok)) { throw std::runtime_error("Failed to password authentication."); }
61:
62: connecter_->WriteLine("STAT");
63: connecter_->ReadLine(&line);
64: if (!regex_match(line, match, reg_ok)) { throw std::runtime_error("Failed to get status."); }
65: num_mails_ = boost::lexical_cast<unsigned int>(match[1]);
66: } catch(boost::system::system_error const& e) {
67: Disconnect();
68: throw e;
69: }
70:
71: is_active_ = true;
72: }
73:
74: POP3Client::~POP3Client() throw() { Disconnect(); }
75:
76: MailHeader POP3Client::GetHeader(int index) {
77: MailHeader mh;
78:
79: try {
80: bool in_data = false;
81: std::string sect_title, sect_data;
82: connecter_->WriteLine("TOP " + boost::lexical_cast< std::string >(index) + " 0");
83:
84: for (;;) {
85: std::string line;
86: xp::smatch match;
87:
88: connecter_->ReadLine(&line);
89:
90: if (in_data) {
91: if (!line.empty() && std::isspace(line[0])) {
92: sect_data.append(line.substr(1));
93: continue; // Data in multi-lines.
94: }
95: if ("Date" == sect_title) {
96: // s1:day(xx), s2:month(English), s3:year(xxxx), s4:hour(xx), s5:minute(xx), s6:second(xx)
97: // s7:sign of time zone(+-), s8:time zone hour(xx), s9:time zone minute(xx)
98: // Ex. Thu, 13 Jan 2011 17:16:37 +0900
99: if (xp::regex_match(sect_data, match, xp::sregex(
100: +xp::alpha >> ", " >> (xp::s1=+xp::_d) >> ' ' >> (xp::s2 = +xp::alpha) >> ' ' >> (xp::s3 = +xp::_d) >> ' ' >>
101: (xp::s4 = +xp::_d) >> ':' >> (xp::s5 = +xp::_d) >> ':' >> (xp::s6 = +xp::_d) >> ' ' >>
102: (xp::s7 = (xp::set = '+', '-')) >> (xp::s8 = xp::repeat<2>(xp::_d)) >> (xp::s9 = xp::repeat<2>(xp::_d)) >> *xp::_))) {
103: mh.date = boost::posix_time::time_from_string(
104: match[3] + '-' + match[2] + '-' + match[1] + ' ' +
105: match[4] + ':' + match[5] + ':' + match[6]);
106: // Adjust time zone to UTC.
107: mh.date -= boost::posix_time::hours(boost::lexical_cast<int>(match[7] + match[8]))
108: + boost::posix_time::minutes(boost::lexical_cast<int>(match[7] + match[9]));
109: }
110: } else if ("Subject" == sect_title) {
111: mh.subject = DecodeString(sect_data);
112: } else if ("From" == sect_title) {
113: if (xp::regex_match(sect_data, match, xp::sregex(
114: !xp::as_xpr('"') >> (xp::s1 = *~(xp::set='<', '"')) >> !xp::as_xpr('"') >> *xp::_s
115: >> !xp::as_xpr("<">> (xp::s2 = *~xp::as_xpr('>')) >> '>')))) {
116: mh.from_name = boost::trim_copy(DecodeString(match[1]));
117: mh.from_address = match[2];
118: } else {
119: mh.from_name = DecodeString(sect_data);
120: }
121: }
122: in_data = false;
123: }
124:
125: if (xp::regex_match(line, match, xp::sregex(xp::bos >> (xp::s1=+~xp::_s) >> ": " >> (xp::s2=*xp::_)))) {
126: sect_title = match[1];
127: sect_data = match[2];
128: in_data = true;
129: } else if ( "." == line ) { break; }
130: }
131: }
132: catch(boost::system::system_error const& e) {
133: Disconnect();
134: throw e;
135: }
136: return mh;
137: }
138:
139: void POP3Client::Disconnect() throw() {
140: if (!is_active_) { return; }
141:
142: std::string line;
143: connecter_->WriteLine("QUIT");
144: connecter_->ReadLine(&line);
145:
146: connecter_.reset();
147: is_active_ = false;
148: }
149:
150: } // namespace mail
151: } // namespace mog
・main.cc
1: // Copyright (c) 2011 Mog Project. All rights reserved.
2:
3: #pragma comment(lib, "cryptlib.lib")
4:
5: #include <fstream>
6: #include <vector>
7:
8: #include <boost/scoped_ptr.hpp>
9: #include <boost/foreach.hpp>
10: #pragma warning(disable: 4512)
11: #include <boost/program_options.hpp>
12: #pragma warning(default: 4512)
13: #include <boost/format.hpp>
14: #include <boost/filesystem.hpp>
15: #include <boost/date_time/local_time_adjustor.hpp>
16: #include <boost/date_time/c_local_time_adjustor.hpp>
17: #include <boost/xpressive/xpressive.hpp>
18: #include <boost/mpl/if.hpp>
19:
20: #pragma warning(push)
21: #pragma warning(disable: 4083 100 127 189 244 505 512 615)
22: #include <cryptopp/aes.h>
23: #include <cryptopp/dh.h>
24: #include <cryptopp/modes.h>
25: #include <cryptopp/osrng.h>
26: #pragma warning(pop)
27:
28: #include "pop3.h"
29: #include "base64.h"
30:
31: using boost::posix_time::ptime;
32: using CryptoPP::AES;
33:
34: namespace po = boost::program_options;
35: namespace xp = boost::xpressive;
36:
37: namespace {
38:
39: enum ExitCode { kOK = 0, kErrorSyntax, kErrorSetup, kErrorFetch, kErrorWriteResult, kErrorFatal };
40: enum ExecutionMode { kModeFetch, kModeUsage, kModeSetup };
41: struct ProgramOption {
42: ProgramOption() : mode(kModeFetch) {}
43: ExecutionMode mode;
44: int out_count;
45: std::string out_format;
46: std::string ini_file_path;
47: };
48: struct ConnectOption {
49: ConnectOption()
50: :server_port("110") {}
51: std::string server_address;
52: std::string server_port;
53: std::string username;
54: std::string password;
55: };
56:
57: // Copy string with trailing null characters.
58: void CopyWholeString(std::string const& src, std::string * dst) {
59: std::string::const_iterator it_src = src.begin();
60: std::string::iterator it_dst = dst->begin();
61: while (it_src != src.end() && it_dst != dst->end()) { *it_dst++ = *it_src++; }
62: }
63:
64: // AES encrypt and decrypt.
65: template <bool IS_ENCRYPT>
66: std::string FilterString(std::string const& in_value, std::string const& iv) {
67: std::string const kEncryptKey = "mogmog";
68: std::string encrypt_key(AES::DEFAULT_KEYLENGTH, 0);
69: std::string initialization_vector(AES::BLOCKSIZE, 0);
70: CopyWholeString(kEncryptKey, &encrypt_key);
71: CopyWholeString(iv, &initialization_vector);
72:
73: boost::mpl::if_<boost::mpl::bool_<IS_ENCRYPT>,
74: CryptoPP::CTR_Mode<AES>::Encryption,
75: CryptoPP::CTR_Mode<AES>::Decryption>::type processor;
76: processor.SetKeyWithIV(reinterpret_cast<byte const*>(
77: encrypt_key.c_str()), encrypt_key.size(), reinterpret_cast<byte const*>(initialization_vector.c_str()));
78:
79: std::string out_value;
80: CryptoPP::StreamTransformationFilter stf(processor, new CryptoPP::StringSink(out_value));
81: stf.Put(reinterpret_cast<byte const*>(in_value.c_str()), in_value.size());
82: stf.MessageEnd();
83: return out_value;
84: }
85:
86: // Parse command line.
87: void ParseCommandLine(int argc, char * argv[], ProgramOption * option) {
88: po::options_description opt("Usage");
89: opt.add_options()
90: ("help", "Print this message.")
91: ("count", po::value<int>()->default_value(5), "Number of headers to get.")
92: ("format", po::value<std::string>()->default_value("[%m/%d %H:%M] %N <%A>%n %J"),
93: "Output format.\n"
94: "%y Last two digits of the year number\n"
95: "%Y Year number\n"
96: "%m Month number\n"
97: "%d Day number in the month\n"
98: "%H Hour number (24 hour system)\n"
99: "%M Minute number\n"
100: "%S Second number\n"
101: "%J Mail subject\n"
102: "%N Mail from-name\n"
103: "%A Mail from-address\n"
104: "%n New line\n"
105: "%% Character %")
106: ("ini-file", po::value<std::string>()->default_value("mogmail.ini"), "Path to the initial file.")
107: ("setup", "Remake the initial file.");
108: po::variables_map vm;
109: po::store(po::parse_command_line(argc, argv, opt), vm);
110: po::notify(vm);
111:
112: if (vm.count("help")) {
113: std::cout << opt << std::endl;
114: option->mode = kModeUsage;
115: return;
116: }
117: if (vm.count("setup")) { option->mode = kModeSetup; }
118: option->ini_file_path = vm["ini-file"].as<std::string>();
119: option->out_count = vm["count"].as<int>();
120: if (option->out_count <= 0) {
121: throw std::runtime_error("Count should be over 0.");
122: }
123: option->out_format = vm["format"].as<std::string>();
124: }
125:
126: // Read input from std::cin.
127: bool GetLine(std::string const& prompt, std::string * line) {
128: std::cout << prompt;
129: std::getline(std::cin, *line);
130: return !line->empty();
131: }
132:
133: // Save configuration.
134: void SaveConnectOption(std::string const& path, ConnectOption const& opt) {
135: std::ofstream ini_file(path);
136: if (!ini_file) { throw std::runtime_error("Failed to save ini-file."); }
137:
138: ini_file << "[POP3]\n";
139: ini_file << "SERVER=" << opt.server_address << "\n";
140: ini_file << "PORT=" << opt.server_port << "\n";
141: ini_file << "USERNAME=" << opt.username << "\n";
142: ini_file << "PASSWORD=" << mog::util::Base64::Encode(
143: FilterString<true>(opt.password, opt.username)) << "\n";
144: }
145:
146: // Load configuration.
147: void LoadConnectOption(std::string const& path, ConnectOption *opt) {
148: std::ifstream ini_file(path);
149: if (!ini_file) {
150: throw std::runtime_error("Failed to open " + path +".\n" +
151: "Execute '--setup' first.\n");
152: }
153:
154: std::string line;
155: std::string password_tmp;
156: xp::smatch match;
157: while (std::getline(ini_file, line)) {
158: if (xp::regex_match(line, match,
159: xp::sregex(xp::bos >> (xp::s1=+~(xp::set='=')) >> "=" >> (xp::s2=*xp::_)))) {
160: if ("SERVER" == match[1]) {
161: opt->server_address = match[2];
162: } else if ("PORT" == match[1]) {
163: opt->server_port = match[2];
164: } else if ("USERNAME" == match[1]) {
165: opt->username = match[2];
166: } else if ("PASSWORD" == match[1]) {
167: password_tmp = mog::util::Base64::Decode(match[2]);
168: }
169: }
170: }
171: opt->password = FilterString<false>(password_tmp, opt->username);
172: }
173:
174: // Interactive configuration setup.
175: void SetupConfig(std::string const& path) {
176: ConnectOption conn_opt;
177: std::cout << boost::format("*** SETUP MODE ***") << std::endl;
178: std::string line;
179: for (;;) { if (GetLine("POP3 Server Address?: ", &conn_opt.server_address)) { break; } }
180: if (GetLine("POP3 Server Port?[110]: ", &line)) { conn_opt.server_port = line; }
181: for (;;) { if (GetLine("Username: ", &conn_opt.username)) { break; } }
182: for (;;) { if (GetLine("Password: ", &conn_opt.password)) { break; } }
183:
184: // Test connection.
185: std::cout << "Testing connection..." << std::endl;
186: boost::scoped_ptr<mog::mail::POP3Client> pop_client;
187: pop_client.reset(new mog::mail::POP3Client(
188: conn_opt.server_address, conn_opt.server_port, conn_opt.username, conn_opt.password));
189: pop_client.reset();
190: std::cout << "OK." << std::endl;
191:
192: // Save configuration.
193: SaveConnectOption(path, conn_opt);
194: std::cout << "Saved configuration to " << path << "." << std::endl;
195: }
196:
197: // Format result.
198: std::string FormatResult(mog::mail::MailHeader const& mh, std::string const& format) {
199: std::string out_value;
200: tm local_time = boost::posix_time::to_tm(boost::date_time::c_local_adjustor<ptime>::utc_to_local(mh.date));
201:
202: bool flag_escaped = false;
203: for (std::string::const_iterator it = format.begin(); it != format.end(); ++it) {
204: if (flag_escaped) {
205: switch (*it) {
206: case 'y': out_value.append((boost::format("%02d") % (local_time.tm_year % 100)).str()); break;
207: case 'Y': out_value.append((boost::format("%04d") % (local_time.tm_year + 1900)).str()); break;
208: case 'm': out_value.append((boost::format("%02d") % (local_time.tm_mon + 1)).str()); break;
209: case 'd': out_value.append((boost::format("%02d") % local_time.tm_mday).str()); break;
210: case 'H': out_value.append((boost::format("%02d") % local_time.tm_hour).str()); break;
211: case 'M': out_value.append((boost::format("%02d") % local_time.tm_min).str()); break;
212: case 'S': out_value.append((boost::format("%02d") % local_time.tm_sec).str()); break;
213: case 'J': out_value.append(mh.subject); break;
214: case 'N': out_value.append(mh.from_name); break;
215: case 'A': out_value.append(mh.from_address); break;
216: case 'n': out_value.push_back('\n'); break;
217: case '%': out_value.push_back('%'); break;
218: default: throw std::runtime_error("Invalid output format: " + format);
219: }
220: flag_escaped = false;
221: } else if ('%' == *it) {
222: flag_escaped = true;
223: } else {
224: out_value.push_back(*it);
225: }
226: }
227: return out_value;
228: }
229:
230: } // namespace
231:
232: ////////////////////////////////////////////////////////////////////////////////
233: // main
234: int main(int argc, char * argv[]) {
235: ExitCode exit_code = kOK;
236: try {
237: ProgramOption option;
238:
239: // Parse command line options.
240: try {
241: ParseCommandLine(argc, argv, &option);
242: } catch(std::exception const& e) {
243: std::cout << "Error: " << e.what() << std::endl;
244: std::cout << "Check '--help' option.";
245: return kErrorSyntax;
246: }
247:
248: // Check mode.
249: switch (option.mode) {
250: case kModeUsage:
251: return kOK;
252: case kModeSetup:
253: // Save configuration file.
254: try {
255: SetupConfig(option.ini_file_path);
256: } catch(std::exception const& e) {
257: std::cout << "Error: " << e.what() << std::endl;
258: return kErrorSetup;
259: }
260: return kOK;
261: case kModeFetch:
262: break;
263: default:
264: assert(!"Unexpected mode.");
265: }
266:
267: int num_mails = 0;
268: std::vector<mog::mail::MailHeader> mail_header;
269: try {
270: // Load parameters.
271: ConnectOption conn_opt;
272: LoadConnectOption(option.ini_file_path, &conn_opt);
273:
274: // Connect to the POP3 server.
275: boost::scoped_ptr<mog::mail::POP3Client> pop_client;
276: pop_client.reset(new mog::mail::POP3Client(
277: conn_opt.server_address, conn_opt.server_port, conn_opt.username, conn_opt.password));
278: // Get mail count.
279: num_mails = pop_client->num_mails();
280: // Get headers.
281: for (int i = num_mails; i > num_mails - option.out_count && i > 0; --i) {
282: mail_header.push_back(pop_client->GetHeader(i));
283: }
284: // Disconnect from the server.
285: pop_client.reset();
286: } catch(std::exception const& e) {
287: std::cout << std::string("Error: ") + e.what();
288: exit_code = kErrorFetch;
289: }
290: // Format result.
291: try {
292: for (int i = 0; i < option.out_count; ++i) {
293: if (i < num_mails) {
294: std::cout << FormatResult(mail_header[i], option.out_format) << std::endl;
295: }
296: }
297: } catch(std::exception const& e) {
298: std::cout << std::string("Error: ") + e.what();
299: exit_code = kErrorWriteResult;
300: }
301: } catch(...) {
302: std::cout << "FATAL EXCEPTION!\n";
303: return kErrorFatal;
304: }
305: // system("pause");
306: return exit_code;
307: }
0 件のコメント:
コメントを投稿