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 件のコメント:
コメントを投稿